Hardcode Go default empty response
This commit is contained in:
@@ -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 |
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user