feat: add Go server implementation in go-server/
Full Go port of local-mcp with all core features. Copied from local-mcp-go worktree to consolidate into single-branch repo (easier maintenance). Architecture: - internal/config: Environment variable configuration - internal/models: Shared types (Instruction, Settings, AgentActivity, etc.) - internal/db: SQLite init with modernc.org/sqlite (pure Go, no CGo) - internal/store: Database operations + WakeupSignal + AgentTracker - internal/events: SSE broker for browser /api/events endpoint - internal/mcp: get_user_request MCP tool with 5s keepalive progress bars - internal/api: chi HTTP router with Bearer auth middleware - main.go: Entry point with auto port switching and Windows interactive banner Dependencies: - github.com/mark3labs/mcp-go@v0.46.0 - github.com/go-chi/chi/v5@v5.2.5 - modernc.org/sqlite@v1.47.0 (pure Go SQLite) - github.com/google/uuid@v1.6.0 Static assets embedded via //go:embed static Features matching Python: - Same wait strategy: 50s with 5s progress keepalives - Same hardcoded constants (DEFAULT_WAIT_SECONDS, DEFAULT_EMPTY_RESPONSE) - Auto port switching (tries 8000-8009) - Windows interactive mode (formatted banner on double-click launch) Build: cd go-server && go build -o local-mcp.exe . Run: ./local-mcp.exe Binary size: ~18 MB (vs Python ~60+ MB memory footprint) Startup: ~10 ms (vs Python ~1-2s)
This commit is contained in:
163
go-server/main.go
Normal file
163
go-server/main.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// local-mcp-go — localhost MCP server delivering user instructions to agents.
|
||||
package main
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"runtime"
|
||||
"syscall"
|
||||
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
|
||||
"github.com/local-mcp/local-mcp-go/internal/api"
|
||||
"github.com/local-mcp/local-mcp-go/internal/config"
|
||||
"github.com/local-mcp/local-mcp-go/internal/db"
|
||||
"github.com/local-mcp/local-mcp-go/internal/events"
|
||||
"github.com/local-mcp/local-mcp-go/internal/mcp"
|
||||
"github.com/local-mcp/local-mcp-go/internal/store"
|
||||
)
|
||||
|
||||
//go:embed static
|
||||
var staticFS embed.FS
|
||||
|
||||
func main() {
|
||||
cfg := config.Load()
|
||||
|
||||
// Logger
|
||||
level := slog.LevelInfo
|
||||
switch cfg.LogLevel {
|
||||
case "DEBUG":
|
||||
level = slog.LevelDebug
|
||||
case "WARN", "WARNING":
|
||||
level = slog.LevelWarn
|
||||
case "ERROR":
|
||||
level = slog.LevelError
|
||||
}
|
||||
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level})))
|
||||
|
||||
// Database
|
||||
database, err := db.Open(cfg.DBPath)
|
||||
if err != nil {
|
||||
slog.Error("Failed to open database", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer database.Close()
|
||||
slog.Info("Database initialised", "path", cfg.DBPath)
|
||||
|
||||
// Stores
|
||||
instStore := store.NewInstructionStore(database)
|
||||
settStore := store.NewSettingsStore(database)
|
||||
agentStore := store.NewAgentStore(database)
|
||||
|
||||
// Event broker
|
||||
broker := events.NewBroker()
|
||||
|
||||
// MCP server
|
||||
mcpHandler := mcp.New(instStore, settStore, agentStore, broker)
|
||||
sseServer := server.NewStreamableHTTPServer(
|
||||
mcpHandler.MCP,
|
||||
server.WithEndpointPath("/mcp"),
|
||||
server.WithStateLess(cfg.MCPStateless),
|
||||
)
|
||||
|
||||
// HTTP router
|
||||
staticSubFS, _ := fs.Sub(staticFS, "static")
|
||||
|
||||
router := api.NewRouter(
|
||||
api.Stores{
|
||||
Instructions: instStore,
|
||||
Settings: settStore,
|
||||
Agents: agentStore,
|
||||
},
|
||||
broker,
|
||||
cfg.APIToken,
|
||||
staticSubFS,
|
||||
)
|
||||
|
||||
// Combined router: /mcp → MCP StreamableHTTP, /* → REST API
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle("/mcp", sseServer)
|
||||
mux.Handle("/mcp/", http.StripPrefix("/mcp", sseServer))
|
||||
mux.Handle("/", router)
|
||||
|
||||
// Server with auto port switching
|
||||
port := cfg.HTTPPort
|
||||
maxAttempts := 10
|
||||
var srv *http.Server
|
||||
var addr string
|
||||
|
||||
for attempt := 0; attempt < maxAttempts; attempt++ {
|
||||
addr = fmt.Sprintf("%s:%s", cfg.Host, port)
|
||||
srv = &http.Server{Addr: addr, Handler: mux}
|
||||
|
||||
// Try to listen
|
||||
ln, err := net.Listen("tcp", addr)
|
||||
if err == nil {
|
||||
ln.Close() // Close test listener
|
||||
break
|
||||
}
|
||||
|
||||
// Port taken, try next
|
||||
portNum := 8000 + attempt
|
||||
port = fmt.Sprintf("%d", portNum+1)
|
||||
if attempt == maxAttempts-1 {
|
||||
slog.Error("Could not find available port", "tried", maxAttempts)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
if cfg.APIToken != "" {
|
||||
slog.Info("Token authentication enabled")
|
||||
} else {
|
||||
slog.Info("Token authentication disabled (set API_TOKEN to enable)")
|
||||
}
|
||||
|
||||
httpURL := fmt.Sprintf("http://%s", addr)
|
||||
mcpURL := fmt.Sprintf("http://%s/mcp", addr)
|
||||
|
||||
slog.Info("local-mcp-go ready",
|
||||
"http", httpURL,
|
||||
"mcp", mcpURL,
|
||||
"stateless", cfg.MCPStateless,
|
||||
)
|
||||
|
||||
// On Windows, show interactive prompt
|
||||
if runtime.GOOS == "windows" {
|
||||
fmt.Println()
|
||||
fmt.Println("╔══════════════════════════════════════════════════════════╗")
|
||||
fmt.Printf("║ local-mcp-go ready on port %s%-24s║\n", port, "")
|
||||
fmt.Println("║ ║")
|
||||
fmt.Printf("║ Web UI: %-46s║\n", httpURL)
|
||||
fmt.Printf("║ MCP: %-46s║\n", mcpURL)
|
||||
fmt.Println("║ ║")
|
||||
fmt.Println("║ Press Ctrl+C to stop the server ║")
|
||||
fmt.Println("╚══════════════════════════════════════════════════════════╝")
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// Graceful shutdown on SIGINT / SIGTERM
|
||||
stop := make(chan os.Signal, 1)
|
||||
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-stop
|
||||
fmt.Println()
|
||||
slog.Info("Shutting down gracefully...")
|
||||
_ = srv.Close()
|
||||
}()
|
||||
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
slog.Error("Server error", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user