Repository navigation
feat(mcp): add resource templates and a resources changed hook - #452
erkamyaman wants to merge 4 commits into
Conversation
|
@erkamyaman is attempting to deploy a commit to the NuxtLabs Team on Vercel. A member of the Team first needs to authorize it. |
|
👁️🗨️ Review this pull request with grouped, summarized diffs at: raw result💭 analyzed by {
"headSha": "61427efbcc5307381b1f1dd71c4ba4008e1d820b",
"result": {
"overallSummary": "Adds two MCP route options that let a host serve parameterized resources and notify agents of resource-list changes: `mcp.resourceTemplates` (RFC 6570 level-1 URI templates for `resources/templates/list`/`read`/`list`) and `mcp.onResourcesChanged` (a `notify()` hook sending `notifications/resources/list_changed`). The options are threaded through the existing MCP option path (`McpRouteOptions`, `resolveMcpConfig`, `mountMcpRoute`, hub `initHub`, `createMcpFetchHandler`, stdio `createMcpServer`, Next.js host), backed by a new DF0084 validation error, extensive tests, and docs.",
"groups": [
{
"key": "resource-templates-feature",
"label": "Resource templates & hook",
"summary": "Adds `mcp.resourceTemplates` (RFC 6570 level-1 URI templates served through `resources/templates/list`/`resources/read`/`resources/list`) and `mcp.onResourcesChanged` (a host hook that notifies `notifications/resources/list_changed`). The new options and types flow through the same path as other MCP route options: compile/validate in `build-server.ts` (new DF0084 diagnostic), wire into the fetch handler, the devframe route config/mount, the hub's aggregate route, and the Next.js host.",
"category": "api",
"filePaths": [
"packages/agentic/src/mcp/build-server.ts",
"packages/agentic/src/mcp/fetch.ts",
"packages/devframe/src/adapters/_shared.ts",
"packages/devframe/src/adapters/initiate.ts",
"packages/devframe/src/adapters/mcp.ts",
"packages/devframe/src/node/diagnostics.ts",
"packages/devframe/src/types/devframe.ts",
"packages/devframe/src/types/mcp.ts",
"packages/hub/src/node/initiate.ts",
"packages/next/src/host.ts"
],
"fileNotes": [
{
"path": "packages/agentic/src/mcp/build-server.ts",
"text": "Core logic: single-pass, non-backtracking URI template matcher (percent-decoding, no-`/` segment rule), template compilation/validation (DF0084), and the `bridgeListChanged` wiring for the new `onResourcesChanged` hook with unsubscribe-on-close semantics.",
"critical": true
}
]
},
{
"key": "tests",
"label": "Tests",
"summary": "Coverage for the new options: template listing/reading/matching edge cases (percent-decoding, multi-variable segments, ReDoS resistance, `__proto__`), DF0084 for invalid templates, `onResourcesChanged` notify/unsubscribe over in-memory, HTTP and hub transports.",
"category": "tests",
"filePaths": [
"packages/agentic/src/mcp/__tests__/mcp-http.test.ts",
"packages/agentic/src/mcp/__tests__/mcp-server.test.ts",
"packages/hub/src/node/__tests__/initiate.test.ts"
]
},
{
"key": "docs",
"label": "Documentation",
"summary": "Documents `resourceTemplates` and `onResourcesChanged`, and adds the DF0084 error page.",
"category": "docs",
"filePaths": [
"docs/content/1.guide/18.hub-initiate.md",
"docs/content/2.adapters/7.mcp.md",
"docs/content/6.errors/DF0084.md",
"docs/content/6.errors/index.md"
]
},
{
"key": "type-snapshots",
"label": "Type snapshots",
"summary": "Generated public-API type snapshots updated to reflect the new types and options.",
"category": "other",
"filePaths": [
"tests/__snapshots__/tsnapi/@devframes/next/hub.snapshot.d.ts",
"tests/__snapshots__/tsnapi/devframe/index.snapshot.d.ts",
"tests/__snapshots__/tsnapi/devframe/internal.snapshot.d.ts",
"tests/__snapshots__/tsnapi/devframe/types.snapshot.d.ts",
"tests/__snapshots__/tsnapi/devframe/adapters/mcp.snapshot.d.ts"
]
}
],
"schemaVersion": 1,
"source": "llm",
"generatedAt": "2026-10-20T19:19:35.309Z",
"model": "vercel-ai-gateway/anthropic/claude-sonnet-5",
"locale": "en"
}
} |
There was a problem hiding this comment.
🟡 Changes recommended
Template matching can block the process and fails to preserve an accepted parameter name.
2 open findings
What changed in this PR
Adds parameterized MCP resources and resource-list change notifications across devframe, hub, and Next.js integrations.
Changes:
- Adds resource template listing, matching, reading, and DF0084 validation.
- Connects resource-change subscriptions to HTTP and stdio notifications.
- Adds integration tests, public types, API snapshots, and documentation.
| File | Description |
|---|---|
| tests/__snapshots__/tsnapi/devframe/types.snapshot.d.ts | Records new type exports. |
| tests/__snapshots__/tsnapi/devframe/internal.snapshot.d.ts | Records DF0084. |
| tests/__snapshots__/tsnapi/devframe/index.snapshot.d.ts | Records public options and types. |
| tests/__snapshots__/tsnapi/devframe/adapters/mcp.snapshot.d.ts | Records adapter type exports. |
| tests/__snapshots__/tsnapi/@devframes/next/hub.snapshot.d.ts | Records Next.js options. |
| packages/next/src/host.ts | Forwards new MCP options. |
| packages/hub/src/node/initiate.ts | Wires aggregate MCP options. |
| packages/hub/src/node/__tests__/initiate.test.ts | Tests hub integration and cleanup. |
| packages/devframe/src/types/mcp.ts | Defines template and subscription types. |
| packages/devframe/src/types/devframe.ts | Extends MCP route options. |
| packages/devframe/src/node/diagnostics.ts | Defines DF0084. |
| packages/devframe/src/adapters/mcp.ts | Exports new MCP types. |
| packages/devframe/src/adapters/initiate.ts | Forwards route options. |
| packages/devframe/src/adapters/_shared.ts | Preserves options during resolution. |
| packages/devframe/src/adapters/__tests__/initiate.test.ts | Tests route integration. |
| packages/agentic/src/mcp/fetch.ts | Compiles templates and connects notifications. |
| packages/agentic/src/mcp/build-server.ts | Implements templates and subscription lifecycle. |
| packages/agentic/src/mcp/__tests__/mcp-server.test.ts | Tests matching, reads, and notifications. |
| packages/agentic/src/mcp/__tests__/mcp-http.test.ts | Tests HTTP resources and notifications. |
| docs/content/6.errors/index.md | Indexes DF0084. |
| docs/content/6.errors/DF0084.md | Explains template validation errors. |
| docs/content/2.adapters/7.mcp.md | Documents both new options. |
| docs/content/1.guide/18.hub-initiate.md | Documents aggregate MCP support. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
A devframe can pass `mcp.resourceTemplates` to serve resources whose URI holds parameters, such as one page's data, and `mcp.onResourcesChanged` to tell coding agents the resource list changed. An invalid template fails at mount with DF0084. Without them the server is unchanged.
Undefined fields drop out of the JSON result, so the listings pass them as is. The devframe and hub initiate tests that only repeated what the HTTP and in-memory tests already catch are removed.
…alue
A template with several values in one segment, such as `{a}-{b}-{c}`,
compiled to a regular expression with overlapping `([^/]+)` groups, so a
crafted URI could backtrack long enough to block the process. Templates
now match in one pass from left to right with no regular expression: each
value runs to the first place the next text appears. Two expressions
with nothing between them (`{a}{b}`) throw DF0084, since a URI cannot
show where one value ends.
The params object has no prototype, so a variable named `__proto__`
keeps its value.
A value ran to the first place the next literal appeared, even when that
place was inside a `%XX` escape of the value. With `{a}D{b}`, the URI
`a%2DbDc` split at the `D` of `%2D`, so `a` could not be decoded and a
valid URI matched no template. The search now skips places inside an
escape, so `a` reads `a-b` and `b` reads `c`.
be9aa46 to
61427ef
Compare


What
Two new MCP route options.
mcp.resourceTemplatesserves resources whose URI holds parameters, such as one page's data:resources/templates/listreturns the templates andresources/listadds each template'slist()entries.resources/readtries the templates after the registered and shared-state resources, and before the existing unknown-resource error. The first matching template wins. A result istext, orjsonstringified like the other resources.{var}) only. Each variable matches one non-empty segment and is percent-decoded, so%2Fgives a/. Malformed percent-encoding does not match.{}, a stray brace, a repeated name) fails when the route mounts, with DF0084.readorlistthat throws reaches the client the same way a throwing resource read does today.mcp.onResourcesChanged(notify, ctx)lets the host tell coding agents that the resource list changed.notify()sendsnotifications/resources/list_changedover HTTP and stdio. The returned unsubscribe runs when the server closes, and anotify()after that does nothing.list,readand the hook take the devframe context as their last argument. Route options are resolved beforesetup()runs, so this is how they reach live state. In the hub they get the hub context.Without these options the server is unchanged:
resources/templates/liststays method-not-found andresources/listis the same.The options follow the same path as the other MCP route options:
McpRouteOptions,resolveMcpConfig,mountMcpRoute, the hub'sinitHub,createMcpFetchHandler, stdiocreateMcpServerand the Next.js host.New types:
McpResourceTemplate,McpResourceTemplateEntry,McpResourceTemplateContent,McpResourcesChangedHook.Why
Pangular Inspector lets coding agents read one browser tab's data as
pangular://page/{pageId}/<name>, and tells them when a tab connects or goes away. Today it does this through a local patch of the dist. This moves both options into devframe.Tests
mcp-server.test.ts: no change without templates,templates/list, reads with different parameters and several variables, percent-decoding, no match for an extra/, an empty segment or a malformed%, first match wins, registered resources first,list()entries, the context passed in, a throwing read, DF0084 for 8 invalid templates, andonResourcesChangedsendinglist_changedand unsubscribing on close.mcp-http.test.ts: templates over Streamable HTTP, andlist_changedon alistenstream.initiate.test.ts: the options reach the route, and the hook gets the hub context and unsubscribes on close.pnpm lint && pnpm knip && pnpm test && pnpm typecheck && pnpm buildpass.Note: DF0081 to DF0083 are left free because #451 and the prompts PR use them.