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:
73
go-server/internal/events/broker.go
Normal file
73
go-server/internal/events/broker.go
Normal file
@@ -0,0 +1,73 @@
|
||||
// Package events provides an SSE event broker for fanning out server-sent
|
||||
// events to browser clients watching /api/events.
|
||||
package events
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Event is the wire format sent to browser clients.
|
||||
type Event struct {
|
||||
Type string `json:"type"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
// Broker distributes named events to all currently-connected SSE clients.
|
||||
// Clients subscribe by calling Subscribe(); they must call Unsubscribe() when
|
||||
// done to avoid goroutine leaks.
|
||||
type Broker struct {
|
||||
mu sync.RWMutex
|
||||
clients map[chan []byte]struct{}
|
||||
}
|
||||
|
||||
// NewBroker creates a ready-to-use Broker.
|
||||
func NewBroker() *Broker {
|
||||
return &Broker{clients: make(map[chan []byte]struct{})}
|
||||
}
|
||||
|
||||
// Subscribe returns a channel that will receive serialised SSE "data: ..." lines.
|
||||
func (b *Broker) Subscribe() chan []byte {
|
||||
ch := make(chan []byte, 32) // buffered so a slow reader doesn't stall others
|
||||
b.mu.Lock()
|
||||
b.clients[ch] = struct{}{}
|
||||
b.mu.Unlock()
|
||||
return ch
|
||||
}
|
||||
|
||||
// Unsubscribe removes the channel and closes it.
|
||||
func (b *Broker) Unsubscribe(ch chan []byte) {
|
||||
b.mu.Lock()
|
||||
delete(b.clients, ch)
|
||||
b.mu.Unlock()
|
||||
close(ch)
|
||||
}
|
||||
|
||||
// Broadcast encodes and sends an event to all subscribers. Slow subscribers
|
||||
// are skipped (their buffered channel is full) to prevent head-of-line blocking.
|
||||
func (b *Broker) Broadcast(eventType string, data any) {
|
||||
ev := Event{
|
||||
Type: eventType,
|
||||
Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
|
||||
Data: data,
|
||||
}
|
||||
payload, err := json.Marshal(ev)
|
||||
if err != nil {
|
||||
return // should never happen
|
||||
}
|
||||
line := fmt.Sprintf("data: %s\n\n", payload)
|
||||
msg := []byte(line)
|
||||
|
||||
b.mu.RLock()
|
||||
for ch := range b.clients {
|
||||
select {
|
||||
case ch <- msg:
|
||||
default: // skip stalled clients
|
||||
}
|
||||
}
|
||||
b.mu.RUnlock()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user