Currently at every run we generate a new token, we can overcome this but is seems that we can make the whole process more seamless somehow, Cluade generated the following code, which I haven't yet reviewed, needs to be reviewed and check if we need it and makes the process more seamless
// cmd/ebpf-mcp/main.go - Enhanced with seamless token management
package main
import (
"crypto/rand"
"encoding/hex"
"flag"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/sameehj/ebpf-mcp/internal/server"
"github.com/sameehj/ebpf-mcp/internal/tools"
)
const (
Version = "v1.0.0"
TokenFile = ".ebpf-mcp-token"
)
var (
transport = flag.String("t", "stdio", "Transport type: stdio, http")
port = flag.String("p", "8080", "HTTP port (only for http transport)")
debug = flag.Bool("debug", false, "Enable debug logging")
authToken = flag.String("token", "", "Custom auth token (auto-generated if not provided)")
tokenDir = flag.String("token-dir", "", "Directory to store token file (default: home directory)")
install = flag.Bool("install", false, "Install as systemd service")
setup = flag.Bool("setup", false, "Interactive setup with Claude CLI integration")
)
func main() {
flag.Parse()
if *debug {
log.Printf("[DEBUG] Debug logging enabled")
}
fmt.Printf("🔧 eBPF MCP Server %s\n", Version)
// Handle special modes
if *install {
handleInstall()
return
}
if *setup {
handleSetup()
return
}
// Register all tools
tools.RegisterAll()
log.Printf("[DEBUG] Creating MCP server...")
// Handle authentication token
token := getOrCreateToken()
// Create and start server
srv := server.NewMCPServer()
tools.RegisterAllWithMCP(srv)
log.Printf("[DEBUG] Registering tools...")
switch *transport {
case "stdio":
startSTDIOServer(srv)
case "http":
startHTTPServer(srv, token)
default:
log.Fatalf("Unknown transport: %s", *transport)
}
}
// getOrCreateToken manages authentication tokens seamlessly
func getOrCreateToken() string {
// 1. Check command line flag
if *authToken != "" {
log.Printf("🔐 Using provided auth token")
return *authToken
}
// 2. Check environment variable
if envToken := os.Getenv("MCP_AUTH_TOKEN"); envToken != "" {
log.Printf("🔐 Using token from MCP_AUTH_TOKEN environment variable")
return envToken
}
// 3. Check for saved token file
tokenPath := getTokenPath()
if savedToken := loadSavedToken(tokenPath); savedToken != "" {
log.Printf("🔐 Using saved token from %s", tokenPath)
return savedToken
}
// 4. Generate and save new token
newToken := generateSecureToken()
if saveToken(tokenPath, newToken) {
log.Printf("🔐 Generated and saved new token to %s", tokenPath)
log.Printf("💡 Token: %s", newToken)
fmt.Printf("\n🎯 Quick Setup Commands:\n")
fmt.Printf(" Export token: export MCP_AUTH_TOKEN=%s\n", newToken)
fmt.Printf(" Claude CLI: claude mcp add ebpf http://localhost:%s/mcp -t http -H \"Authorization: Bearer %s\"\n", *port, newToken)
fmt.Printf("\n")
} else {
log.Printf("🔐 Auto-generated token (not saved): %s", newToken)
log.Printf("💡 Set MCP_AUTH_TOKEN environment variable to persist this token")
}
return newToken
}
// getTokenPath determines where to store the token file
func getTokenPath() string {
if *tokenDir != "" {
return filepath.Join(*tokenDir, TokenFile)
}
// Try home directory
if homeDir, err := os.UserHomeDir(); err == nil {
return filepath.Join(homeDir, TokenFile)
}
// Fallback to current directory
return TokenFile
}
// loadSavedToken attempts to load a previously saved token
func loadSavedToken(path string) string {
data, err := os.ReadFile(path)
if err != nil {
return ""
}
token := strings.TrimSpace(string(data))
if len(token) == 64 { // Expected length for hex-encoded 32-byte token
return token
}
return ""
}
// saveToken saves the token to file
func saveToken(path string, token string) bool {
err := os.WriteFile(path, []byte(token), 0600) // Readable only by owner
return err == nil
}
// generateSecureToken creates a cryptographically secure token
func generateSecureToken() string {
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
log.Printf("Failed to generate secure token: %v", err)
// Fallback to timestamp-based token
return fmt.Sprintf("%x", time.Now().UnixNano())
}
return hex.EncodeToString(bytes)
}
// handleSetup provides interactive setup
func handleSetup() {
fmt.Printf("🎯 eBPF MCP Server Interactive Setup\n\n")
// Get or create token
token := getOrCreateToken()
fmt.Printf("📋 Setup Complete!\n\n")
fmt.Printf("1. Start the server:\n")
fmt.Printf(" sudo ./ebpf-mcp-server -t http -debug\n\n")
fmt.Printf("2. Add to Claude CLI:\n")
fmt.Printf(" claude mcp add ebpf http://localhost:%s/mcp -t http -H \"Authorization: Bearer %s\"\n\n", *port, token)
fmt.Printf("3. Test the connection:\n")
fmt.Printf(" claude --debug\n")
fmt.Printf(" Then try: 'What eBPF capabilities does this system have?'\n\n")
fmt.Printf("💡 Pro Tips:\n")
fmt.Printf(" - Set MCP_AUTH_TOKEN=%s to persist the token\n", token)
fmt.Printf(" - Use --install flag to set up as systemd service\n")
fmt.Printf(" - Add -debug flag for troubleshooting\n\n")
}
// handleInstall sets up systemd service
func handleInstall() {
serviceContent := fmt.Sprintf(`[Unit]
Description=eBPF MCP Server
After=network.target
[Service]
Type=simple
User=root
ExecStart=%s -t http -p %s
Environment=MCP_AUTH_TOKEN=%s
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
`, getExecutablePath(), *port, getOrCreateToken())
servicePath := "/etc/systemd/system/ebpf-mcp.service"
if err := os.WriteFile(servicePath, []byte(serviceContent), 0644); err != nil {
log.Fatalf("Failed to write service file: %v", err)
}
fmt.Printf("✅ Systemd service installed to %s\n\n", servicePath)
fmt.Printf("To enable and start:\n")
fmt.Printf(" sudo systemctl daemon-reload\n")
fmt.Printf(" sudo systemctl enable ebpf-mcp\n")
fmt.Printf(" sudo systemctl start ebpf-mcp\n\n")
fmt.Printf("To check status:\n")
fmt.Printf(" sudo systemctl status ebpf-mcp\n")
}
// getExecutablePath returns the full path to the current executable
func getExecutablePath() string {
if path, err := os.Executable(); err == nil {
return path
}
return "./ebpf-mcp-server" // fallback
}
// startHTTPServer starts the HTTP server with enhanced logging
func startHTTPServer(srv *server.MCPServer, token string) {
fmt.Printf("🔧 ebpf-mcp HTTP server listening on :%s\n", *port)
fmt.Printf(" MCP endpoint: http://localhost:%s/mcp\n", *port)
fmt.Printf(" Discovery: http://localhost:%s/.well-known/mcp/metadata.json\n", *port)
fmt.Printf(" Health check: http://localhost:%s/health\n", *port)
fmt.Printf("\n💡 Ready for Claude CLI integration!\n\n")
if err := srv.ListenAndServeHTTP(":"+*port, token); err != nil {
log.Fatalf("HTTP server failed: %v", err)
}
}
// startSTDIOServer starts the STDIO server
func startSTDIOServer(srv *server.MCPServer) {
fmt.Printf("🔧 ebpf-mcp STDIO server starting...\n")
if err := srv.ListenAndServeSTDIO(); err != nil {
log.Fatalf("STDIO server failed: %v", err)
}
}
Currently at every run we generate a new token, we can overcome this but is seems that we can make the whole process more seamless somehow, Cluade generated the following code, which I haven't yet reviewed, needs to be reviewed and check if we need it and makes the process more seamless