-
Notifications
You must be signed in to change notification settings - Fork 9
Document MCP custom content resources (mcpResources) and the harper+rest:// descriptor scheme #567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
4240e0a
0af6b44
d175c6f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -108,6 +108,49 @@ class Orders extends Tables.orders { | |
|
|
||
| The corresponding instance method runs through Harper's normal `transactional()` envelope, so per-record `allow*` predicates and audit logging behave the same way as regular verb dispatch. Authentication is "is the user logged in" only — finer-grained gating is the method's responsibility. | ||
|
|
||
| ### Custom `mcpResources` opt-in | ||
|
|
||
| A component author can expose arbitrary content — documentation pages, rendered reports, any `text` or `blob` payload — as MCP resources under author-chosen URIs by declaring a static `mcpResources` array (5.1.18+): | ||
|
|
||
| ```ts | ||
| class DocsPages extends Resource { | ||
| static mcpResources = [ | ||
| { | ||
| uri: 'docs:///index', | ||
| name: 'docs index', | ||
| description: 'List of all documentation pages', | ||
| mimeType: 'text/markdown', | ||
| method: 'readIndex', | ||
| }, | ||
| { | ||
| uriTemplate: 'docs:///{+path}', | ||
| name: 'docs page', | ||
| description: 'A documentation page by path', | ||
| mimeType: 'text/markdown', | ||
| method: 'readPage', | ||
| completions: { path: ['guides/install.md', 'guides/deploy.md'] }, | ||
| }, | ||
| ]; | ||
|
|
||
| async readIndex() { | ||
| return { text: '- docs:///guides/install.md\n…', mimeType: 'text/markdown' }; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Our content style prefers plain ASCII when typography is not meaningful. Could this sample use |
||
| } | ||
|
|
||
| async readPage(params) { | ||
| return { text: loadPage(params.path), mimeType: 'text/markdown' }; | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Each entry declares exactly one of `uri` (fixed — listed by `resources/list`) or `uriTemplate` (listed by `resources/templates/list`). Templates use `{name}` to match a single path segment and `{+name}` to match across segments; `resources/read` extracts the parameters and passes them to the named instance method as its first argument. The method returns a string (text content), `{ text, mimeType? }`, `{ blob, mimeType? }` (base64 binary), or any other object (serialized as JSON). | ||
|
|
||
| Notes: | ||
|
|
||
| - Reads dispatch on the **live** registry class, so an exported `resources.js` subclass's method (and its access control) always wins — the same rule as custom `mcpTools`. Custom resources are served to **any** MCP session, including anonymous, unauthenticated ones (the public-docs case this feature targets) — the MCP layer performs no auth check for them; to restrict one, check `context.user` in the `read` method and throw. | ||
| - `completions` optionally declares candidate values per template parameter, served by `completion/complete`. | ||
| - Custom URIs must use an author-chosen scheme (`docs:///…` above). The reserved schemes — `harper:`, `harper+rest:`, `http:`, `https:` — are rejected at registration so custom entries cannot shadow the built-in surfaces. | ||
| - A read error from the method surfaces to the client as a sanitized JSON-RPC error; the raw error is written to the server log. | ||
|
|
||
| ### `exportTypes` gating | ||
|
|
||
| The MCP surface mirrors the public REST surface. A Resource is filtered out of MCP enumeration entirely when its registration sets `exportTypes.mcp = false`: | ||
|
|
@@ -133,13 +176,13 @@ Both profiles serve `resources/list`, `resources/read`, and `resources/templates | |
|
|
||
| The schema URIs honor each user's `permission[db].tables[table]` walk — a user with no `read` or `describe` perm on a table gets a "permission denied" response from `resources/read`. | ||
|
|
||
| ### `https://` URIs | ||
| ### `harper+rest://` URIs | ||
|
|
||
| The application profile additionally exposes every exported `Resource` (that passes the `exportTypes.mcp` gate **and** the `hasRestVerbs` check) as an `https://<host>:<port>/<path>` URI. These resolve in-process via `Resources.getMatch(path, 'mcp')` — there is no outbound HTTP request. The body returned by `resources/read` is a small descriptor: | ||
| The application profile additionally exposes every exported `Resource` (that passes the `exportTypes.mcp` gate **and** the `hasRestVerbs` check) as a `harper+rest://<host>:<port>/<path>` URI (5.1.18+ — earlier releases listed these under `http(s)://`, which the MCP spec reserves for resources a client can fetch directly from the web; legacy `http(s)://` URIs continue to work for `resources/read` and `resources/subscribe`). These resolve in-process via `Resources.getMatch(path, 'mcp')` — there is no outbound HTTP request. The body returned by `resources/read` is a small descriptor: | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because this is a change to the existing descriptor URI behavior, please add |
||
|
|
||
| ```json | ||
| { | ||
| "uri": "https://node.example.com:9926/Product", | ||
| "uri": "harper+rest://node.example.com:9926/Product", | ||
| "path": "Product", | ||
| "database": "data", | ||
| "table": "product", | ||
|
|
@@ -149,6 +192,10 @@ The application profile additionally exposes every exported `Resource` (that pas | |
|
|
||
| Per-record reads go through the tools surface, where each Resource's `allow{Read,…}` predicates run. The `resources/read` descriptor itself is a fast, side-effect-free hint — not a capability. | ||
|
|
||
| ### Custom content URIs | ||
|
|
||
| Author-declared `mcpResources` entries (see [Custom `mcpResources` opt-in](#custom-mcpresources-opt-in)) appear alongside the built-in surfaces: fixed URIs in `resources/list`, templates in `resources/templates/list`. A registered custom URI always wins over the discovered surfaces on `resources/read`. | ||
|
|
||
| ## `notifications/*/list_changed` | ||
|
|
||
| After the `initialize` handshake, an MCP client opens `GET /mcp` to keep an SSE channel open for server-push frames. Harper subscribes to its existing role-cache and schema-reload event channels and, whenever one fires: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we use the repository's standard availability annotation here? Please add
<VersionBadge version="v5.1.18" />on its own line below the heading and remove(5.1.18+)from this sentence. That keeps this patch-level addition consistent with the neighboring v5.1 feature documentation.