Skip to content

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

Open
erkamyaman wants to merge 1 commit into
devframes:mainfrom
erkamyaman:feat/mcp-resource-templates
Open

erkamyaman wants to merge 1 commit 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.

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

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-16 05:49 UTC
🔗 head 4102443
🤖 automated by pulls.review

{
  "headSha": "4102443d76b64be02ccf4c19986c470a05e487b7",
  "result": {
    "overallSummary": "Adds two MCP route options: `resourceTemplates`, which serve parameterized resources (RFC 6570 level-1 URI templates) via `resources/templates/list` and `resources/read`, and `onResourcesChanged`, a host hook that lets callers push `notifications/resources/list_changed`. Both are threaded through the existing MCP option path (`McpRouteOptions`, `resolveMcpConfig`, the devframe and hub route mounting, `createMcpServer`/`createMcpFetchHandler`, and the Next.js host), with a new DF0084 diagnostic for invalid templates, extensive tests, and documentation.",
    "groups": [
      {
        "key": "resource-templates-core",
        "label": "Resource templates core",
        "summary": "Core implementation: compiles RFC 6570 level-1 `uriTemplate`s into matchers (DF0084 on invalid syntax), serves `resources/templates/list` and matches templates in `resources/read` after registered/shared-state resources, adds template `list()` entries to `resources/list`, and extends `bridgeListChanged` to also forward a host `onResourcesChanged` subscription into `notifications/resources/list_changed` over HTTP and stdio.",
        "category": "api",
        "filePaths": [
          "packages/agentic/src/mcp/build-server.ts",
          "packages/agentic/src/mcp/fetch.ts"
        ],
        "critical": true,
        "fileNotes": [
          {
            "path": "packages/agentic/src/mcp/build-server.ts",
            "text": "`compileResourceTemplates` runs once at mount/startup (fail-fast with DF0084); `match()` is pure percent-decoding with try/catch so malformed encoding falls through to the unknown-resource error."
          }
        ]
      },
      {
        "key": "wire-mcp-options",
        "label": "Wire options through hosts",
        "summary": "Plumbs the new `resourceTemplates` and `onResourcesChanged` route options through `McpRouteOptions`, `resolveMcpConfig`, `mountMcpRoute`, the hub's `initHub`, and the Next.js host, adding the new public types and the DF0084 diagnostic.",
        "category": "api",
        "filePaths": [
          "packages/devframe/src/types/devframe.ts",
          "packages/devframe/src/types/mcp.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/hub/src/node/initiate.ts",
          "packages/next/src/host.ts"
        ],
        "fileNotes": [
          {
            "path": "packages/devframe/src/node/diagnostics.ts",
            "text": "Adds the DF0084 diagnostic definition used by `compileResourceTemplates`."
          }
        ]
      },
      {
        "key": "tests",
        "label": "Tests",
        "summary": "Covers the new behavior end to end: template listing/reading, matching rules (percent-decoding, segment boundaries, first-match-wins, registered resources first), context propagation, DF0084 for invalid templates, and `onResourcesChanged` notifications/unsubscription over in-memory, HTTP, devframe and hub routes.",
        "category": "tests",
        "filePaths": [
          "packages/agentic/src/mcp/__tests__/mcp-server.test.ts",
          "packages/agentic/src/mcp/__tests__/mcp-http.test.ts",
          "packages/devframe/src/adapters/__tests__/initiate.test.ts",
          "packages/hub/src/node/__tests__/initiate.test.ts"
        ]
      },
      {
        "key": "docs",
        "label": "Documentation",
        "summary": "Documents the new `resourceTemplates` and `onResourcesChanged` MCP options and the new DF0084 error.",
        "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": "snapshots",
        "label": "Generated type snapshots",
        "summary": "Auto-generated public-API 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-16T05:49:03.095Z",
    "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.

if (names.includes(varname))
throw invalid(`the name "${varname}" is used more than once.`)
names.push(varname)
source += `${escapeRegExp(literal)}([^/]+)`
const found = pattern.exec(uri)
if (!found)
return undefined
const params: Record<string, string> = {}

This branch has not been deployed

No deployments
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