Skip to content

feat(mcp): add resource templates and a resources changed hook - #452

Merged
antfu merged 7 commits into
devframes:mainfrom
erkamyaman:feat/mcp-resource-templates
Oct 30, 2026
Merged

antfu merged 7 commits into
devframes:mainfrom
erkamyaman:feat/mcp-resource-templates

Conversation

@erkamyaman

Copy link
Copy Markdown
Contributor

What

Two new MCP route options.

mcp.resourceTemplates serves resources whose URI holds parameters, such as one page's data:

mcp: {
  resourceTemplates: [{
    uriTemplate: 'my-tool://page/{pageId}/tree',
    name: 'my-tool:page:tree',
    mimeType: 'application/json',
    list: ctx => pages(ctx).map(id => ({ uri: `my-tool://page/${id}/tree`, name: `Tree of ${id}` })),
    read: ({ pageId }, uri, ctx) => ({ json: treeOf(ctx, pageId) }),
  }],
}
  • With a non-empty list, resources/templates/list returns the templates and resources/list adds each template's list() entries.
  • resources/read tries the templates after the registered and shared-state resources, and before the existing unknown-resource error. The first matching template wins. A result is text, or json stringified like the other resources.
  • Matching is RFC 6570 level 1 ({var}) only. Each variable matches one non-empty segment and is percent-decoded, so %2F gives a /. Malformed percent-encoding does not match.
  • An invalid template (an operator, a modifier, an empty {}, a stray brace, a repeated name) fails when the route mounts, with DF0084.
  • A read or list that 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() sends notifications/resources/list_changed over HTTP and stdio. The returned unsubscribe runs when the server closes, and a notify() after that does nothing.

list, read and the hook take the devframe context as their last argument. Route options are resolved before setup() 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/list stays method-not-found and resources/list is the same.

The options follow the same path as the other MCP route options: McpRouteOptions, resolveMcpConfig, mountMcpRoute, the hub's initHub, createMcpFetchHandler, stdio createMcpServer and 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, and onResourcesChanged sending list_changed and unsubscribing on close.
  • mcp-http.test.ts: templates over Streamable HTTP, and list_changed on a listen stream.
  • devframe and hub 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 build pass.

Note: DF0081 to DF0083 are left free because #451 and the prompts PR use them.

Copilot AI balanced review requested due to automatic review settings October 16, 2026 05:48
@vercel

vercel Bot commented Oct 16, 2026

Copy link
Copy Markdown

@erkamyaman is attempting to deploy a commit to the NuxtLabs Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

👁️‍🗨️ Review this pull request with grouped, summarized diffs at:
👉 https://pulls.review/gh/devframes/devframe/452?from=github-actions[bot]

raw result

💭 analyzed by vercel-ai-gateway/anthropic/claude-sonnet-5
🕰️ 2026-10-30 22:11 UTC
🔗 head 8e5b23f
🤖 automated by pulls.review

{
  "headSha": "8e5b23f875abd315968872962abead1f0c2317d9",
  "result": {
    "overallSummary": "Adds two new MCP route options — `resourceTemplates` (RFC 6570 level-1 URI templates served through `resources/templates/list`/`resources/read`) and `onResourcesChanged` (a host hook that pushes `notifications/resources/list_changed`) — and threads them through every MCP entry point (route options, hub, Next.js host, stdio, fetch handler), with a new DF0084 diagnostic, tests, docs and generated type snapshots.",
    "groups": [
      {
        "key": "mcp-resource-templates",
        "label": "Resource templates & hook",
        "summary": "Core implementation: compiles and matches `uriTemplate`s without regex/backtracking, serves them from `resources/list`/`resources/templates/list`/`resources/read`, adds the `onResourcesChanged` bridge to `bridgeListChanged`, defines the new types and the DF0084 diagnostic, and wires both options through `resolveMcpConfig`, the devframe/hub route mounting and the Next.js host so every MCP surface (HTTP fetch handler, stdio server, hub aggregate route) exposes them identically.",
        "category": "api",
        "filePaths": [
          "packages/agentic/src/mcp/build-server.ts",
          "packages/agentic/src/mcp/fetch.ts",
          "packages/devframe/src/types/mcp.ts",
          "packages/devframe/src/types/devframe.ts",
          "packages/devframe/src/node/diagnostics.ts",
          "packages/devframe/src/adapters/mcp.ts",
          "packages/devframe/src/adapters/_shared.ts",
          "packages/devframe/src/adapters/initiate.ts",
          "packages/hub/src/node/initiate.ts",
          "packages/next/src/host.ts"
        ],
        "critical": true,
        "fileNotes": [
          {
            "path": "packages/agentic/src/mcp/build-server.ts",
            "text": "`compileResourceTemplates`/`matchTemplate` implement RFC 6570 level-1 matching in one linear pass (no regex engine, no backtracking) so a crafted URI can't cause a pathological-time match; validation of the `uriTemplate` runs once at mount time and throws DF0084 for anything beyond a plain `{name}` expression.",
            "critical": true
          },
          {
            "path": "packages/agentic/src/mcp/fetch.ts",
            "text": "Templates are compiled once per endpoint (not per request) so an invalid template fails fast at mount time, and `onResourcesChanged` is wired into the same `bridgeListChanged` used for tool/resource change notifications."
          }
        ]
      },
      {
        "key": "tests",
        "label": "Tests",
        "summary": "Covers template listing/reading, parameter decoding and matching edge cases (percent-encoding, repeated/empty values, adjacent expressions, __proto__ names, ReDoS-style worst case), precedence over registered/shared-state resources, DF0084 for invalid templates, and `onResourcesChanged` notifying and unsubscribing on close, both in-memory, over Streamable HTTP, and through the hub's aggregate route.",
        "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": "Docs",
        "summary": "Documents the new `resourceTemplates` and `onResourcesChanged` route options and the new 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 `.d.ts` snapshot updates reflecting the new public types and options; mechanical, follows directly from the type changes above.",
        "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-30T22:11:27.062Z",
    "model": "vercel-ai-gateway/anthropic/claude-sonnet-5",
    "locale": "en"
  }
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 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.

Copilot AI balanced review requested due to automatic review settings October 20, 2026 05:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The reported URI-matching correctness and event-loop blocking issues remain unresolved.

2 open issues

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 20, 2026 05:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The reported template-matching correctness and process-blocking risks remain unresolved.

2 open issues

Review effort: Balanced

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Changes recommended

Delimiter matching splits percent-encoded parameters, rejecting valid resource URIs.

1 open issue
2 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 20, 2026 19:17
erkamyaman and others added 4 commits October 20, 2026 19:18
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`.
@erkamyaman
erkamyaman force-pushed the feat/mcp-resource-templates branch from be9aa46 to 61427ef Compare October 20, 2026 19:18

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The additive changes preserve existing behavior, with no unresolved findings in matching, option forwarding, or subscription cleanup.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 20, 2026 19:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

No blocking issues were identified in the implementation, option forwarding, lifecycle handling, or accompanying tests.

No open issues 🎉

Review effort: Balanced

antfubot and others added 3 commits October 30, 2026 21:35
…esource-templates

# Conflicts:
#	docs/content/6.errors/index.md
#	packages/devframe/src/node/diagnostics.ts
#	tests/__snapshots__/tsnapi/devframe/internal.snapshot.d.ts
- Compile a template with one split instead of a matchAll loop and two brace checks
- Drop the stop > end check: the last value's stop <= position check covers it
- Reuse McpResourceTemplateEntry for resources/list and inline the templates/list handler
- Fold the percent-decode and no-match tests into one match() test
- Keep the hub test inside the existing describe instead of moving callMcp

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 30, 2026 22:10
@antfu
antfu merged commit 8482ab9 into devframes:main Oct 30, 2026
4 of 13 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

No active review issues remain for the MCP resource-template and resource-change notification additions.

No open issues 🎉

Review effort: Balanced

@erkamyaman
erkamyaman deleted the feat/mcp-resource-templates branch October 30, 2026 22:14
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.

4 participants