feat(mcp): agent-discoverable disabled tools (spec 049)#476
Merged
Conversation
Follow-up to PR #468. Opt-in include_disabled on retrieve_tools + conditional counts on upstream_servers, 5-state classifier, reactive discovery nudges. Token-cost-zero until exercised. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Converts the brainstormed design (docs/superpowers/specs/2026-05-18-...) into canonical speckit format. Opt-in include_disabled discovery, 5-state status + remediation map, conditional per-server counts, reactive nudges. No enforcement/storage change. Related #468 ## Changes - specs/049-agent-discoverable-disabled-tools/spec.md - specs/049-agent-discoverable-disabled-tools/checklists/requirements.md ## Testing - N/A (spec only); requirements checklist all items pass
Related #468 ## Changes - plan.md (constitution check PASS, no violations) - research.md (decisions + codebase facts; no open unknowns) - data-model.md (5-state status enum, additive response shapes) - contracts/mcp-deltas.md (retrieve_tools + upstream_servers deltas) - quickstart.md (curl + live-MCP verification recipe) - CLAUDE.md active-technologies updated by speckit agent-context ## Testing - N/A (planning docs only)
Related #468 ## Changes - tasks.md: Setup, Foundational (classifier+types), US1 MVP, US2, US3, Polish ## Testing - N/A (task plan only)
Related #468 ## Changes - contracts: DisabledToolStatus consts, LockedToolEntry, ServerToolCounts - runtime: ClassifyDisabledTool (pure, 5-state precedence, read-only) - tests: precedence/unknown/pending table + 1k benchmark (6.6 ns/op) ## Testing - go test ./internal/runtime -run TestClassifyDisabledTool: PASS - BenchmarkClassifyDisabledTool: 6.6 ns/op (<<100ms budget)
Related #468 ## Changes - retrieve_tools: include_disabled param + one-line schema hint - handler: split callable/disabled, agent-scope before classify, cap min(limit,10), once-per-response remediation map - p.classifyDisabledTool (storage-sourced, mirrors runtime classifier) - in-memory include_disabled usage counter (no persistence) ## Testing - TestDisabledDiscovery_{DefaultPathUnchanged,OptIn,CapAtTen,UsageCounter}: PASS - regressions (ExcludesDisabled, CallBlockedTool, BlockedToolMessageFor): PASS
Related #468 ## Changes - blockedToolMessageFor: append include_disabled:true pointer (both branches) - retrieve_tools: when 0 callable + droppedCount>0 + flag off, emit a one-line 'notice' count nudge (no inline entries) ## Testing - TestBlockedToolMessage_DiscoveryPointer, TestDisabledDiscovery_ZeroResultNudge: PASS
Related #468 ## Changes - upstream_servers list/get: emit ServerToolCounts only when a non-callable count > 0 (all-callable servers gain 0 bytes) - classifyServerToolStatus: single storage-sourced truth (runtime- independent); classifyDisabledTool now delegates to it (no drift) ## Testing - TestServerToolCounts_Conditional + full server/runtime suites: PASS
Related #468 ## Changes - quickstart.md: verification-results table (§1-§5 PASS), documents the pre-existing #468 config->runtime disabled_tools gap (out of 049 scope) - tasks.md: all phases checked off ## Testing - runtime/server/contracts unit suites: PASS; verify-oas: PASS - live MCP+curl: default unchanged, include_disabled, counts, blocked msg
Deploying mcpproxy-docs with
|
| Latest commit: |
1f2e07e
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://6025f6d0.mcpproxy-docs.pages.dev |
| Branch Preview URL: | https://049-agent-discoverable-disab.mcpproxy-docs.pages.dev |
|
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
📦 Build ArtifactsWorkflow Run: View Run Available Artifacts
How to DownloadOption 1: GitHub Web UI (easiest)
Option 2: GitHub CLI gh run download 26034750884 --repo smart-mcp-proxy/mcpproxy-go
|
Dumbris
added a commit
that referenced
this pull request
May 18, 2026
…477) The merged #476 classifyServerToolStatus read config-denial only from the storage ServerConfig copy. Config-file disabled_tools on stdio servers live in the live runtime config and are not always mirrored to storage, so disabled_by_config classification + upstream_servers counts were wrong at runtime for that case (unit tests passed because the no-runtime harness uses the storage fallback path). Prefer p.mainServer.runtime.IsToolConfigDenied (same authority isToolCallable/blockedToolMessage use); fall back to storage IsToolAllowedByConfig only when no runtime is wired. Adds a runtime regression test pinning that ClassifyDisabledTool reads live config (the authority the server layer now delegates to). Related #468
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #468. Lets agents discover, on demand, tools that exist but are locked, with machine-branchable status + remediation — at zero token cost on the default path.
Spec:
specs/049-agent-discoverable-disabled-tools/(speckit: spec/plan/tasks/research/data-model/contracts/quickstart).What changed
retrieve_tools: opt-ininclude_disabled(default false). When true, returns adisabled[]list (name/server/description/status) after callable results (cappedmin(limit,10)) + a once-per-responseremediationmap keyed only by present statuses. Default output is byte-for-byte unchanged.ClassifyDisabledTool):server_disabled→disabled_by_config→disabled_by_user→pending_approval→disabled_unknown, fixed precedence; single source of truth shared by both surfaces.upstream_serverslist/get: conditional per-servertoolscount block, emitted only when a non-callable count > 0 (all-callable servers gain 0 bytes).TOOL_BLOCKEDnow appends aninclude_disabled:truepointer; zero-callable-result responses carry a one-line count nudge.isToolCallableuntouched), no new persistent storage, in-memory-only usage counter.Testing
internal/runtimeclassifier (5-state precedence + bench 6.6 ns/op),internal/serverdiscovery (default-unchanged, opt-in, cap, counter, nudge, blocked-message pointer, server counts),internal/contracts. All PASS.verify-oas: PASS (MCP-only shapes; no REST swagger change).quickstart.mdverification table.disabled_toolsload gap for stdio config-file servers is documented in quickstart.md; out of 049 scope.Related #468