Hardcode Go default empty response

This commit is contained in:
Brandon Zhang
2026-03-27 17:55:05 +08:00
parent 372e30ff6f
commit f437f6939c
6 changed files with 26 additions and 59 deletions

View File

@@ -146,15 +146,14 @@ All routes are local-only and intended for `localhost` usage.
**Purpose** **Purpose**
- Return the next pending instruction, if one exists. - 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. - Record agent activity so the UI can infer whether an agent is currently connected/recently active.
**Suggested input schema** **Suggested input schema**
```json ```json
{ {
"agent_id": "optional-string", "agent_id": "optional-string"
"default_response_override": "optional-string"
} }
``` ```
@@ -194,7 +193,7 @@ All routes are local-only and intended for `localhost` usage.
"status": "ok", "status": "ok",
"result_type": "default_response", "result_type": "default_response",
"instruction": null, "instruction": null,
"response": "No new instructions available.", "response": "call this tool `get_user_request` again to fetch latest user input...",
"remaining_pending": 0, "remaining_pending": 0,
"waited_seconds": 10 "waited_seconds": 10
} }
@@ -250,7 +249,7 @@ Returns current server and agent summary.
}, },
"settings": { "settings": {
"default_wait_seconds": 10, "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 "agent_stale_after_seconds": 30
} }
} }
@@ -347,7 +346,7 @@ Returns editable runtime settings.
```json ```json
{ {
"default_wait_seconds": 10, "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 "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 | | `DB_PATH` | `data/local_mcp.sqlite3` | SQLite database path |
| `LOG_LEVEL` | `INFO` | Logging level | | `LOG_LEVEL` | `INFO` | Logging level |
| `DEFAULT_WAIT_SECONDS` | `10` | Default tool wait timeout | | `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 | | `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 | | `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 <token>`; web UI prompts for the token on first load | | `API_TOKEN` | _(empty)_ | When set, all `/api/*` and `/mcp` requests require `Authorization: Bearer <token>`; web UI prompts for the token on first load |

View File

@@ -23,7 +23,6 @@ func handleUpdateConfig(stores Stores, broker *events.Broker) http.HandlerFunc {
// Decode partial patch // Decode partial patch
var patch struct { var patch struct {
DefaultWaitSeconds *int `json:"default_wait_seconds"` DefaultWaitSeconds *int `json:"default_wait_seconds"`
DefaultEmptyResponse *string `json:"default_empty_response"`
AgentStaleAfterSeconds *int `json:"agent_stale_after_seconds"` AgentStaleAfterSeconds *int `json:"agent_stale_after_seconds"`
} }
if err := json.NewDecoder(r.Body).Decode(&patch); err != nil { 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 { if patch.DefaultWaitSeconds != nil {
current.DefaultWaitSeconds = *patch.DefaultWaitSeconds current.DefaultWaitSeconds = *patch.DefaultWaitSeconds
} }
if patch.DefaultEmptyResponse != nil {
current.DefaultEmptyResponse = *patch.DefaultEmptyResponse
}
if patch.AgentStaleAfterSeconds != nil { if patch.AgentStaleAfterSeconds != nil {
current.AgentStaleAfterSeconds = *patch.AgentStaleAfterSeconds current.AgentStaleAfterSeconds = *patch.AgentStaleAfterSeconds
} }

View File

@@ -6,6 +6,8 @@ import (
"strconv" "strconv"
) )
const DefaultEmptyResponse = "call this tool `get_user_request` again to fetch latest user input..."
// Config holds all runtime configuration values for local-mcp. // Config holds all runtime configuration values for local-mcp.
type Config struct { type Config struct {
Host string Host string
@@ -13,7 +15,6 @@ type Config struct {
DBPath string DBPath string
LogLevel string LogLevel string
DefaultWaitSeconds int DefaultWaitSeconds int
DefaultEmptyResponse string
AgentStaleAfterSeconds int AgentStaleAfterSeconds int
MCPStateless bool MCPStateless bool
APIToken string APIToken string
@@ -27,7 +28,6 @@ func Load() Config {
DBPath: getEnv("DB_PATH", "data/local_mcp.sqlite3"), DBPath: getEnv("DB_PATH", "data/local_mcp.sqlite3"),
LogLevel: getEnv("LOG_LEVEL", "INFO"), LogLevel: getEnv("LOG_LEVEL", "INFO"),
DefaultWaitSeconds: getEnvInt("DEFAULT_WAIT_SECONDS", 10), DefaultWaitSeconds: getEnvInt("DEFAULT_WAIT_SECONDS", 10),
DefaultEmptyResponse: getEnv("DEFAULT_EMPTY_RESPONSE", ""),
AgentStaleAfterSeconds: getEnvInt("AGENT_STALE_AFTER_SECONDS", 30), AgentStaleAfterSeconds: getEnvInt("AGENT_STALE_AFTER_SECONDS", 30),
MCPStateless: getEnvBool("MCP_STATELESS", true), MCPStateless: getEnvBool("MCP_STATELESS", true),
APIToken: getEnv("API_TOKEN", ""), APIToken: getEnv("API_TOKEN", ""),
@@ -58,4 +58,3 @@ func getEnvBool(key string, defaultVal bool) bool {
} }
return defaultVal return defaultVal
} }

View File

@@ -40,11 +40,10 @@ CREATE TABLE IF NOT EXISTS agent_activity (
` `
// defaultSettings seeds initial values; OR IGNORE means existing rows are unchanged. // defaultSettings seeds initial values; OR IGNORE means existing rows are unchanged.
const defaultSettings = ` const defaultSettings = "" +
INSERT OR IGNORE INTO settings (key, value) VALUES ('default_wait_seconds', '10'); "INSERT OR IGNORE INTO settings (key, value) VALUES ('default_wait_seconds', '10');\n" +
INSERT OR IGNORE INTO settings (key, value) VALUES ('default_empty_response', ''); "INSERT OR IGNORE INTO settings (key, value) VALUES ('agent_stale_after_seconds', '30');\n" +
INSERT OR IGNORE INTO settings (key, value) VALUES ('agent_stale_after_seconds','30'); "DELETE FROM settings WHERE key = 'default_empty_response';\n"
`
// Open opens (creating if necessary) a SQLite database at dbPath, applies the // Open opens (creating if necessary) a SQLite database at dbPath, applies the
// schema, and seeds default settings. // schema, and seeds default settings.
@@ -74,4 +73,3 @@ func Open(dbPath string) (*sql.DB, error) {
return db, nil return db, nil
} }

View File

@@ -11,6 +11,7 @@ import (
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "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/events"
"github.com/local-mcp/local-mcp-go/internal/models" "github.com/local-mcp/local-mcp-go/internal/models"
"github.com/local-mcp/local-mcp-go/internal/store" "github.com/local-mcp/local-mcp-go/internal/store"
@@ -25,9 +26,6 @@ const (
// multiple keepalive progress updates. // multiple keepalive progress updates.
defaultWaitSeconds = 50 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 // keepaliveInterval controls how often a log notification is sent to the
// client while waiting. Reduced to 5s (from 20s) for more frequent progress updates. // client while waiting. Reduced to 5s (from 20s) for more frequent progress updates.
// This keeps transport-level TCP/HTTP read timeouts from firing. // This keeps transport-level TCP/HTTP read timeouts from firing.
@@ -69,8 +67,6 @@ If no instruction is available the tool will wait up to wait_seconds
Args: Args:
agent_id: An identifier for this agent instance (used to track connectivity). 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: Returns:
A dict with keys: status, result_type, instruction, response, A dict with keys: status, result_type, instruction, response,
@@ -79,10 +75,6 @@ Returns:
mcp.Description("Identifier for this agent instance"), mcp.Description("Identifier for this agent instance"),
mcp.DefaultString("unknown"), mcp.DefaultString("unknown"),
), ),
mcp.WithString("default_response_override",
mcp.Description("Override the server-default empty response for this call"),
mcp.DefaultString(""),
),
), ),
h.handleGetUserRequest, h.handleGetUserRequest,
) )
@@ -92,7 +84,6 @@ Returns:
func (h *Handler) handleGetUserRequest(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func (h *Handler) handleGetUserRequest(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
agentID := req.GetString("agent_id", "unknown") agentID := req.GetString("agent_id", "unknown")
defaultOverride := req.GetString("default_response_override", "")
// Wait time is hardcoded to stay safely under the 60s client timeout // Wait time is hardcoded to stay safely under the 60s client timeout
actualWait := defaultWaitSeconds actualWait := defaultWaitSeconds
@@ -161,7 +152,7 @@ func (h *Handler) handleGetUserRequest(ctx context.Context, req mcp.CallToolRequ
case <-ctx.Done(): case <-ctx.Done():
// Client disconnected. // Client disconnected.
slog.Debug("get_user_request: context cancelled", "agent", agentID) slog.Debug("get_user_request: context cancelled", "agent", agentID)
return emptyResult(defaultOverride, 0), nil return emptyResult(0), nil
case <-wakeup.Chan(): case <-wakeup.Chan():
// Instruction may have arrived — loop back to check. // Instruction may have arrived — loop back to check.
case <-time.After(sleep): 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) 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) { func (h *Handler) deliverInstruction(ctx context.Context, item *models.Instruction, agentID string, waited int) (*mcp.CallToolResult, error) {
@@ -252,12 +243,8 @@ func (h *Handler) deliverInstruction(ctx context.Context, item *models.Instructi
return mcp.NewToolResultText(jsonMarshalStr(result)), nil return mcp.NewToolResultText(jsonMarshalStr(result)), nil
} }
func emptyResult(override string, waited int) *mcp.CallToolResult { func emptyResult(waited int) *mcp.CallToolResult {
resp := override resp := config.DefaultEmptyResponse
if resp == "" {
resp = defaultEmptyResponse
}
resultType := "empty" resultType := "empty"
if resp != "" { if resp != "" {
resultType = "default_response" resultType = "default_response"
@@ -278,12 +265,3 @@ func jsonMarshalStr(v any) string {
b, _ := json.Marshal(v) b, _ := json.Marshal(v)
return string(b) return string(b)
} }

View File

@@ -5,6 +5,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/local-mcp/local-mcp-go/internal/config"
"github.com/local-mcp/local-mcp-go/internal/models" "github.com/local-mcp/local-mcp-go/internal/models"
) )
@@ -28,6 +29,7 @@ func (s *SettingsStore) Get() (models.Settings, error) {
cfg := models.Settings{ cfg := models.Settings{
DefaultWaitSeconds: 10, DefaultWaitSeconds: 10,
DefaultEmptyResponse: config.DefaultEmptyResponse,
AgentStaleAfterSeconds: 30, AgentStaleAfterSeconds: 30,
} }
@@ -41,8 +43,6 @@ func (s *SettingsStore) Get() (models.Settings, error) {
if n, err := strconv.Atoi(value); err == nil { if n, err := strconv.Atoi(value); err == nil {
cfg.DefaultWaitSeconds = n cfg.DefaultWaitSeconds = n
} }
case "default_empty_response":
cfg.DefaultEmptyResponse = value
case "agent_stale_after_seconds": case "agent_stale_after_seconds":
if n, err := strconv.Atoi(value); err == nil { if n, err := strconv.Atoi(value); err == nil {
cfg.AgentStaleAfterSeconds = n cfg.AgentStaleAfterSeconds = n
@@ -58,12 +58,9 @@ func (s *SettingsStore) Update(patch models.Settings) error {
_, err := s.db.Exec(` _, err := s.db.Exec(`
INSERT OR REPLACE INTO settings (key, value) VALUES INSERT OR REPLACE INTO settings (key, value) VALUES
('default_wait_seconds', ?), ('default_wait_seconds', ?),
('default_empty_response', ?),
('agent_stale_after_seconds', ?)`, ('agent_stale_after_seconds', ?)`,
strconv.Itoa(patch.DefaultWaitSeconds), strconv.Itoa(patch.DefaultWaitSeconds),
patch.DefaultEmptyResponse,
strconv.Itoa(patch.AgentStaleAfterSeconds), strconv.Itoa(patch.AgentStaleAfterSeconds),
) )
return err return err
} }