Repository navigation
Progressive tool discovery for the CPZAI MCP server - #9
Merged
Merged
Conversation
31 tools and ~27KB of JSON Schema landed in every client's context on every request, and tool-selection accuracy falls off past roughly thirty tools. Clients that can defer loading (Claude Code, the MCP connector's mcp_toolset defer_loading) were fine; everything else paid in full. POST /mcp/compact advertises 15 tools instead of 31 (12KB vs 27KB, a 55% cut): search_tools finds any deferred read-only tool by name, title, description, parameter name or parameter description and returns its full input schema, and call_tool dispatches it. POST /mcp is unchanged. call_tool takes read-only tools only. A client gates approval on the tool name it can see, so routing an order through a generic dispatcher would hide it from the check meant to catch it: place_order, execute_strategy, create_connection, the webhook writes and every other mutation stay first-class tools in both modes. It also refuses an already-advertised read tool, answers an unknown name with near matches, and validates arguments against the real schema before dispatch. Tools are captured once and replayed, so the advertised catalogue and the dispatchable catalogue cannot drift. New cpzai://guides/discovery resource documents both endpoints, including the defer_loading config for the MCP connector.
Three things the catalogue was not doing. SCOPES. tools/list described the whole server to every credential, so a key scoped to data was still shown place_order and a model would plan around tools that 403 when it reaches them. The spec permits the advertised set to vary by the authorization on the request, since credentials are per-request input rather than connection state. Scopes come from GET /me, cached per credential digest for 60s with a periodic sweep and a size bound, looked up only for tools/list and never on the call path, under a 3s budget of its own so a slow API costs an unfiltered list rather than a stalled client. A data key now sees 10 tools instead of 31; an identity-only token sees get_profile. search_tools will not return an out-of-scope tool and call_tool will not dispatch one. Unknown is not none: if the lookup fails, or the REST API has no scopes field yet, the full catalogue is advertised and the reason is logged. Verified against an unreachable API. Enforcement stays in the REST API. OUTPUT SCHEMAS. 24 tools now declare the envelope they actually return, so upstream drift fails loudly instead of returning a plausible object. Only shapes this server controls are declared: the action tools that proxy elsewhere, place_order and create_connection promise nothing. The SDK skips output validation on isError results, so error envelopes are unaffected. Costs ~4KB on the full endpoint, nothing on a deferred one. SDK 1.30.0 (was 1.27.1). Still protocol 2025-11-25; the 2026-07-28 implementation ships as separate beta packages that are explicitly not production ready, so it stays off the order path until it stabilizes. Adds api-client timeoutMs so a caller can set its own request budget.
chris-cpz
force-pushed
the
feat/mcp-capability-expansion
branch
from
September 15, 2026 08:55
0cf6190 to
3269020
Compare
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.
Three changes, one theme: the tool catalogue should be the one this caller can actually use.
1. Progressive tool discovery
31 tools and ~27KB of JSON Schema landed in every client's context on every request. Clients that defer tool loading themselves (Claude Code, and the MCP connector's
mcp_toolset.default_config.defer_loading) were already fine. Everything else paid in full, and tool-selection accuracy degrades past roughly thirty tools.POST /mcpis unchanged. NewPOST /mcp/compact:search_tools: natural-language or keyword query, optional category, returns each match with its full input and output schema. Searches names, titles, descriptions, parameter names and parameter descriptions, sounsettledorentity_idfinds the tool that takes it.call_tool: invokes a discovered tool and returns its result unchanged.list_accounts,list_positions,list_orders,get_market_data.call_tooltakes read-only tools only. A client gates approval on the tool name it can see, so routing an order through a generic dispatcher would hide it from exactly the check meant to catch it. It also refuses an already-advertised read tool, answers an unknown name with near matches, and validates arguments against the real schema before dispatch.2. Scope-aware
tools/listtools/listdescribed the whole server to every credential. The spec permits the advertised set to vary by the authorization presented, since credentials are per-request input rather than connection state. Scopes come fromGET /me(needs CPZ-Lab/cpzai#1635), cached per credential digest for 60s with a periodic sweep and a size bound, looked up only fortools/list, never on the call path, under a 3s budget of its own so a slow API costs an unfiltered list rather than a stalled client.search_toolswill not return an out-of-scope tool andcall_toolwill not dispatch one.Unknown is not none. If the lookup fails, or the REST API has no
scopesfield yet, the full catalogue is advertised and the reason is logged. Verified against an unreachable API, so the two deploys are order independent. Enforcement stays in the REST API.3. Declared output schemas
24 tools declare the envelope they actually return (
{data, count},{data},{message}), so upstream drift fails loudly instead of returning a plausible-looking object. Only shapes this server controls are declared: the action tools that proxy elsewhere, plusplace_orderandcreate_connection, promise nothing. The SDK skips output validation onisErrorresults, so error envelopes are unaffected.Measured
datascope only/mcp/mcp/compactNot two catalogues
captureToolsruns the existingregisterTools/registerExpandedToolsagainst a recorder, then the permitted subset is replayed onto the server. One definition per tool; advertised, searchable and dispatchable surfaces cannot drift.Also
SDK 1.27.1 to 1.30.0. Still protocol 2025-11-25: the 2026-07-28 implementation ships as separate
@modelcontextprotocol/server@betapackages that are explicitly not production ready with APIs that may still change, so it stays off an order-placing surface until it stabilizes. Its headline change, a stateless core, is already how this server runs.api-clientgains an optionaltimeoutMsso a caller can set its own request budget.Verification
tests/tool-search.test.tsandtests/scopes.test.ts. Includes: dispatch matches a direct call and applies the tool's own argument transforms;place_orderthroughcall_toolis refused and never reaches the API; a data-scoped key gets exactly the 10 expected tools; an identity-only credential getsget_profilealone; scope lookup caches per credential and returns null loudly on failure.tools/listreturns 31 and 15 tools, 24 and 7 with output schemas; unauthenticated POST still returns 401 with theWWW-Authenticatechallenge; with an unreachable REST API both paths still serve the full catalogue and log the reason.tool-catalog.jsonis generated from full mode and is unchanged.New
cpzai://guides/discoveryresource and README sections document both endpoints, thedefer_loadingconfig for the MCP connector, and the scope behaviour.Deploy
Not git-triggered. S3 zip, CodeBuild
aquila-mcp-build, thenaws ecs update-service --cluster aquila-mcp --service aquila-mcp --force-new-deployment.