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)
78 lines
2.3 KiB
Go
78 lines
2.3 KiB
Go
// Package db manages the SQLite connection and schema migrations.
|
|
package db
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
_ "modernc.org/sqlite" // pure-Go SQLite driver, no CGo required
|
|
)
|
|
|
|
// schema creates all tables if they do not already exist.
|
|
const schema = `
|
|
CREATE TABLE IF NOT EXISTS instructions (
|
|
id TEXT PRIMARY KEY,
|
|
content TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'pending',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
consumed_at TEXT,
|
|
consumed_by_agent_id TEXT,
|
|
position INTEGER NOT NULL DEFAULT 0
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_instructions_status ON instructions(status);
|
|
CREATE INDEX IF NOT EXISTS idx_instructions_position ON instructions(position);
|
|
|
|
CREATE TABLE IF NOT EXISTS settings (
|
|
key TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS agent_activity (
|
|
agent_id TEXT PRIMARY KEY,
|
|
last_seen_at TEXT NOT NULL,
|
|
last_fetch_at TEXT NOT NULL,
|
|
last_result_type TEXT NOT NULL
|
|
);
|
|
`
|
|
|
|
// defaultSettings seeds initial values; OR IGNORE means existing rows are unchanged.
|
|
const defaultSettings = `
|
|
INSERT OR IGNORE INTO settings (key, value) VALUES ('default_wait_seconds', '10');
|
|
INSERT OR IGNORE INTO settings (key, value) VALUES ('default_empty_response', '');
|
|
INSERT OR IGNORE INTO settings (key, value) VALUES ('agent_stale_after_seconds','30');
|
|
`
|
|
|
|
// Open opens (creating if necessary) a SQLite database at dbPath, applies the
|
|
// schema, and seeds default settings.
|
|
func Open(dbPath string) (*sql.DB, error) {
|
|
dir := filepath.Dir(dbPath)
|
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
return nil, fmt.Errorf("create db directory: %w", err)
|
|
}
|
|
|
|
db, err := sql.Open("sqlite", dbPath+"?_pragma=journal_mode(WAL)&_pragma=foreign_keys(on)&_pragma=busy_timeout(5000)")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("open database: %w", err)
|
|
}
|
|
|
|
// Serialise all writes through a single connection to avoid locking.
|
|
db.SetMaxOpenConns(1)
|
|
|
|
if _, err := db.Exec(schema); err != nil {
|
|
_ = db.Close()
|
|
return nil, fmt.Errorf("apply schema: %w", err)
|
|
}
|
|
|
|
if _, err := db.Exec(defaultSettings); err != nil {
|
|
_ = db.Close()
|
|
return nil, fmt.Errorf("seed settings: %w", err)
|
|
}
|
|
|
|
return db, nil
|
|
}
|
|
|