repl: warn on stale MCP auto_approve keys (closes #33)

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) <noreply@anthropic.com>
This commit is contained in:
2026-05-16 21:05:08 +00:00
parent 299dcce78f
commit 0c93e31186
+46
View File
@@ -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+)")