Skip to content

Improve operation discovery and token efficiency#7

Merged
gabrielbauman merged 2 commits into
mainfrom
gbauman/discovery-token-efficiency
Jun 3, 2026
Merged

Improve operation discovery and token efficiency#7
gabrielbauman merged 2 commits into
mainfrom
gbauman/discovery-token-efficiency

Conversation

@gabrielbauman

Copy link
Copy Markdown
Owner

What

Keeps the MCP tool surface constant (still 7 tools) while making registered APIs more discoverable and search/execute cheaper in always-on context. Two themes: token efficiency and capability discovery.

Changes

Token efficiency

  • search emits compact JSON instead of pretty-printed, and accepts an optional limit (default 25, max 50) — ~32% fewer tokens per search on a typical result set.
  • execute's description is assembled from only the API kinds actually registered, so an OpenAPI-only setup no longer carries the GraphQL/SOAP client-shape blurbs in always-on context.

Capability discovery

  • Capture each API's description (OpenAPI info.description; GraphQL Query-root doc, best-effort) through the ProtocolAdapter seam into the registry, surfaced by list_apis so an agent can see what an API is for. SOAP is left best-effort/unset — its name is already the service name.
  • list_apis gains an optional api id that returns just that API plus a capability map: operation tags with per-tag counts, capped at the 40 busiest with a truncation note, so an agent can see an API's areas before searching. Turns thousands of opaque operations into a navigable overview (Cloudflare's 3,068 ops → a ~309-token map).
  • A one-line discovery-strategy hint added to the server instructions.

Interaction with codegen versioning (#6)

description is registration-time metadata derived in prepare() and stored in the registry — exactly like name, which regenerateApi preserves verbatim. It does not change the cached artifacts (types / ops index), so it deliberately does not bump CODEGEN_VERSION (which gates artifact staleness; bumping would re-fetch every spec for a non-artifact change). Existing entries pick up a description on re-registration (add --force), the same way they'd pick up a changed spec title.

Verification

deno task check && lint && fmt && test all pass (including the new openapi-sanitize tests). The ~32% search compaction and the capability-map size were measured against the real cached ops indexes.

Keep the MCP tool surface constant while making registered APIs more
discoverable and search/execute cheaper in always-on context:

- search: emit compact JSON instead of pretty-printed and accept an
  optional limit (default 25, max 50) - ~32% fewer tokens per search.
- execute: assemble its description from only the API kinds actually
  registered, so an OpenAPI-only setup drops the GraphQL/SOAP blurbs.
- Capture each API's description (OpenAPI info.description; GraphQL
  Query-root doc, best-effort) through the adapter seam into the
  registry, surfaced by list_apis.
- list_apis: an optional api id returns that API plus a capability map
  (operation tags -> counts, top 40) so an agent sees an API's areas
  before searching - a navigable overview of thousands of operations.
- Add a one-line discovery-strategy hint to the server instructions.
Copilot AI review requested due to automatic review settings June 3, 2026 07:21

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves MCP capability discovery and reduces token overhead in always-on context by (a) propagating a short API description from protocol adapters into the registry and surfacing it via list_apis, and (b) making search results more compact while tailoring execute’s tool description to only the API kinds present.

Changes:

  • Add optional description metadata to PreparedApi/RegistryEntry, populated from OpenAPI info.description and (best-effort) GraphQL Query-root docs.
  • Enhance list_apis with an optional api filter that returns a per-tag “capability map” (tag counts) for that API.
  • Make search return compact JSON and accept an optional limit (default 25, max 50); generate a slimmer execute tool description based on registered kinds.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/registry.ts Adds optional description field to persisted registry entries.
src/adapter.ts Extends PreparedApi to carry an optional source-derived description.
src/register.ts Persists adapter-provided descriptions into registry entries during registration.
src/openapi.ts Extracts and clamps OpenAPI info.description, returning it from prepare().
src/graphql.ts Captures a best-effort description from the schema’s Query root type.
src/commands/serve.ts Adds search.limit, compact search output, per-kind execute description assembly, and list_apis drill-in capability map.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/commands/serve.ts Outdated
Comment on lines +967 to +971
async ({ api }: ListApisArgs) => {
const state = await loadState();
return {
content: [{ type: "text" as const, text: await listApisResult(state) }],
content: [{
type: "text" as const,
Comment thread src/commands/serve.ts Outdated
}
apis.push(api);
}
return JSON.stringify(apis, null, 2);
Comment thread src/commands/serve.ts
Comment on lines +90 to +95
const tail =
`console.log anything you want back. Chain multiple calls in one execution: intermediate results ` +
`stay in the sandbox instead of round-tripping through you. Set check:false to skip type-checking ` +
`for one run (use when a stale spec enum rejects a value the live API accepts; you lose type ` +
`feedback that run). Set timeoutMs to raise the 30s default (max 120000) for long or paginated ` +
`runs. Input: { api: <registered id>, code: <typescript>, check?: boolean, timeoutMs?: number }.`;
… description

- list_apis returns { text, isError }: an unknown api id is now reported
  with isError set, rather than a bare string that broke the JSON contract.
- list_apis emits compact JSON like search (was pretty-printed), trimming
  per-call tokens, especially with the capability map.
- The execute description hot-reloads: every handler reads the registry via
  loadStateSynced, which re-derives the description (and pushes
  tools/list_changed) whenever the registered kind-set changes, so a
  mid-session add_api/remove_api or external add keeps it accurate.
  Verified end-to-end over stdio.
@gabrielbauman

Copy link
Copy Markdown
Owner Author

Addressed all three review comments in 99da401:

  1. list_apis unknown-api JSON contractlistApisResult now returns { text, isError }; an unknown api id is reported with isError: true (consistent with the other request helpers) instead of a bare string.

  2. list_apis pretty-printed JSON — now emits compact JSON like search, lowering per-call token cost (most noticeable with the capability map).

  3. Stale execute description after hot-reload — fixed properly rather than reverting the per-kind tailoring. Every handler now reads the registry through a loadStateSynced wrapper that re-derives the description and pushes a tools/list_changed (via RegisteredTool.update()) whenever the registered kind-set changes; add_api/remove_api also sync after mutating. So a mid-session add (MCP or external anyapi-mcp add) that introduces a new kind, or a removal that drops the last of a kind, keeps the description accurate.

Verified end-to-end over stdio: with an OpenAPI + GraphQL registry the description lists both; after a mid-session remove_api of the GraphQL API it narrows to OpenAPI-only and a tools/list_changed is emitted. deno task check && lint && fmt && test all green.

@gabrielbauman gabrielbauman merged commit a092b0a into main Jun 3, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants