feat(mcp): add ttlMs + cacheScope to ReadResource contents (SEP-2549) - #201
Conversation
|
Moving to draft pending audit follow-ups: replacing the |
|
The CACHE_HINTS design and TTL values look well-reasoned, and the pass-through behaviour of the current SDK makes this forward-compatible. Two things needed before this can land: parse-endpoint.js and parse.js in the repo root look like debugging scripts from exploring the OpenAPI spec. Please remove them from this branch — they shouldn't be committed. |
…p dead schema key) Two changes from the code-review audit on PR accordproject#201: 1. SDK type augmentation. Replace the implicit reliance on the SDK's loose contents[] typing with an explicit module augmentation in server/types/mcp-augmentation.d.ts that declares ttlMs + cacheScope as optional fields on TextResourceContents and BlobResourceContents. This documents the SEP-2549 (CacheableResult) fields as intentional forward-compatible extensions of the wire shape rather than relying on the type checker not noticing them. See: blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate tsconfig.json already picks up the file via include: ["**/*"]. 2. Drop the dead schema key from CACHE_HINTS. The apap://schema/protocol.cto resource only exists on jay/feat/mcp-concerto-context (PR accordproject#199). On main, no handler emits that URI, so the CACHE_HINTS.schema entry was dead code. The test that pinned it has been retired too. A TODO comment in mcp.ts notes that the key should be re-added (ttlMs: 86_400_000, cacheScope: 'public') in a one-line follow-up once accordproject#199 merges. No behaviour change for the four resource URIs (apap://templates, apap://templates/{id}, apap://agreements, apap://agreements/{id}); ttlMs and cacheScope land on every contents[] entry exactly as before. Tests: 70/70 jest pass (npm test); mcp.test alone 6/6. Typecheck: npx tsc --noEmit -p . clean. Signed-off-by: Jay Guwalani <guwalanijj@gmail.com>
Mirrors the upstream change at accordproject/apap#201. Two items from the code-review audit: 1. SDK type augmentation. Replace four per-callsite `as unknown as Array<{ uri; mimeType; text; ttlMs?; cacheScope? }>` casts in src/handlers/mcp.ts with a single module augmentation in src/types/mcp-augmentation.d.ts that adds optional ttlMs and cacheScope fields to TextResourceContents and BlobResourceContents. The augmentation documents the SEP-2549 (CacheableResult) fields as intentional forward-compatible extensions of the wire shape: blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate tsconfig.json picks up the file via include: ["src/**/*"]. 2. Drop the dead schema key from CACHE_HINTS. The apap://schema/protocol.cto resource only exists on jay/feat/mcp-concerto-context (upstream PR #199). On main, no handler emits that URI, so the CACHE_HINTS.schema entry was dead. A TODO comment in mcp.ts notes that the key should be re-added (ttlMs: 86_400_000, cacheScope: 'public') in a one-line follow-up once #199 merges upstream and the schema resource ports here. No behaviour change for the four resource URIs already emitted; ttlMs and cacheScope land on every contents[] entry exactly as before, just without the cast. Tests: 54/54 vitest pass (npm test). Typecheck: tsc --noEmit clean (npm run typecheck). Signed-off-by: Jay Guwalani <guwalanijj@gmail.com>
|
Pushed
No wire-format change for the four resource URIs already covered; Verification: Marked ready for review. |
…p dead schema key) Two changes from the code-review audit on PR accordproject#201: 1. SDK type augmentation. Replace the implicit reliance on the SDK's loose contents[] typing with an explicit module augmentation in server/types/mcp-augmentation.d.ts that declares ttlMs + cacheScope as optional fields on TextResourceContents and BlobResourceContents. This documents the SEP-2549 (CacheableResult) fields as intentional forward-compatible extensions of the wire shape rather than relying on the type checker not noticing them. See: blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate tsconfig.json already picks up the file via include: ["**/*"]. 2. Drop the dead schema key from CACHE_HINTS. The apap://schema/protocol.cto resource only exists on jay/feat/mcp-concerto-context (PR accordproject#199). On main, no handler emits that URI, so the CACHE_HINTS.schema entry was dead code. The test that pinned it has been retired too. A TODO comment in mcp.ts notes that the key should be re-added (ttlMs: 86_400_000, cacheScope: 'public') in a one-line follow-up once accordproject#199 merges. No behaviour change for the four resource URIs (apap://templates, apap://templates/{id}, apap://agreements, apap://agreements/{id}); ttlMs and cacheScope land on every contents[] entry exactly as before. Tests: 70/70 jest pass (npm test); mcp.test alone 6/6. Typecheck: npx tsc --noEmit -p . clean. Signed-off-by: Jay Guwalani <guwalanijj@gmail.com>
b7e6c8f to
957fe03
Compare
|
Some suggestions:
|
Addresses one of Niall's audit items on accordproject#201. The outer describe 'MCP handler emits cache hints on the wire (smoke)' claimed a resources/read smoke test that mocks global.fetch, but the actual test just re-read two CACHE_HINTS values. One of the assertions (agreementList !== agreementItem by reference) is trivially true for distinct object keys and catches nothing. The regression this block could catch is already covered by the per-key value assertions above (both agreementList and agreementItem are pinned to ttlMs 30_000 and cacheScope 'private'). Removing the block rather than replacing it keeps the file honest about what is actually tested. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Jay Guwalani <guwalanijj@gmail.com>
5835a08 to
9d61150
Compare
9d61150 to
0576ab7
Compare
…549) Forward-looking cache hints for `ReadResourceResult.contents[]` mirroring the SEP-2549 CacheableResult shape in the MCP 2026-07-28 RC. Defaults chosen by mutability of each resource: - template lists are volatile and per-client (60s, private) - single templates are hash-immutable (5min, public) - agreements are short-lived because the row can be triggered/updated (30s, private) - the bundled Concerto schema is immutable per deploy (24h, public) Fields are spread alongside uri/mimeType/text so the SEP wire shape lands unchanged once the SDK accepts them at the top level; the current SDK's pass-through path lets caching proxies see them today as forward- compatible hints. Rebased on top of de87943 (post-accordproject#199 and post-accordproject#202). Previous branch had a TODO deferring the `schema` key until accordproject#199 landed. accordproject#199 is now merged, so `CACHE_HINTS.schema` is added with { ttlMs: 86_400_000, cacheScope: 'public' } and spread into the schema resource callback. TODO removed. Wire-format tests pin per-resource defaults so a value change is a visible-to-every-client change and CI catches it. Module augmentation in server/types/mcp-augmentation.d.ts declares the two fields on TextResourceContents / BlobResourceContents so the spread typechecks without per-callsite casts. Signed-off-by: Jay Guwalani <guwalanijj@gmail.com>
0576ab7 to
5804c1f
Compare
🎉 Thank you for your contribution! 🎉Dear @JayDS22, Your pull request has been successfully merged into the project! We greatly appreciate your efforts and the time you've dedicated to improving our repository. What happens next?
Once again, thank you for being part of our community! Best regards, |
Brings the roadmap current through the first-half deliverables: - Status header now reflects W7 start, first-half PRs (accordproject#184, accordproject#196, accordproject#199, accordproject#200, accordproject#201, accordproject#202) all merged upstream, midterm dispatch published, Thursday sync slot confirmed with Niall. - Workstream table reflects Proposal Core first-half complete, MCP RC migration on track through SEP-2549, Alternatives Evaluation complete with Medium publication. - 12-week schedule marks W4–W6 Done, W7 Active (subscriptions/listen slice scoped in apap-mcp-poc#6), W8 covers upstream port + JSON-RPC error mapping. - Milestones: adds Jul 12 midterm dispatch published and Jul 13 midterm eval submitted. - Open decisions: 2, 3, and 4 all resolved (alternatives shipped, MCP RC transport parallel via accordproject#201, Thursday sync slot confirmed). - Contributions to date: adds accordproject#200, accordproject#201, accordproject#202, accordproject#208, and the peer review posted on accordproject#194. accordproject#196 marked merged; accordproject#197 marked superseded. - Adds Comms deliverables section for the Medium/LinkedIn/Discord surfaces. Signed-off-by: Jay Guwalani <guwalanijj@gmail.com>
Per Niall's Discord feedback on Jul 14 that MCP-native tool retrieval (ACE-Router paper, ACL 2026) "might be a future work element to explore if it is worthwhile" — parking the thread in the roadmap doc so it's discoverable during the W12 handoff writeup rather than lost in Discord scrollback. Captures the peer-reviewed prior art (ACE-Router paper + code), the ecosystem validation (Anthropic Tool Search production numbers, Red Hat ToolScope, MCP community issue #2808), and how it composes with the SEP-2549 cache hints landed in accordproject#201. Signed-off-by: Jay Guwalani <guwalanijj@gmail.com>
… review) Niall flagged that the un-paged `listTemplates(db)` was a regression on the `apap://templates` MCP resource path: `parseQueryParams` used to clamp list reads to ≤100 rows, and the service function returns the whole table on every call. That's a token-budget + scalability regression, and it means the per-resource `ttlMs` / `cacheScope` hints from accordproject#201 now annotate an unbounded payload. Add `{ limit?: number; offset?: number }` opts to `listTemplates` directly on the primitive, matching the ≤100 cap the REST layer already applied. Callers with no opts (the current MCP resource path) get the same effective bound as before. Slice 3 REST unification will pass `limit` / `offset` through from `parseQueryParams` and inherit the bounded primitive rather than having to re-add paging in memory. Also updates the fluent Drizzle mock in templateService.test.ts to handle both `.limit(N)` and `.limit(N).offset(M)` chains, and adds four tests pinning the clamp behavior (limit>100 → 100, limit<1 → 1, offset<0 → 0, no-opts → 100/0). Validation: - npm test: 9/9 suites pass, 126 tests total (4 new) - npx tsc --noEmit clean Signed-off-by: Jay Guwalani <guwalanijj@gmail.com>
Summary
Annotate every ReadResource response with the per-entry caching hints proposed in SEP-2549 ("CacheableResult") so the wire shape matches the MCP 2026-07-28 RC ahead of SDK support:
https://blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate
Per-resource defaults are picked by mutability:
apap://templatesapap://templates/{id}apap://agreementsapap://agreements/{id}apap://schema/protocol.ctoThe fields are emitted alongside
uri/mimeType/text. The SDK'sReadResourceResulttype does not yet declare them, but the SDK's response path is pass-through (no schema-strip), so caching proxies see them today as forward-compatible hints. Once SEP-2549 lands in the SDK the spread becomes a plain field assignment with no behavioural change.Mirrors the POC change at JayDS22/apap-mcp-poc#4.
Test plan
npm test -- --testPathPattern='mcp\\.test'(7/7 pass)npm testfull suite (71/71 pass)npx tsc --noEmit -p .cleanPOST /mcpwithresources/readforapap://agreements, confirmttlMs/cacheScopepresent in JSON-RPC responseNotes
server/handlers/mcp.test.tsis the first jest file formcp.ts; it pins the cache defaults so future edits cannot silently change a per-resource TTL or scope.resources/readpayload gains two optional fields.