feat: read the engine's unfulfilled-keys report instead of diffing declared vs delivered - #1308
feat: read the engine's unfulfilled-keys report instead of diffing declared vs delivered#1308ralphstodomingo wants to merge 4 commits into
Conversation
…clared vs delivered The MCP catalog now keeps the `_meta` of a server's last tools/list page per client, exposed as `MCP.listMeta(name)`. On attach, the gaps come from the engine's `ai.altimate/unfulfilled` report, grouped by reason in the toast and headless line with the engine's detail (e.g. `spawn docker ENOENT`); `no-bridge` entries stay out of the missing set as before. The attached outcome carries the full report. `MIN_ENGINE_VERSION` moves to 0.7.2, the first engine that emits it; an engine that sends none claims no gaps rather than inventing them. Closes #1307 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHBUvb843k1R7UAGi8Ya9b
Env-guarded (`ALTIMATE_ENGINE_E2E_ROOT`), skipped otherwise: spawns a built engine over stdio the way the overlay does, against a fake Altimate API and a real second MCP server, and reads the `ai.altimate/unfulfilled` report through `MCP.listMeta` into the attach toast text. The engine is a node shebang script, so the test spawns node rather than the bun test runner. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHBUvb843k1R7UAGi8Ya9b
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Marker Guard flagged the changed lines in the upstream-shared catalog; the single-line marker comments did not count as a wrapped block. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHBUvb843k1R7UAGi8Ya9b
Engine → altimate-code, through the real attach pathThis runs the CLI's production attach code under a real instance with no model turn: the binding cache, the Declared by the workspace: Result — release candidate (head
|
Custom (tenant-created) integrations carry numeric ids; the parser treated the whole report as malformed over that one field and the attach announced no gaps at all. Take the id as a string. Found by the engine-to-CLI run against a local backend with a custom MCP integration. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GHBUvb843k1R7UAGi8Ya9b
|
One more commit on this head, |
Issue for this PR
Closes #1307
Type of change
What does this PR do?
When a workspace is bound, the attach toast says "N of M declared integration tools available" and lists what is declared but absent. Until now that list came from a client-side diff: fetch the workspace's allowlist from the API, subtract the tool names the engine served. A diff can name keys, never reasons — an expired Jira token, an MCP server whose binary is not installed, an integration the tenant removed from the catalog, an extension tool with no VS Code window, and a key the provider does not offer all read the same.
@altimateai/datamate0.7.2 (AltimateAI/altimate-mcp-engine#248) reports every declared-but-unserved key with a reason under_meta["ai.altimate/unfulfilled"]on eachtools/listresponse. This PR reads it:_metaof a server's lasttools/listpage per client.paginatekeeps only each page's items, so the result object — the only carrier of_meta— was dropped. A listing starts with none; any page that carries one sets it; a listing without one clears it. Exposed asMCP.listMeta(name)(undefined while not connected).no-bridgeentries stay out of the "missing" line, as absent extension tools without an IDE were already treated as expected; every other reason is named, grouped, with the engine's detail (e.g.spawn docker ENOENT) —Declared but not available — no usable connection: jira_search_issues; server failed to start (spawn docker ENOENT): gh_list_prs, gh_create_pr.The "N of M" headline still counts declared keys that are present. The attached outcome carries the full report for later surfaces._meta(nothing at or above the floor does) yields an outcome with neithermissingnorunfulfilled, and a toast with no gap line — not "all served".MIN_ENGINE_VERSION), the first engine that emits the report. Do not merge before@altimateai/datamate@0.7.2is on npm (AltimateAI/altimate-mcp-engine#249 is the bump); until then every attach would refuse with "needs 0.7.2 or newer".The client no longer reads what it cannot know: the allowlist lookup (
declared()) is kept only for the headline's denominator and the extension-tool count, and a report without a reachable allowlist still names the gaps.Claims
missingandunfulfilledexist on the outcome only when the engine sent a well-formed report; a malformed or absent_metayields neither (test: "an engine that sends no report is not read as having no gaps";parseUnfulfilledcases).no-bridgenever counts as missing, and every other reason does — includingunknown-keyon an extension key while a bridge is connected (reportedMissing; test "no-bridge entries in the report are expected, never missing").tools/list_changedrefresh, the post-OAuth reconnect all go throughMcpCatalog.defs→listTools, which is the only writer (catalog-list-meta.test.tscovers first page, multi-page, and clearing).key=reason; test "a gap whose reason changed is announced again")._metais retained per client but read only fordatamate; tool conversion and the storeddefsare unchanged.Residuals
MIN_ENGINE_VERSIONcomment.How did you verify your code works?
bun run typecheckclean; prettier clean on the files this PR touches (the files that were already non-conforming onmainare left as they were).test/altimate/workspace(all),test/mcp/catalog-list-meta.test.ts,test/altimate/precedence-guard-order.test.ts: 460 pass.test/mcpand the two session suites whose MCP stubs gainedlistMeta: 279 pass; the 5mcp.headersfailures and 1oauth-auto-connectfailure reproduce identically on an untouchedmaincheckout (environmental, not this change).New tests: 6 attach cases (reasons in the toast, no-bridge exclusion, no-report, report-without-allowlist, reason-change re-announce, the existing inventory case now stating the engine's report),
describeMissing/parseUnfulfilled/reportedMissingunit cases, 3 catalog cases over a real in-memory MCP server.End to end through the real MCP service (
test/mcp/engine-unfulfilled.e2e.test.ts, env-guarded, skipped in CI): the engine atAltimateAI/altimate-mcp-engine#248's head built as 0.7.2 is spawned over stdio byMCP.addexactly as the overlay spawns it, against a fake Altimate API, a real second MCP server and a missing binary;MCP.listMeta("datamate")returns the five-entry report with the expected reasons and the toast text readsDeclared but not available — no usable connection: jira_search_issues; not offered by the integration: ghost; server failed to start (spawn altimate-e2e-missing-binary ENOENT): whatever; no longer in the catalog: retired_tool.— 1 pass. Run it withALTIMATE_ENGINE_E2E_ROOT=<engine checkout with dist/> bun test test/mcp/engine-unfulfilled.e2e.test.tsfrompackages/opencode.Engine → CLI through the real attach path (evidence):
bootstrap+beforeTurnon a bound directory against the 0.7.2 release candidate (engine PRs 250 + 248 merged, built locally, on PATH asdatamate). Settled outcomeattachedwithdeclared: 5,missing: [jira_search_issues, ghost, whatever, retired_tool], the full report incl. theno-bridgeentry, and the exact toast text; 8/8 checks. A 0.7.1 build is refused asengine-too-oldwith the install line; 2/2.Screenshots / recordings
Not a UI change beyond toast text; the exact strings are asserted in the tests above.
Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_01GHBUvb843k1R7UAGi8Ya9b
Appendix — complexity delta (altimate-code: engine unfulfilled report)
e8c21c2af7→93c879af8· only functions this diff touches · advisory, not a gate.✅ No touched function changed in complexity (12 touched, 4 new, all under 10).
ℹ️ How to read these numbers
Cognitive (Sonar spec) counts breaks in linear reading flow — each
if/loop/catch/ternary/boolean-operator switch adds 1, and nesting makes every further break cost more. It approximates how much you must hold in your head to follow the function: 0–5 trivial · 6–10 easy · 11–15 moderate (15 = Sonar's recommended per-function cap) · 16–25 hard to follow · >25 needs decomposition.CCN (cyclomatic) counts independent paths — also the minimum number of test cases for full branch coverage of the function.
Only functions this diff touches are measured, as deltas — pre-existing complexity is not counted against this change. Rising numbers aren't automatically wrong; they're where review attention should go. Test files excluded.
Summary by cubic
Replaces the client-side diff of declared vs delivered tools with the engine's own unfulfilled-keys report, so the attach toast can say why a tool is missing (expired connection, failed spawn, etc.) instead of just naming it. Closes #1307.
MCP catalog
tools/list_metaper client, exposed viaMCP.listMeta(name).Behavior
no-bridgeentries never count as missing; every other reported reason does.MIN_ENGINE_VERSIONmoves to 0.7.2, the first engine that emits the report.Written for commit 8253154. Summary will update on new commits.