You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 56f387d
Browse filesBrowse the repository at this point in the historyBrowse files
feat(mcp): add resource templates and a resources changed hook
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.
Copy file name to clipboardExpand all lines: docs/content/1.guide/18.hub-initiate.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -89,7 +89,7 @@ Registrations are validated fail-fast: one module per type (`DF8108`), an existi
89
89
90
90
The hub's **single Auth** is one gate at the shared transport for every mounted devframe, built-ins, and the MCP route; one handshake (OTP, magic link, or pre-shared token) unlocks the namespace; `auth: false` disables it for localhost.
91
91
92
-
The aggregate MCP route mounts through the `'auto'` default once any mounted devframe (or an agent-flagged hub command) exposes agent tools; `mcp: true` forces it on, `mcp: false` off. It has its own origin gate, independent of this RPC Auth: the mounted route trusts same-machine callers, and `mcp: { authorization }` adds an identity check when the hub is reachable beyond loopback. `mcp: { exposeSharedState }` takes `false` or a `(key) => boolean` filter to choose which shared-state keys agents can read through `devframe_state_read` and `devframe://state/<key>`. `mcp: { instructions }` sets the usage guidance that the route sends in the MCP `initialize` result. The hub exposes one aggregate route over every mounted devframe's tools.
92
+
The aggregate MCP route mounts through the `'auto'` default once any mounted devframe (or an agent-flagged hub command) exposes agent tools; `mcp: true` forces it on, `mcp: false` off. It has its own origin gate, independent of this RPC Auth: the mounted route trusts same-machine callers, and `mcp: { authorization }` adds an identity check when the hub is reachable beyond loopback. `mcp: { exposeSharedState }` takes `false` or a `(key) => boolean` filter to choose which shared-state keys agents can read through `devframe_state_read` and `devframe://state/<key>`. `mcp: { instructions }` sets the usage guidance that the route sends in the MCP `initialize` result. `mcp: { resourceTemplates }` adds resource templates that coding agents list through `resources/templates/list` and read through `resources/read`, and `mcp: { onResourcesChanged }` sends `notifications/resources/list_changed` each time its `notify()` runs. The hub exposes one aggregate route over every mounted devframe's tools.
`createMcpServer` and `createMcpFetchHandler` take the same `instructions` option.
83
83
84
+
### Resource templates
85
+
86
+
`resourceTemplates` on the route options is a list of resource templates that the MCP server serves through `resources/templates/list`. Each template has a `uriTemplate`, a `name`, an optional `title`, `description` and `mimeType`, a `read(params, uri, ctx)` function, and an optional `list(ctx)` function. Both functions can be sync or async. They get the node-side context (`ctx`), so they can read live state: the route options are resolved before `setup()` runs, so a template cannot keep `ctx` from there.
87
+
88
+
```ts
89
+
devframeViteBridge(myDevframe, {
90
+
mcp: {
91
+
resourceTemplates: [{
92
+
uriTemplate: 'my-tool://module/{id}',
93
+
name: 'module',
94
+
mimeType: 'application/json',
95
+
list: ctx=>listModules(ctx).map(id=> ({ uri: `my-tool://module/${encodeURIComponent(id)}`, name: id })),
The `uriTemplate` uses RFC 6570 level 1: plain `{name}` expressions. Each one matches one non-empty URI segment, without `/`, and `read` gets its value percent-decoded, so a value with a `/` comes in as `%2F`. Any other template syntax throws [DF0084](/errors/DF0084) when the route mounts. `read` returns `text`, or `json` that the server sends as JSON text, and an optional `mimeType` (the default is the `mimeType` of the template, then `application/json`).
103
+
104
+
`resources/read` sends a `devframe://resource/<id>` or `devframe://state/<key>` URI to the registered resource or the shared state as before. It tries any other URI against the templates in order, and the first template that matches reads it. A URI that nothing matches gets the usual unknown-resource error, and an error thrown by `read` goes to the MCP client like the error of a registered resource. `resources/list` adds the entries that the `list` of each template returns. With an empty or omitted list, the resources stay as they are and `resources/templates/list` answers method not found. `createMcpServer` and `createMcpFetchHandler` take the same `resourceTemplates` option.
105
+
106
+
### Telling coding agents the resources changed
107
+
108
+
The route sends `notifications/resources/list_changed` when the agent surface changes or a shared-state key is added. For other changes, such as new entries from the `list` of a template, pass `onResourcesChanged`: a function that gets `notify` and the node-side context, and returns an unsubscribe function. Each `notify()` sends `notifications/resources/list_changed`, over HTTP (on the `subscriptions/listen` streams) and over `stdio`. The route calls the unsubscribe function when it closes. `createMcpServer` and `createMcpFetchHandler` take the same `onResourcesChanged` option.
description: 'MCP resource template "{name}" has an invalid `uriTemplate` "{uriTemplate}": {reason}'
4
+
---
5
+
6
+
## Message
7
+
8
+
> MCP resource template "`{name}`" has an invalid `uriTemplate` "`{uriTemplate}`": `{reason}`
9
+
10
+
## Cause
11
+
12
+
An entry in the `resourceTemplates` list of the MCP route options (or of `createMcpServer` / `createMcpFetchHandler`) has a `uriTemplate` that goes beyond RFC 6570 level 1. The MCP server supports plain `{name}` expressions only. A name is made of letters, digits, `_` and `.`, and each name appears once. An operator (`{+path}`, `{?q}`, `{/id}`), a modifier (`{path*}`, `{id:3}`), a list of names (`{a,b}`), an empty expression (`{}`), or a `{` or `}` outside an expression throws this. The check runs once, when the MCP route mounts or `createMcpServer` starts, so no request is served with a broken template.
13
+
14
+
## Example
15
+
16
+
```ts
17
+
initDevframe(myDevframe, {
18
+
mcp: {
19
+
resourceTemplates: [{
20
+
uriTemplate: 'my-tool://file/{+path}', // `+` is a level 2 operator → DF0084
Use a plain `{name}` expression for each value. A value matches one URI segment, so a value with a `/` (such as a file path) comes in percent-encoded (`my-tool://file/src%2Fmain.ts`) and reaches `read` decoded (`src/main.ts`):
-[`packages/agentic/src/mcp/build-server.ts`](https://github.com/devframes/devframe/blob/main/packages/agentic/src/mcp/build-server.ts): `compileResourceTemplates()` throws this for a `uriTemplate` beyond RFC 6570 level 1. `createMcpFetchHandler()` (which `mountMcpHttp()` and the MCP route use) and `createMcpServer()` call it once, before they serve any request.
0 commit comments