Files
local-mcp/go-server/internal/store/settings.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

70 lines
1.6 KiB
Go

package store
import (
"database/sql"
"fmt"
"strconv"
"github.com/local-mcp/local-mcp-go/internal/models"
)
// SettingsStore reads and writes the settings table.
type SettingsStore struct {
db *sql.DB
}
// NewSettingsStore creates a SettingsStore backed by db.
func NewSettingsStore(db *sql.DB) *SettingsStore {
return &SettingsStore{db: db}
}
// Get returns the current settings.
func (s *SettingsStore) Get() (models.Settings, error) {
rows, err := s.db.Query(`SELECT key, value FROM settings`)
if err != nil {
return models.Settings{}, fmt.Errorf("get settings: %w", err)
}
defer rows.Close()
cfg := models.Settings{
DefaultWaitSeconds: 10,
AgentStaleAfterSeconds: 30,
}
for rows.Next() {
var key, value string
if err := rows.Scan(&key, &value); err != nil {
return cfg, err
}
switch key {
case "default_wait_seconds":
if n, err := strconv.Atoi(value); err == nil {
cfg.DefaultWaitSeconds = n
}
case "default_empty_response":
cfg.DefaultEmptyResponse = value
case "agent_stale_after_seconds":
if n, err := strconv.Atoi(value); err == nil {
cfg.AgentStaleAfterSeconds = n
}
}
}
return cfg, rows.Err()
}
// Update saves settings. Only non-nil fields are updated; pass a partial
// struct pointer using the Patch helper below.
func (s *SettingsStore) Update(patch models.Settings) error {
_, err := s.db.Exec(`
INSERT OR REPLACE INTO settings (key, value) VALUES
('default_wait_seconds', ?),
('default_empty_response', ?),
('agent_stale_after_seconds', ?)`,
strconv.Itoa(patch.DefaultWaitSeconds),
patch.DefaultEmptyResponse,
strconv.Itoa(patch.AgentStaleAfterSeconds),
)
return err
}