feat(config): enabled_tools/disabled_tools per-server allowlist/denylist in mcp_config.json#468
Conversation
…enylist Adds two mutually exclusive fields to ServerConfig that let operators declare tool visibility statically in mcp_config.json rather than having to call the API or CLI after every fresh install. enabled_tools: ["list_issues", "get_issue"] // allowlist — only these visible disabled_tools: ["delete_repo", "force_push"] // denylist — hide these, allow rest Config validation rejects a server that has both fields set. On every applyDifferentialToolUpdate (server connect / tool refresh), applyConfigToolFilter walks the in-memory config, computes the desired enabled/disabled state for each discovered tool, and calls setToolEnabledNoEmit to persist it in BBolt. All existing enforcement paths (isToolCallable, retrieve_tools pre-ranking, call_tool_*) pick up the change automatically with no further modifications. Five unit tests cover: allowlist disables unlisted tools, allowlist re-enables a tool moved back into the list, denylist disables listed tools, no-op when neither field is set, and end-to-end integration through applyDifferentialToolUpdate. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Thanks @nlaurance — clean implementation and genuinely thorough tests. I support the idea of config-based tool filtering — declaring tool visibility in My concern is purely the interaction model with the per-tool enable/disable that recently landed (#463 Web UI, #447, REST/CLI
Definition I'd like us to agree onFor me, config-based filtering should mean hard-off for unwanted tools, and for that to be solid it has to be transparent, predictable, and surfaced in the UI and CLI — never a silent toggle that snaps back. Precisely:
Spec use cases
Implementation-wise this means config should override the stored DB enable state at evaluation time rather than overwriting it: evaluate the config layer in Does this definition work for you? If we're aligned on it, I'm happy to pair on this PR and convert the current behavior to override the DB option after config read along these lines — the config fields + mutual-exclusion validation you've already written are solid and stay as-is. |
Summary
enabled_tools(allowlist) anddisabled_tools(denylist) fields toServerConfigso operators can declare tool visibility statically inmcp_config.jsonwithout needing to call the API or CLI after every fresh installapplyDifferentialToolUpdate(server connect / tool refresh), the newapplyConfigToolFiltersyncs the config declarations into BBoltToolApprovalRecord.Disabledflags, so all existing enforcement paths (isToolCallable,retrieve_toolspre-ranking,call_tool_*, direct mode) pick up the change automaticallyUsage
{ "mcpServers": [ { "name": "github", "url": "https://api.github.com/mcp", "enabled_tools": ["list_issues", "get_issue", "list_repos"] }, { "name": "filesystem", "url": "...", "disabled_tools": ["write_file", "delete_file", "execute_code"] } ] }Test plan
go test ./internal/config/ ./internal/runtime/— 349 tests passTestApplyConfigToolFilter_EnabledTools_DisablesNonListedTools— allowlist hides unlisted toolsTestApplyConfigToolFilter_DisabledTools_DisablesListedTools— denylist hides specified toolsTestApplyConfigToolFilter_NoFilter_NoChanges— no records written when neither field is setTestApplyConfigToolFilter_EnabledTools_ReEnablesTool— tool moved back into allowlist is re-enabledTestApplyDifferentialToolUpdate_RespectsEnabledToolsConfig— end-to-end throughapplyDifferentialToolUpdateTestValidateDetailed/enabled_tools_and_disabled_tools_are_mutually_exclusive— validation rejects both fields set simultaneouslygo build ./...— clean build🤖 Generated with Claude Code