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:
58
go-server/internal/store/agent.go
Normal file
58
go-server/internal/store/agent.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/local-mcp/local-mcp-go/internal/models"
|
||||
)
|
||||
|
||||
// AgentStore records and retrieves agent connectivity data.
|
||||
type AgentStore struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
// NewAgentStore creates an AgentStore backed by db.
|
||||
func NewAgentStore(db *sql.DB) *AgentStore {
|
||||
return &AgentStore{db: db}
|
||||
}
|
||||
|
||||
// Record upserts agent activity for agentID with the given result type.
|
||||
func (s *AgentStore) Record(agentID, resultType string) error {
|
||||
now := time.Now().UTC().Format(time.RFC3339Nano)
|
||||
_, err := s.db.Exec(`
|
||||
INSERT INTO agent_activity (agent_id, last_seen_at, last_fetch_at, last_result_type)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(agent_id) DO UPDATE SET
|
||||
last_seen_at = excluded.last_seen_at,
|
||||
last_fetch_at = excluded.last_fetch_at,
|
||||
last_result_type = excluded.last_result_type`,
|
||||
agentID, now, now, resultType)
|
||||
return err
|
||||
}
|
||||
|
||||
// Latest returns the most recently active agent, or nil if no agent has ever
|
||||
// called get_user_request.
|
||||
func (s *AgentStore) Latest() (*models.AgentActivity, error) {
|
||||
row := s.db.QueryRow(`
|
||||
SELECT agent_id, last_seen_at, last_fetch_at, last_result_type
|
||||
FROM agent_activity
|
||||
ORDER BY last_seen_at DESC
|
||||
LIMIT 1`)
|
||||
|
||||
var a models.AgentActivity
|
||||
var seenStr, fetchStr string
|
||||
err := row.Scan(&a.AgentID, &seenStr, &fetchStr, &a.LastResultType)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("latest agent: %w", err)
|
||||
}
|
||||
|
||||
a.LastSeenAt, _ = time.Parse(time.RFC3339Nano, seenStr)
|
||||
a.LastFetchAt, _ = time.Parse(time.RFC3339Nano, fetchStr)
|
||||
return &a, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user