diff --git a/go-server/README-GO.md b/go-server/README-GO.md index 09c2789..006ba25 100644 --- a/go-server/README-GO.md +++ b/go-server/README-GO.md @@ -146,15 +146,14 @@ All routes are local-only and intended for `localhost` usage. **Purpose** - Return the next pending instruction, if one exists. -- If none exists, wait for a configurable duration, then return either an empty response or a configured default response. +- If none exists, wait for a configurable duration, then return the server-controlled default response. - Record agent activity so the UI can infer whether an agent is currently connected/recently active. **Suggested input schema** ```json { - "agent_id": "optional-string", - "default_response_override": "optional-string" + "agent_id": "optional-string" } ``` @@ -194,7 +193,7 @@ All routes are local-only and intended for `localhost` usage. "status": "ok", "result_type": "default_response", "instruction": null, - "response": "No new instructions available.", + "response": "call this tool `get_user_request` again to fetch latest user input...", "remaining_pending": 0, "waited_seconds": 10 } @@ -250,7 +249,7 @@ Returns current server and agent summary. }, "settings": { "default_wait_seconds": 10, - "default_empty_response": "No new instructions available.", + "default_empty_response": "call this tool `get_user_request` again to fetch latest user input...", "agent_stale_after_seconds": 30 } } @@ -347,7 +346,7 @@ Returns editable runtime settings. ```json { "default_wait_seconds": 10, - "default_empty_response": "No new instructions available.", + "default_empty_response": "call this tool `get_user_request` again to fetch latest user input...", "agent_stale_after_seconds": 30 } ``` @@ -562,7 +561,7 @@ The server starts on `http://localhost:8000` by default. | `DB_PATH` | `data/local_mcp.sqlite3` | SQLite database path | | `LOG_LEVEL` | `INFO` | Logging level | | `DEFAULT_WAIT_SECONDS` | `10` | Default tool wait timeout | -| `DEFAULT_EMPTY_RESPONSE` | _(empty)_ | Default response when queue is empty | +| `DEFAULT_EMPTY_RESPONSE` | `call this tool \`get_user_request\` again to fetch latest user input...` | Default response when queue is empty | | `AGENT_STALE_AFTER_SECONDS` | `30` | Seconds of inactivity before agent shown as idle | | `MCP_STATELESS` | `true` | `true` for stateless sessions (survives restarts, recommended); `false` for stateful | | `API_TOKEN` | _(empty)_ | When set, all `/api/*` and `/mcp` requests require `Authorization: Bearer `; web UI prompts for the token on first load | diff --git a/go-server/internal/api/config.go b/go-server/internal/api/config.go index 93b4013..d15a918 100644 --- a/go-server/internal/api/config.go +++ b/go-server/internal/api/config.go @@ -23,7 +23,6 @@ func handleUpdateConfig(stores Stores, broker *events.Broker) http.HandlerFunc { // Decode partial patch var patch struct { DefaultWaitSeconds *int `json:"default_wait_seconds"` - DefaultEmptyResponse *string `json:"default_empty_response"` AgentStaleAfterSeconds *int `json:"agent_stale_after_seconds"` } if err := json.NewDecoder(r.Body).Decode(&patch); err != nil { @@ -42,9 +41,6 @@ func handleUpdateConfig(stores Stores, broker *events.Broker) http.HandlerFunc { if patch.DefaultWaitSeconds != nil { current.DefaultWaitSeconds = *patch.DefaultWaitSeconds } - if patch.DefaultEmptyResponse != nil { - current.DefaultEmptyResponse = *patch.DefaultEmptyResponse - } if patch.AgentStaleAfterSeconds != nil { current.AgentStaleAfterSeconds = *patch.AgentStaleAfterSeconds } diff --git a/go-server/internal/config/config.go b/go-server/internal/config/config.go index 54e7bd6..c4cc456 100644 --- a/go-server/internal/config/config.go +++ b/go-server/internal/config/config.go @@ -6,6 +6,8 @@ import ( "strconv" ) +const DefaultEmptyResponse = "call this tool `get_user_request` again to fetch latest user input..." + // Config holds all runtime configuration values for local-mcp. type Config struct { Host string @@ -13,7 +15,6 @@ type Config struct { DBPath string LogLevel string DefaultWaitSeconds int - DefaultEmptyResponse string AgentStaleAfterSeconds int MCPStateless bool APIToken string @@ -27,7 +28,6 @@ func Load() Config { DBPath: getEnv("DB_PATH", "data/local_mcp.sqlite3"), LogLevel: getEnv("LOG_LEVEL", "INFO"), DefaultWaitSeconds: getEnvInt("DEFAULT_WAIT_SECONDS", 10), - DefaultEmptyResponse: getEnv("DEFAULT_EMPTY_RESPONSE", ""), AgentStaleAfterSeconds: getEnvInt("AGENT_STALE_AFTER_SECONDS", 30), MCPStateless: getEnvBool("MCP_STATELESS", true), APIToken: getEnv("API_TOKEN", ""), @@ -58,4 +58,3 @@ func getEnvBool(key string, defaultVal bool) bool { } return defaultVal } - diff --git a/go-server/internal/db/db.go b/go-server/internal/db/db.go index 655188f..2e99e85 100644 --- a/go-server/internal/db/db.go +++ b/go-server/internal/db/db.go @@ -40,11 +40,10 @@ CREATE TABLE IF NOT EXISTS agent_activity ( ` // 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'); -` +const defaultSettings = "" + + "INSERT OR IGNORE INTO settings (key, value) VALUES ('default_wait_seconds', '10');\n" + + "INSERT OR IGNORE INTO settings (key, value) VALUES ('agent_stale_after_seconds', '30');\n" + + "DELETE FROM settings WHERE key = 'default_empty_response';\n" // Open opens (creating if necessary) a SQLite database at dbPath, applies the // schema, and seeds default settings. @@ -74,4 +73,3 @@ func Open(dbPath string) (*sql.DB, error) { return db, nil } - diff --git a/go-server/internal/mcp/handler.go b/go-server/internal/mcp/handler.go index 9464335..5719b6d 100644 --- a/go-server/internal/mcp/handler.go +++ b/go-server/internal/mcp/handler.go @@ -11,6 +11,7 @@ import ( "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" + "github.com/local-mcp/local-mcp-go/internal/config" "github.com/local-mcp/local-mcp-go/internal/events" "github.com/local-mcp/local-mcp-go/internal/models" "github.com/local-mcp/local-mcp-go/internal/store" @@ -25,9 +26,6 @@ const ( // multiple keepalive progress updates. defaultWaitSeconds = 50 - // defaultEmptyResponse is returned when the queue is empty after waiting. - defaultEmptyResponse = "call this tool `get_user_request` again to fetch latest user input..." - // keepaliveInterval controls how often a log notification is sent to the // client while waiting. Reduced to 5s (from 20s) for more frequent progress updates. // This keeps transport-level TCP/HTTP read timeouts from firing. @@ -38,9 +36,9 @@ const ( // Handler wraps the MCP server and holds references to the stores it needs. type Handler struct { - MCP *server.MCPServer - instStore *store.InstructionStore - settStore *store.SettingsStore + MCP *server.MCPServer + instStore *store.InstructionStore + settStore *store.SettingsStore agentStore *store.AgentStore broker *events.Broker } @@ -53,7 +51,7 @@ func New( broker *events.Broker, ) *Handler { h := &Handler{ - MCP: server.NewMCPServer("local-mcp", "1.0.0"), + MCP: server.NewMCPServer("local-mcp", "1.0.0"), instStore: instStore, settStore: settStore, agentStore: agentStore, @@ -69,8 +67,6 @@ If no instruction is available the tool will wait up to wait_seconds Args: agent_id: An identifier for this agent instance (used to track connectivity). - default_response_override: Override the server-default empty response text - for this single call. Returns: A dict with keys: status, result_type, instruction, response, @@ -79,10 +75,6 @@ Returns: mcp.Description("Identifier for this agent instance"), mcp.DefaultString("unknown"), ), - mcp.WithString("default_response_override", - mcp.Description("Override the server-default empty response for this call"), - mcp.DefaultString(""), - ), ), h.handleGetUserRequest, ) @@ -92,7 +84,6 @@ Returns: func (h *Handler) handleGetUserRequest(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { agentID := req.GetString("agent_id", "unknown") - defaultOverride := req.GetString("default_response_override", "") // Wait time is hardcoded to stay safely under the 60s client timeout actualWait := defaultWaitSeconds @@ -161,7 +152,7 @@ func (h *Handler) handleGetUserRequest(ctx context.Context, req mcp.CallToolRequ case <-ctx.Done(): // Client disconnected. slog.Debug("get_user_request: context cancelled", "agent", agentID) - return emptyResult(defaultOverride, 0), nil + return emptyResult(0), nil case <-wakeup.Chan(): // Instruction may have arrived — loop back to check. case <-time.After(sleep): @@ -221,7 +212,7 @@ func (h *Handler) handleGetUserRequest(ctx context.Context, req mcp.CallToolRequ } slog.Info("get_user_request: empty", "agent", agentID, "waited", waited, "gen", myGen) - return emptyResult(defaultOverride, waited), nil + return emptyResult(waited), nil } func (h *Handler) deliverInstruction(ctx context.Context, item *models.Instruction, agentID string, waited int) (*mcp.CallToolResult, error) { @@ -230,7 +221,7 @@ func (h *Handler) deliverInstruction(ctx context.Context, item *models.Instructi // Broadcast consumed event + status update. h.broker.Broadcast("instruction.consumed", map[string]any{ - "item": item, + "item": item, "consumed_by_agent_id": agentID, }) h.broker.Broadcast("status.changed", map[string]any{"queue": counts}) @@ -238,8 +229,8 @@ func (h *Handler) deliverInstruction(ctx context.Context, item *models.Instructi slog.Info("get_user_request: delivered", "id", item.ID, "agent", agentID, "waited", waited) result := map[string]any{ - "status": "ok", - "result_type": "instruction", + "status": "ok", + "result_type": "instruction", "instruction": map[string]any{ "id": item.ID, "content": item.Content, @@ -252,12 +243,8 @@ func (h *Handler) deliverInstruction(ctx context.Context, item *models.Instructi return mcp.NewToolResultText(jsonMarshalStr(result)), nil } -func emptyResult(override string, waited int) *mcp.CallToolResult { - resp := override - if resp == "" { - resp = defaultEmptyResponse - } - +func emptyResult(waited int) *mcp.CallToolResult { + resp := config.DefaultEmptyResponse resultType := "empty" if resp != "" { resultType = "default_response" @@ -278,12 +265,3 @@ func jsonMarshalStr(v any) string { b, _ := json.Marshal(v) return string(b) } - - - - - - - - - diff --git a/go-server/internal/store/settings.go b/go-server/internal/store/settings.go index 077525e..9a6bcc0 100644 --- a/go-server/internal/store/settings.go +++ b/go-server/internal/store/settings.go @@ -5,6 +5,7 @@ import ( "fmt" "strconv" + "github.com/local-mcp/local-mcp-go/internal/config" "github.com/local-mcp/local-mcp-go/internal/models" ) @@ -28,6 +29,7 @@ func (s *SettingsStore) Get() (models.Settings, error) { cfg := models.Settings{ DefaultWaitSeconds: 10, + DefaultEmptyResponse: config.DefaultEmptyResponse, AgentStaleAfterSeconds: 30, } @@ -41,8 +43,6 @@ func (s *SettingsStore) Get() (models.Settings, error) { 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 @@ -58,12 +58,9 @@ 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 } -