From 0c93e311861427e57593a494a1e1c267130cc3a3 Mon Sep 17 00:00:00 2001 From: Markus Fritsche Date: Sat, 16 May 2026 21:05:08 +0000 Subject: [PATCH] repl: warn on stale MCP auto_approve keys (closes #33) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto-approve policy keys that point at unconnected aliases, mistyped tool names, or malformed forms were silently ignored — leaving the user with surprise confirm prompts and no diagnostic. validate_auto_approve() now walks config.mcp.auto_approve at startup (after the MCP connect loop) and after each :mcp connect. For each key: - "alias__*" — warn if alias has no live session - "alias__tool" — warn if alias unknown OR tool not in registry - anything else — warn as malformed (not in alias__tool form) Non-fatal. The re-run on :mcp connect lets a key that referenced a not-yet-connected alias become live without a restart. Co-Authored-By: Claude Opus 4.7 (1M context) --- repl.lua | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/repl.lua b/repl.lua index 4041171..fa826d0 100644 --- a/repl.lua +++ b/repl.lua @@ -161,6 +161,48 @@ function M.run(config) return true, #sess:list_tools() end + -- Walk config.mcp.auto_approve and warn about keys that match no live + -- tool / no live alias (issue #33). Stale entries silently failed to + -- auto-approve, leaving the user with unexpected confirm prompts. + -- Called at startup AND after :mcp connect so newly-arrived sessions + -- retroactively validate any keys that referenced them. + local function validate_auto_approve() + local policy = config.mcp and config.mcp.auto_approve + if not policy then return end + for key, _ in pairs(policy) do + local alias_glob = key:match("^(.-)__%*$") + if alias_glob then + if not mcp_sessions[alias_glob] then + renderer.status(("auto_approve key '%s': no MCP server " + .. "connected for alias '%s'"):format(key, alias_glob)) + end + else + local alias, tname = key:match("^(.-)__(.+)$") + if not alias or alias == "" or not tname then + renderer.status(("auto_approve key '%s': not in " + .. "'alias__tool' or 'alias__*' form"):format(key)) + else + local sess = mcp_sessions[alias] + if not sess then + renderer.status(("auto_approve key '%s': no MCP " + .. "server connected for alias '%s'") + :format(key, alias)) + else + local found = false + for _, t in ipairs(sess:list_tools()) do + if t.name == tname then found = true; break end + end + if not found then + renderer.status(("auto_approve key '%s': " + .. "alias '%s' has no tool named '%s'") + :format(key, alias, tname)) + end + end + end + end + end + end + if config.mcp and config.mcp.servers then for alias, server_cfg in pairs(config.mcp.servers) do local ok, n = connect_mcp(alias, server_cfg) @@ -168,6 +210,7 @@ function M.run(config) renderer.status(("mcp %s: %d tools"):format(alias, n)) end end + validate_auto_approve() end -- Assemble OpenAI-shape `tools` array across all live sessions, with @@ -827,6 +870,9 @@ function M.run(config) if ok then renderer.status(("mcp %s: connected (%d tools)") :format(alias, n)) + -- Re-validate auto_approve so any stale keys that + -- referenced this alias become live (issue #33 bonus). + validate_auto_approve() end elseif sub == "disconnect" then local alias = sub_args:match("^%s*(%S+)")