feat(mcp): add resource templates and a resources changed hook by erkamyaman · Pull Request #452 · devframes/devframe · GitHub
Skip to content

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

Open
erkamyaman wants to merge 4 commits into
devframes:mainfrom
erkamyaman:feat/mcp-resource-templates
Open

erkamyaman wants to merge 4 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

@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-20 19:19 UTC
🔗 head 61427ef
🤖 automated by pulls.review

{
  "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"
  }
}

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.

Comment thread packages/agentic/src/mcp/build-server.ts Outdated
Comment thread packages/agentic/src/mcp/build-server.ts Outdated
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

Comment thread packages/agentic/src/mcp/build-server.ts Outdated
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

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.

3 participants