Files
local-mcp/go-server/internal/store/agent.go
Brandon Zhang 8a0dffbcae 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)
2026-03-27 15:45:26 +08:00

59 lines
1.6 KiB
Go

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
}