feat(mcp): overlay tools: list_overlays, get_overlay_exposure, configure_overlay - #11
Merged
Merged
Conversation
…rlays Overlay strategies are live on the platform, but an agent could not see them: nothing on this server read the exposure resolver, and list_strategies has no role filter. get_overlay_exposure reads GET /cpz/overlay/exposure on the cpz gateway with the caller's own X-CPZ-Key/X-CPZ-Secret (strategies scope), the same route cpz-py's client.overlays.exposure() uses, with its 30 second budget. The result leads with the verdict (complete, hedge_ratio_status, reason) and, on an incomplete book, a withheld block naming the missing prices and history. The ratio, drift, band flag and suggested order are forced to null whenever complete is false, and an upstream that sends numbers anyway is logged, so a ratio computed from part of a book can never reach a model as a number. list_overlays scans /v1/strategies in full (oldest first, 100 a page, refused past 2000 rather than listed from a partial scan), keeps strategy_role overlay, and reads each page's configuration from the exposure resolver, which is the only API-credential route that carries targets and policy. An unconfigured overlay (409 overlay_not_configured) is a state; any other read failure makes the page an error with every entry attached, because an unread configuration is unknown, not empty. hedge_instruments, rebalance_trigger and notes are named as unavailable. There is no configure_overlay. save_overlay_config is SECURITY INVOKER and raises unless auth.uid() is set; this server only ever holds an API credential. The gateway's rpc/ passthrough calls PostgREST as service_role (auth.uid() is null) and injects a user_id argument the function does not take, and rest-api has no overlay resource. Writing strategy_role alone through PATCH /strategies would create the half-configured overlay the atomic RPC exists to prevent. The write tool waits for a user-scoped route. callRestApi gains api: 'gateway' (CPZ_GATEWAY_BASE_URL, default https://api-ai.cpz-lab.com/cpz), sending the same credential headers. Both tools map to the strategies scope and the strategies category; they are read-only, so compact mode defers them behind search_tools and call_tool. Catalogue 31 -> 33 tools; compact stays at 15. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… route CPZ-Lab/cpzai#1736 closed the platform gap the first commit described: the cpz gateway now serves GET /overlays, GET and PUT /overlays/{id} for an API credential (strategies scope), writing through save_overlay_config_as with the key owner's id. configure_overlay PUTs {role, policy, targets} to /overlays/{id}. It is a destructive, idempotent write, advertised by name in compact mode and never dispatchable through call_tool. Policy and target objects are strict, so a misspelt field is refused instead of silently replaced by a database default. Before any request it refuses: unknown objective or trigger, hedge_ratio outside 0..5, tolerance_band outside (0, 1], beta without a benchmark, no targets, a self-target, the same target twice (broker and account key normalised as the database does), weight outside (0, 10], environment other than paper or live, and policy or targets with role alpha. Only the fields given are sent, so defaults stay the database's. The platform's 400 message comes through verbatim. A 200 whose read-back failed is reported as saved with a note to confirm; a 5xx, a timeout or an unreadable 200 is never reported as a save and carries operation_outcome unknown. The PUT is not retried. list_overlays is now one GET /overlays with no market data: full policy (hedge_instruments, rebalance_trigger and notes included) and targets. The strategies scan, the per-overlay exposure fan-out and the unavailable fields note are gone. A truncated list says it is not the complete list; a 200 without an overlays array or a truncated flag is refused. get_overlay_exposure is unchanged except that its 409 guidance now points at configure_overlay and list_overlays. Catalogue 33 -> 34 tools; compact 15 -> 16. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Summary
Overlay strategies are live on the platform, but an MCP client could not see or configure them. This adds three tools. All three call the cpz gateway (
https://api-ai.cpz-lab.com/cpz) with the caller's ownX-CPZ-Key/X-CPZ-Secretand need thestrategiesscope. OAuth tokens are unsealed to that same pair, and the gateway authenticates and scopes it the same way rest-api does. This server already calls the same host for the Simons proxy.list_overlaysGET /cpz/overlaysget_overlay_exposureGET /cpz/overlay/exposure?strategy_id=<uuid>configure_overlayPUT /cpz/overlays/{uuid}The write route came from CPZ-Lab/cpzai#1736 (
save_overlay_config_as, merged and deployed). The first commit on this branch shipped the two reads only, with the gap written up; the second addsconfigure_overlayand moveslist_overlaysonto the new config route.list_overlays
configuredis true when an overlay has a policy and at least one target.{data, count, truncated}. The platform returns at most 200 overlays, ordered by title. Whentruncatedis true the result adds a note saying it is not the complete list.overlaysarray or no booleantruncatedis refused asinvalid_upstream_response.get_overlay_exposure
client.overlays.exposure().data:{complete, hedge_ratio_status: measured | not_measured | withheld, hedge_ratio_reason, withheld?, data}.complete: falseadds awithheldblock. It lists the missing prices and history and tells the model not to size or place a hedge from the result.completeis false,hedge.current_ratio,drift,outside_bandandsuggested_orderare forced tonull. The platform already withholds them. If a drifted upstream sends numbers anyway, they are removed and the event is logged. A ratio computed from part of a book can never reach a model as a number.not_measuredis distinct fromwithheld. It covers objectives with no ratio (fx, duration, tail, custom, vol_target) and a target book with nothing to hedge. None of these cases is reported as zero.not_an_overlay/overlay_not_configured, 502, and 503market_data_unconfigured. The 409s getguidancepointing atconfigure_overlayandlist_overlays.complete, nohedgeblock or notargetslist is refused asinvalid_upstream_response.configure_overlay
PUT /overlays/{id}with{role, policy, targets}, or{role: "alpha"}alone to remove the configuration.role: "overlay"replaces the whole policy and the whole target list. The description says so, and tells the model to readlist_overlaysfirst. It also says that becoming an overlay turns short selling on.readOnlyHint: false, destructiveHint: true, idempotentHint: true. Compact mode advertises it by name, andcall_toolrefuses it (not_dispatchable).hedge_ration) is refused rather than dropped and silently replaced by the database default. It refuses:hedge_ratiooutside 0 to 5, ortolerance_bandoutside (0, 1];benchmark_symbol;invalid_overlay_configmessage is passed through verbatim inmessageand in the text content. 404not_found_or_not_ownedand 409duplicate_targetcome through as errors.operation_outcome: "unknown", with guidance to check before saving again. The PUT is not retried.savedobject or nooverlaykey is refused asinvalid_upstream_response, also with outcome unknown.overlay: nullandread_back_erroris reported as saved, with a note to confirm it withlist_overlays.Plumbing
callRestApigainsapi: 'gateway', with baseCPZ_GATEWAY_BASE_URL(defaulthttps://api-ai.cpz-lab.com/cpz). It sends the same headers, timeout, retry policy and request id. Retries stay GET-only.TOOL_SCOPES(strategies) and thestrategiescategory.OUTPUT_SCHEMAScovers the two reads, whose envelopes this server builds;configure_overlaydeclares none, like the other writes that proxy to their own handlers.tool-catalog.jsonare updated. The catalogue goes from 31 to 34 tools, and compact from 15 to 16 (it gainsconfigure_overlay). Adatakey still sees 10.Tests
npm test: 225 passed across 10 files. The baseline onorigin/mainwas 163 passed.tests/overlay-tools.test.tshas 61 tests.tests/api-client.test.tsgains one gateway routing test. The pinned counts are updated.npm run buildandtsc --noEmitare clean.Not verified
origin/feat/overlay-config-api(supabase/functions/_shared/overlay-config.ts, the cpz gateway route and migration20260927180000). The coordinator reports cpzai#1736 merged, migrated and deployed.Follow-ups
npm run export:catalog -- --output /path/to/cpzai/src/lib/mcp-tool-catalog.json.CPZ-Lab/cpzai-mcp-server. Local remotes still point at the old name.🤖 Generated with Claude Code