@ttoss/http-server-mcp 0.27.1 → 0.28.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/dist/index.cjs +164 -4
- package/dist/index.d.cts +213 -4
- package/dist/index.d.mts +213 -4
- package/dist/index.mjs +162 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -478,6 +478,78 @@ register({
|
|
|
478
478
|
|
|
479
479
|
**`buildContext`** injects request-scoped values (DB clients, tenant IDs) into every handler call without threading them through each individual tool. It receives the full `ToolCallContext` so context can vary by identity or by call args.
|
|
480
480
|
|
|
481
|
+
## MCP Apps (interactive UIs)
|
|
482
|
+
|
|
483
|
+
The [`io.modelcontextprotocol/ui`](https://github.com/modelcontextprotocol/ext-apps) extension lets a tool result render as an interactive view instead of text. The view is a `ui://` resource holding an HTML document, which the host loads into a sandboxed iframe and talks to over `postMessage`; the tool points at it through its `_meta`.
|
|
484
|
+
|
|
485
|
+
The extension asks nothing of the transport, so `createMcpRouter` serves it as-is on both protocol revisions. `registerAppResource` owns the parts that are easy to get wrong — the `ui://` scheme, the exact MIME type, `_meta.ui` on both the declaration and the read result, and the deprecated flat linkage key that is still legal until the extension reaches GA.
|
|
486
|
+
|
|
487
|
+
```typescript
|
|
488
|
+
import {
|
|
489
|
+
McpServer,
|
|
490
|
+
registerAppResource,
|
|
491
|
+
UI_EXTENSION_ID,
|
|
492
|
+
UI_RESOURCE_MIME_TYPE,
|
|
493
|
+
z,
|
|
494
|
+
} from '@ttoss/http-server-mcp';
|
|
495
|
+
|
|
496
|
+
const server = new McpServer(
|
|
497
|
+
{ name: 'weather', version: '1.0.0' },
|
|
498
|
+
{
|
|
499
|
+
capabilities: {
|
|
500
|
+
extensions: { [UI_EXTENSION_ID]: { mimeTypes: [UI_RESOURCE_MIME_TYPE] } },
|
|
501
|
+
},
|
|
502
|
+
}
|
|
503
|
+
);
|
|
504
|
+
|
|
505
|
+
const dashboard = registerAppResource({
|
|
506
|
+
server,
|
|
507
|
+
name: 'weather_dashboard',
|
|
508
|
+
uri: 'ui://weather/dashboard',
|
|
509
|
+
description: 'Interactive weather dashboard',
|
|
510
|
+
html: dashboardHtml, // a string, or ({ uri }) => string built per read
|
|
511
|
+
ui: {
|
|
512
|
+
csp: { connectDomains: ['https://api.openweathermap.org'] },
|
|
513
|
+
prefersBorder: true,
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
server.registerTool(
|
|
518
|
+
'get-weather',
|
|
519
|
+
{
|
|
520
|
+
description: 'Get the weather for a location',
|
|
521
|
+
inputSchema: { location: z.string() },
|
|
522
|
+
_meta: dashboard.toolMeta(),
|
|
523
|
+
},
|
|
524
|
+
async ({ location }) => {
|
|
525
|
+
const forecast = await fetchForecast(location);
|
|
526
|
+
return {
|
|
527
|
+
// Text stays meaningful: it is what a host without Apps renders.
|
|
528
|
+
content: [{ type: 'text', text: summarise(forecast) }],
|
|
529
|
+
structuredContent: forecast,
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
);
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
`toolMeta()` is the linkage for **every** registration path — `registerTool`, [`registerToolFromSchema`](#registertoolfromschemaserver-params), and a [`GatedToolDef`](#creategatedtoolregistraroptions) all take `_meta`:
|
|
536
|
+
|
|
537
|
+
```typescript
|
|
538
|
+
registerToolFromSchema(server, {
|
|
539
|
+
name: 'refresh-dashboard',
|
|
540
|
+
description: 'Refresh dashboard data',
|
|
541
|
+
// Callable by the view, hidden from the model.
|
|
542
|
+
_meta: dashboard.toolMeta({ visibility: ['app'] }),
|
|
543
|
+
handler: async () => ({ content: [{ type: 'text', text: 'refreshed' }] }),
|
|
544
|
+
});
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Register the linkage unconditionally
|
|
548
|
+
|
|
549
|
+
The spec suggests checking the client's declared capability before registering UI-enabled tools. Do not: that read is only reliable on the `2026-07-28` revision, where the capability arrives in each request's envelope. In this package's default [stateless mode](#stateless-vs-stateful-mode) there is no remembered `initialize` for a 2025-era client, so a capability-gated registration silently drops the view for every one of them.
|
|
550
|
+
|
|
551
|
+
Declaring it always is also strictly more compatible — a host without Apps support ignores `_meta` and renders the tool's `content`, which is the extension's own graceful-degradation contract. Keep every tool's text result meaningful on its own and the fallback takes care of itself.
|
|
552
|
+
|
|
481
553
|
## Issuing tokens for MCP clients
|
|
482
554
|
|
|
483
555
|
The `auth` option above covers the **resource-server** half of MCP authorization — it verifies tokens issued by an external authorization server (Cognito, Auth0, …). To make your own first-party server _issue_ the tokens an MCP client runs the full OAuth flow against, add the [`@ttoss/http-server-auth`](https://ttoss.dev/docs/modules/packages/http-server-auth) plugin's `oauthServer()` and pair it with `createMcpRouter({ auth: { verifyToken } })` so one deployment both issues and verifies tokens. See the [OAuth Authorization Server](https://ttoss.dev/docs/engineering/guidelines/oauth-authorization-server) guideline.
|
|
@@ -582,6 +654,7 @@ The `GatedToolDef` passed to `register` has:
|
|
|
582
654
|
- `requiredScope` — Single scope that must appear in `identity.scopes`.
|
|
583
655
|
- `inputSchema` — Zod field map or `ZodObject`, forwarded to `server.registerTool`.
|
|
584
656
|
- `gates` (`Array<(ctx: ToolCallContext) => void | Promise<void>>`, optional) — Per-tool guards appended after the global `gates`. Receive the full `ToolCallContext` enabling arg-conditional authorization.
|
|
657
|
+
- `_meta` (`Record<string, unknown>`, optional) — Tool metadata forwarded verbatim on `tools/list`; how a gated tool links to an [MCP Apps](#mcp-apps-interactive-uis) view.
|
|
585
658
|
- `method` — Async handler. Receives merged call args + `buildContext` output.
|
|
586
659
|
|
|
587
660
|
**`ToolCallContext`** is the object passed to gates, `buildContext`, and `onError`:
|
|
@@ -590,6 +663,33 @@ The `GatedToolDef` passed to `register` has:
|
|
|
590
663
|
- `args` (`Record<string, unknown>`) — The validated tool input (post SDK parse).
|
|
591
664
|
- `handler` (`string`) — The tool name, for error attribution.
|
|
592
665
|
|
|
666
|
+
### `registerAppResource(params)`
|
|
667
|
+
|
|
668
|
+
Registers an [MCP Apps](#mcp-apps-interactive-uis) view: a `ui://` resource whose HTML a host renders for a linked tool's result.
|
|
669
|
+
|
|
670
|
+
**Parameters (`params`):**
|
|
671
|
+
|
|
672
|
+
- `server` (`McpServer`) — The MCP server to register the resource on.
|
|
673
|
+
- `name` (`string`) — Resource name, as listed by `resources/list`.
|
|
674
|
+
- `uri` (`string`) — Resource URI. Must use the `ui://` scheme, or the call throws.
|
|
675
|
+
- `description` (`string`, optional) — What the view does and when a host should render it.
|
|
676
|
+
- `html` (`string | ({ uri: URL }) => string | Promise<string>`) — The view's HTML5 document, or a builder invoked per `resources/read`.
|
|
677
|
+
- `ui` (`UiResourceMeta`, optional) — Rendering and security configuration relayed to the host as `_meta.ui`:
|
|
678
|
+
- `csp` (`UiResourceCsp`) — Origins the view may reach, mapped onto the iframe's Content Security Policy: `connectDomains` (`connect-src`), `resourceDomains` (scripts/styles/images/fonts/media), `frameDomains` (`frame-src`), `baseUriDomains` (`base-uri`). An omitted list is the restrictive default, not "no restriction".
|
|
679
|
+
- `permissions` (`UiResourcePermissions`) — Browser capabilities requested for the view (`camera`, `microphone`, `geolocation`, `clipboardWrite`), each as `{}`. A host _may_ grant them, so feature-detect in the view rather than assuming.
|
|
680
|
+
- `domain` (`string`) — Dedicated sandbox origin, for views needing a stable one (OAuth callbacks, CORS, API key allowlists). The format is host-specific.
|
|
681
|
+
- `prefersBorder` (`boolean`) — Whether the host should draw a border and background around the view.
|
|
682
|
+
|
|
683
|
+
**Returns:** `RegisteredAppResource`
|
|
684
|
+
|
|
685
|
+
- `uri` (`string`) — The URI tools link to.
|
|
686
|
+
- `resource` (`RegisteredResource`) — The SDK's registration handle, for `update`/`disable`/`remove`.
|
|
687
|
+
- `toolMeta(params?)` — Builds the `_meta` bag linking a tool to this view, writing both `_meta.ui.resourceUri` and the deprecated flat `_meta['ui/resourceUri']`. Takes `visibility` (`Array<'model' | 'app'>`, optional — the spec's default is both) and `_meta` (`Record<string, unknown>`, optional) to merge further entries in.
|
|
688
|
+
|
|
689
|
+
### `UI_EXTENSION_ID` / `UI_RESOURCE_MIME_TYPE`
|
|
690
|
+
|
|
691
|
+
`'io.modelcontextprotocol/ui'` and `'text/html;profile=mcp-app'`. Use the first as the `capabilities.extensions` key when advertising Apps support on the `McpServer`, and the second as the MIME type a host negotiates.
|
|
692
|
+
|
|
593
693
|
### `registerToolFromSchema(server, params)`
|
|
594
694
|
|
|
595
695
|
Registers a tool using a **plain JSON Schema** object for `inputSchema` instead of a Zod shape.
|
|
@@ -603,6 +703,7 @@ Use this when tool definitions are shared between the MCP server and an AI SDK a
|
|
|
603
703
|
- `params.description` (`string`, optional) — Human-readable description
|
|
604
704
|
- `params.inputSchema` (`JsonObjectSchema`, optional) — Plain JSON Schema object (defaults to `{ type: 'object', properties: {} }`)
|
|
605
705
|
- `params.validateArguments` (`boolean`, optional) — Enforce `inputSchema` on `tools/call` (default: `false`); see [Argument validation](#argument-validation)
|
|
706
|
+
- `params._meta` (`Record<string, unknown>`, optional) — Tool metadata forwarded verbatim on `tools/list`; how a tool links to an [MCP Apps](#mcp-apps-interactive-uis) view
|
|
606
707
|
- `params.handler` (`(args: Record<string, unknown>) => CallToolResult | Promise<CallToolResult>`) — Tool handler receiving the raw request arguments
|
|
607
708
|
|
|
608
709
|
**Returns:** `void`
|
|
@@ -954,3 +1055,4 @@ newest first.
|
|
|
954
1055
|
- [MCP Documentation](https://modelcontextprotocol.io)
|
|
955
1056
|
- [MCP Specification](https://spec.modelcontextprotocol.io/)
|
|
956
1057
|
- [MCP SDK TypeScript](https://github.com/modelcontextprotocol/typescript-sdk)
|
|
1058
|
+
- [MCP Apps extension (`io.modelcontextprotocol/ui`)](https://github.com/modelcontextprotocol/ext-apps)
|
package/dist/index.cjs
CHANGED
|
@@ -120,6 +120,160 @@ var createMcpRequestServer = ({
|
|
|
120
120
|
};
|
|
121
121
|
};
|
|
122
122
|
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/registerAppResource.ts
|
|
125
|
+
/**
|
|
126
|
+
* Identifier of the MCP Apps extension (`io.modelcontextprotocol/ui`), used as
|
|
127
|
+
* the key under `capabilities.extensions` on both sides of the handshake.
|
|
128
|
+
*
|
|
129
|
+
* A client declares Apps support with it; a server may advertise the same key
|
|
130
|
+
* so a host can discover the support without inspecting tool metadata:
|
|
131
|
+
*
|
|
132
|
+
* ```typescript
|
|
133
|
+
* new McpServer(
|
|
134
|
+
* { name: 'weather', version: '1.0.0' },
|
|
135
|
+
* {
|
|
136
|
+
* capabilities: {
|
|
137
|
+
* extensions: {
|
|
138
|
+
* [UI_EXTENSION_ID]: { mimeTypes: [UI_RESOURCE_MIME_TYPE] },
|
|
139
|
+
* },
|
|
140
|
+
* },
|
|
141
|
+
* }
|
|
142
|
+
* );
|
|
143
|
+
* ```
|
|
144
|
+
*/
|
|
145
|
+
var UI_EXTENSION_ID = "io.modelcontextprotocol/ui";
|
|
146
|
+
/** MIME type every MCP Apps HTML resource is served as. */
|
|
147
|
+
var UI_RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
148
|
+
/**
|
|
149
|
+
* Deprecated flat tool metadata key for the linked resource URI. Written
|
|
150
|
+
* alongside `_meta.ui.resourceUri` because both forms are legal until the
|
|
151
|
+
* extension reaches GA, at which point this key is removed from the spec.
|
|
152
|
+
*/
|
|
153
|
+
var LEGACY_RESOURCE_URI_KEY = "ui/resourceUri";
|
|
154
|
+
/**
|
|
155
|
+
* Registers an MCP Apps view: a `ui://` resource a tool result is rendered
|
|
156
|
+
* with, per the `io.modelcontextprotocol/ui` extension.
|
|
157
|
+
*
|
|
158
|
+
* The extension asks nothing of the transport — a view is an ordinary MCP
|
|
159
|
+
* resource, and the tool linkage is ordinary tool `_meta` — so this is
|
|
160
|
+
* registration sugar over `registerResource`, not new protocol surface. What
|
|
161
|
+
* it owns is the part that is easy to get wrong: the `ui://` scheme, the exact
|
|
162
|
+
* MIME type, `_meta.ui` on both the declaration and the read result, and
|
|
163
|
+
* writing the deprecated flat linkage key alongside the current one.
|
|
164
|
+
*
|
|
165
|
+
* **Register the linkage unconditionally.** The extension's negotiation is
|
|
166
|
+
* per-request and only reliably readable on the `2026-07-28` revision; in this
|
|
167
|
+
* package's default stateless 2025-era mode there is no remembered
|
|
168
|
+
* `initialize` to read a client capability from. Gating registration on that
|
|
169
|
+
* capability would therefore silently drop the view for every 2025-era client.
|
|
170
|
+
* A host without Apps support simply ignores `_meta`, so keep each tool's
|
|
171
|
+
* `content` meaningful on its own and let the text result be the fallback.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```typescript
|
|
175
|
+
* import {
|
|
176
|
+
* McpServer,
|
|
177
|
+
* registerAppResource,
|
|
178
|
+
* UI_EXTENSION_ID,
|
|
179
|
+
* UI_RESOURCE_MIME_TYPE,
|
|
180
|
+
* z,
|
|
181
|
+
* } from '@ttoss/http-server-mcp';
|
|
182
|
+
*
|
|
183
|
+
* const server = new McpServer(
|
|
184
|
+
* { name: 'weather', version: '1.0.0' },
|
|
185
|
+
* {
|
|
186
|
+
* capabilities: {
|
|
187
|
+
* extensions: { [UI_EXTENSION_ID]: { mimeTypes: [UI_RESOURCE_MIME_TYPE] } },
|
|
188
|
+
* },
|
|
189
|
+
* }
|
|
190
|
+
* );
|
|
191
|
+
*
|
|
192
|
+
* const dashboard = registerAppResource({
|
|
193
|
+
* server,
|
|
194
|
+
* name: 'weather_dashboard',
|
|
195
|
+
* uri: 'ui://weather/dashboard',
|
|
196
|
+
* description: 'Interactive weather dashboard',
|
|
197
|
+
* html: dashboardHtml,
|
|
198
|
+
* ui: {
|
|
199
|
+
* csp: { connectDomains: ['https://api.openweathermap.org'] },
|
|
200
|
+
* prefersBorder: true,
|
|
201
|
+
* },
|
|
202
|
+
* });
|
|
203
|
+
*
|
|
204
|
+
* server.registerTool(
|
|
205
|
+
* 'get-weather',
|
|
206
|
+
* {
|
|
207
|
+
* description: 'Get the weather for a location',
|
|
208
|
+
* inputSchema: { location: z.string() },
|
|
209
|
+
* _meta: dashboard.toolMeta(),
|
|
210
|
+
* },
|
|
211
|
+
* async ({ location }) => {
|
|
212
|
+
* const forecast = await fetchForecast(location);
|
|
213
|
+
* return {
|
|
214
|
+
* // Text stays meaningful: it is what a host without Apps renders.
|
|
215
|
+
* content: [{ type: 'text', text: summarise(forecast) }],
|
|
216
|
+
* structuredContent: forecast,
|
|
217
|
+
* };
|
|
218
|
+
* }
|
|
219
|
+
* );
|
|
220
|
+
* ```
|
|
221
|
+
*/
|
|
222
|
+
var registerAppResource = ({
|
|
223
|
+
server,
|
|
224
|
+
name,
|
|
225
|
+
uri,
|
|
226
|
+
description,
|
|
227
|
+
html,
|
|
228
|
+
ui
|
|
229
|
+
}) => {
|
|
230
|
+
if (!uri.startsWith("ui://")) throw new Error(`An MCP Apps resource URI must use the ui:// scheme (received "${uri}").`);
|
|
231
|
+
const meta = ui === void 0 ? void 0 : {
|
|
232
|
+
ui
|
|
233
|
+
};
|
|
234
|
+
const resource = server.registerResource(name, uri, {
|
|
235
|
+
description,
|
|
236
|
+
mimeType: UI_RESOURCE_MIME_TYPE,
|
|
237
|
+
...(meta === void 0 ? {} : {
|
|
238
|
+
_meta: meta
|
|
239
|
+
})
|
|
240
|
+
}, async requestedUri => {
|
|
241
|
+
const text = typeof html === "string" ? html : await html({
|
|
242
|
+
uri: requestedUri
|
|
243
|
+
});
|
|
244
|
+
return {
|
|
245
|
+
contents: [{
|
|
246
|
+
uri: requestedUri.href,
|
|
247
|
+
mimeType: UI_RESOURCE_MIME_TYPE,
|
|
248
|
+
text,
|
|
249
|
+
...(meta === void 0 ? {} : {
|
|
250
|
+
_meta: meta
|
|
251
|
+
})
|
|
252
|
+
}]
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
const toolMeta = ({
|
|
256
|
+
visibility,
|
|
257
|
+
_meta
|
|
258
|
+
} = {}) => {
|
|
259
|
+
return {
|
|
260
|
+
..._meta,
|
|
261
|
+
ui: {
|
|
262
|
+
resourceUri: uri,
|
|
263
|
+
...(visibility === void 0 ? {} : {
|
|
264
|
+
visibility
|
|
265
|
+
})
|
|
266
|
+
},
|
|
267
|
+
[LEGACY_RESOURCE_URI_KEY]: uri
|
|
268
|
+
};
|
|
269
|
+
};
|
|
270
|
+
return {
|
|
271
|
+
uri,
|
|
272
|
+
resource,
|
|
273
|
+
toolMeta
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
|
|
123
277
|
//#endregion
|
|
124
278
|
//#region src/registerToolFromSchema.ts
|
|
125
279
|
/**
|
|
@@ -163,7 +317,7 @@ var toStandardSchema = ({
|
|
|
163
317
|
*
|
|
164
318
|
* @param server - The `McpServer` instance to register the tool on.
|
|
165
319
|
* @param params - Tool configuration including name, description, inputSchema,
|
|
166
|
-
* validateArguments, and handler.
|
|
320
|
+
* validateArguments, `_meta`, and handler.
|
|
167
321
|
*
|
|
168
322
|
* @example
|
|
169
323
|
* ```typescript
|
|
@@ -194,14 +348,16 @@ var registerToolFromSchema = (server, params) => {
|
|
|
194
348
|
properties: {}
|
|
195
349
|
},
|
|
196
350
|
validateArguments = false,
|
|
197
|
-
handler
|
|
351
|
+
handler,
|
|
352
|
+
_meta
|
|
198
353
|
} = params;
|
|
199
354
|
server.registerTool(name, {
|
|
200
355
|
description,
|
|
201
356
|
inputSchema: toStandardSchema({
|
|
202
357
|
inputSchema,
|
|
203
358
|
validateArguments
|
|
204
|
-
})
|
|
359
|
+
}),
|
|
360
|
+
_meta
|
|
205
361
|
}, async args => {
|
|
206
362
|
return handler(args);
|
|
207
363
|
});
|
|
@@ -316,7 +472,8 @@ var createGatedToolRegistrar = ({
|
|
|
316
472
|
};
|
|
317
473
|
server.registerTool(def.name, {
|
|
318
474
|
description: def.description,
|
|
319
|
-
inputSchema: def.inputSchema
|
|
475
|
+
inputSchema: def.inputSchema,
|
|
476
|
+
_meta: def._meta
|
|
320
477
|
}, handler);
|
|
321
478
|
};
|
|
322
479
|
return {
|
|
@@ -642,11 +799,14 @@ Object.defineProperty(exports, 'McpServer', {
|
|
|
642
799
|
return _modelcontextprotocol_server.McpServer;
|
|
643
800
|
}
|
|
644
801
|
});
|
|
802
|
+
exports.UI_EXTENSION_ID = UI_EXTENSION_ID;
|
|
803
|
+
exports.UI_RESOURCE_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
|
|
645
804
|
exports.apiCall = apiCall;
|
|
646
805
|
exports.checkScopes = checkScopes;
|
|
647
806
|
exports.createGatedToolRegistrar = createGatedToolRegistrar;
|
|
648
807
|
exports.createMcpRouter = createMcpRouter;
|
|
649
808
|
exports.getIdentity = getIdentity;
|
|
809
|
+
exports.registerAppResource = registerAppResource;
|
|
650
810
|
exports.registerToolFromSchema = registerToolFromSchema;
|
|
651
811
|
Object.defineProperty(exports, 'z', {
|
|
652
812
|
enumerable: true,
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
-
import { CallToolResult, McpServer, McpServer as McpServer$1 } from "@modelcontextprotocol/server";
|
|
3
|
+
import { CallToolResult, McpServer, McpServer as McpServer$1, RegisteredResource } from "@modelcontextprotocol/server";
|
|
4
4
|
import { Router } from "@ttoss/http-server";
|
|
5
5
|
import accepts from "accepts";
|
|
6
6
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -675,6 +675,197 @@ declare namespace Application {
|
|
|
675
675
|
*/
|
|
676
676
|
declare const getIdentity: <T = unknown>() => T | undefined;
|
|
677
677
|
//#endregion
|
|
678
|
+
//#region src/registerAppResource.d.ts
|
|
679
|
+
/**
|
|
680
|
+
* Identifier of the MCP Apps extension (`io.modelcontextprotocol/ui`), used as
|
|
681
|
+
* the key under `capabilities.extensions` on both sides of the handshake.
|
|
682
|
+
*
|
|
683
|
+
* A client declares Apps support with it; a server may advertise the same key
|
|
684
|
+
* so a host can discover the support without inspecting tool metadata:
|
|
685
|
+
*
|
|
686
|
+
* ```typescript
|
|
687
|
+
* new McpServer(
|
|
688
|
+
* { name: 'weather', version: '1.0.0' },
|
|
689
|
+
* {
|
|
690
|
+
* capabilities: {
|
|
691
|
+
* extensions: {
|
|
692
|
+
* [UI_EXTENSION_ID]: { mimeTypes: [UI_RESOURCE_MIME_TYPE] },
|
|
693
|
+
* },
|
|
694
|
+
* },
|
|
695
|
+
* }
|
|
696
|
+
* );
|
|
697
|
+
* ```
|
|
698
|
+
*/
|
|
699
|
+
declare const UI_EXTENSION_ID = "io.modelcontextprotocol/ui";
|
|
700
|
+
/** MIME type every MCP Apps HTML resource is served as. */
|
|
701
|
+
declare const UI_RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
702
|
+
/**
|
|
703
|
+
* Origins an app's view is allowed to reach, mapped by the host onto the
|
|
704
|
+
* iframe's Content Security Policy. Anything not declared is blocked: an
|
|
705
|
+
* omitted list is the restrictive default, not "no restriction".
|
|
706
|
+
*/
|
|
707
|
+
interface UiResourceCsp {
|
|
708
|
+
/** Origins for network requests (`connect-src`). */
|
|
709
|
+
connectDomains?: string[];
|
|
710
|
+
/**
|
|
711
|
+
* Origins for scripts, styles, images, fonts and media (`script-src`,
|
|
712
|
+
* `style-src`, `img-src`, `font-src`, `media-src`).
|
|
713
|
+
*/
|
|
714
|
+
resourceDomains?: string[];
|
|
715
|
+
/** Origins for nested iframes (`frame-src`). */
|
|
716
|
+
frameDomains?: string[];
|
|
717
|
+
/** Allowed document base URIs (`base-uri`). */
|
|
718
|
+
baseUriDomains?: string[];
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Browser capabilities the view asks for. A host *may* grant them, so a view
|
|
722
|
+
* should feature-detect rather than assume: each key is a request, not a
|
|
723
|
+
* guarantee.
|
|
724
|
+
*/
|
|
725
|
+
interface UiResourcePermissions {
|
|
726
|
+
camera?: Record<string, never>;
|
|
727
|
+
microphone?: Record<string, never>;
|
|
728
|
+
geolocation?: Record<string, never>;
|
|
729
|
+
clipboardWrite?: Record<string, never>;
|
|
730
|
+
}
|
|
731
|
+
/** Rendering and security configuration for an app's view (`_meta.ui`). */
|
|
732
|
+
interface UiResourceMeta {
|
|
733
|
+
/** Origins the view may reach; omitted means none. */
|
|
734
|
+
csp?: UiResourceCsp;
|
|
735
|
+
/** Browser capabilities requested for the view. */
|
|
736
|
+
permissions?: UiResourcePermissions;
|
|
737
|
+
/**
|
|
738
|
+
* Dedicated sandbox origin for the view, for cases needing a stable origin
|
|
739
|
+
* (OAuth callbacks, CORS, API key allowlists). The accepted format is
|
|
740
|
+
* host-specific; consult the host's documentation rather than deriving one.
|
|
741
|
+
*/
|
|
742
|
+
domain?: string;
|
|
743
|
+
/** Whether the host should draw a border and background around the view. */
|
|
744
|
+
prefersBorder?: boolean;
|
|
745
|
+
}
|
|
746
|
+
/** Who may reach a tool. Defaults to both when left unset. */
|
|
747
|
+
type UiToolVisibility = 'model' | 'app';
|
|
748
|
+
/** Parameters for {@link registerAppResource}. */
|
|
749
|
+
interface RegisterAppResourceParams {
|
|
750
|
+
/** The `McpServer` instance to register the app resource on. */
|
|
751
|
+
server: McpServer$1;
|
|
752
|
+
/** Resource name, as listed by `resources/list`. */
|
|
753
|
+
name: string;
|
|
754
|
+
/** Resource URI. Must use the `ui://` scheme. */
|
|
755
|
+
uri: string;
|
|
756
|
+
/** What the view does and when a host should render it. */
|
|
757
|
+
description?: string;
|
|
758
|
+
/**
|
|
759
|
+
* The view's HTML5 document, or a builder invoked per `resources/read`.
|
|
760
|
+
* A builder receives the requested URI so one registration can serve
|
|
761
|
+
* variants of a document.
|
|
762
|
+
*/
|
|
763
|
+
html: string | ((args: {
|
|
764
|
+
uri: URL;
|
|
765
|
+
}) => string | Promise<string>);
|
|
766
|
+
/** Rendering and security configuration relayed to the host as `_meta.ui`. */
|
|
767
|
+
ui?: UiResourceMeta;
|
|
768
|
+
}
|
|
769
|
+
/** Parameters for {@link RegisteredAppResource.toolMeta}. */
|
|
770
|
+
interface ToolMetaParams {
|
|
771
|
+
/**
|
|
772
|
+
* Who may reach the tool. Left unset, the spec's default applies — visible
|
|
773
|
+
* to the model *and* callable by the app.
|
|
774
|
+
*/
|
|
775
|
+
visibility?: UiToolVisibility[];
|
|
776
|
+
/** Further `_meta` entries to merge in, e.g. vendor-namespaced keys. */
|
|
777
|
+
_meta?: Record<string, unknown>;
|
|
778
|
+
}
|
|
779
|
+
/** An app resource registered by {@link registerAppResource}. */
|
|
780
|
+
interface RegisteredAppResource {
|
|
781
|
+
/** The URI tools link to. */
|
|
782
|
+
uri: string;
|
|
783
|
+
/** The SDK's registration handle, for `update`/`disable`/`remove`. */
|
|
784
|
+
resource: RegisteredResource;
|
|
785
|
+
/**
|
|
786
|
+
* Builds the `_meta` bag that links a tool to this view. Pass it to
|
|
787
|
+
* `registerTool`, {@link registerToolFromSchema}, or a `GatedToolDef` —
|
|
788
|
+
* whichever registration path the tool uses.
|
|
789
|
+
*/
|
|
790
|
+
toolMeta: (params?: ToolMetaParams) => Record<string, unknown>;
|
|
791
|
+
}
|
|
792
|
+
/**
|
|
793
|
+
* Registers an MCP Apps view: a `ui://` resource a tool result is rendered
|
|
794
|
+
* with, per the `io.modelcontextprotocol/ui` extension.
|
|
795
|
+
*
|
|
796
|
+
* The extension asks nothing of the transport — a view is an ordinary MCP
|
|
797
|
+
* resource, and the tool linkage is ordinary tool `_meta` — so this is
|
|
798
|
+
* registration sugar over `registerResource`, not new protocol surface. What
|
|
799
|
+
* it owns is the part that is easy to get wrong: the `ui://` scheme, the exact
|
|
800
|
+
* MIME type, `_meta.ui` on both the declaration and the read result, and
|
|
801
|
+
* writing the deprecated flat linkage key alongside the current one.
|
|
802
|
+
*
|
|
803
|
+
* **Register the linkage unconditionally.** The extension's negotiation is
|
|
804
|
+
* per-request and only reliably readable on the `2026-07-28` revision; in this
|
|
805
|
+
* package's default stateless 2025-era mode there is no remembered
|
|
806
|
+
* `initialize` to read a client capability from. Gating registration on that
|
|
807
|
+
* capability would therefore silently drop the view for every 2025-era client.
|
|
808
|
+
* A host without Apps support simply ignores `_meta`, so keep each tool's
|
|
809
|
+
* `content` meaningful on its own and let the text result be the fallback.
|
|
810
|
+
*
|
|
811
|
+
* @example
|
|
812
|
+
* ```typescript
|
|
813
|
+
* import {
|
|
814
|
+
* McpServer,
|
|
815
|
+
* registerAppResource,
|
|
816
|
+
* UI_EXTENSION_ID,
|
|
817
|
+
* UI_RESOURCE_MIME_TYPE,
|
|
818
|
+
* z,
|
|
819
|
+
* } from '@ttoss/http-server-mcp';
|
|
820
|
+
*
|
|
821
|
+
* const server = new McpServer(
|
|
822
|
+
* { name: 'weather', version: '1.0.0' },
|
|
823
|
+
* {
|
|
824
|
+
* capabilities: {
|
|
825
|
+
* extensions: { [UI_EXTENSION_ID]: { mimeTypes: [UI_RESOURCE_MIME_TYPE] } },
|
|
826
|
+
* },
|
|
827
|
+
* }
|
|
828
|
+
* );
|
|
829
|
+
*
|
|
830
|
+
* const dashboard = registerAppResource({
|
|
831
|
+
* server,
|
|
832
|
+
* name: 'weather_dashboard',
|
|
833
|
+
* uri: 'ui://weather/dashboard',
|
|
834
|
+
* description: 'Interactive weather dashboard',
|
|
835
|
+
* html: dashboardHtml,
|
|
836
|
+
* ui: {
|
|
837
|
+
* csp: { connectDomains: ['https://api.openweathermap.org'] },
|
|
838
|
+
* prefersBorder: true,
|
|
839
|
+
* },
|
|
840
|
+
* });
|
|
841
|
+
*
|
|
842
|
+
* server.registerTool(
|
|
843
|
+
* 'get-weather',
|
|
844
|
+
* {
|
|
845
|
+
* description: 'Get the weather for a location',
|
|
846
|
+
* inputSchema: { location: z.string() },
|
|
847
|
+
* _meta: dashboard.toolMeta(),
|
|
848
|
+
* },
|
|
849
|
+
* async ({ location }) => {
|
|
850
|
+
* const forecast = await fetchForecast(location);
|
|
851
|
+
* return {
|
|
852
|
+
* // Text stays meaningful: it is what a host without Apps renders.
|
|
853
|
+
* content: [{ type: 'text', text: summarise(forecast) }],
|
|
854
|
+
* structuredContent: forecast,
|
|
855
|
+
* };
|
|
856
|
+
* }
|
|
857
|
+
* );
|
|
858
|
+
* ```
|
|
859
|
+
*/
|
|
860
|
+
declare const registerAppResource: ({
|
|
861
|
+
server,
|
|
862
|
+
name,
|
|
863
|
+
uri,
|
|
864
|
+
description,
|
|
865
|
+
html,
|
|
866
|
+
ui
|
|
867
|
+
}: RegisterAppResourceParams) => RegisteredAppResource;
|
|
868
|
+
//#endregion
|
|
678
869
|
//#region src/registerToolFromSchema.d.ts
|
|
679
870
|
/**
|
|
680
871
|
* A plain JSON Schema object (draft-07 compatible) describing the shape of a
|
|
@@ -721,6 +912,19 @@ interface RegisterToolFromSchemaParams {
|
|
|
721
912
|
* @default false
|
|
722
913
|
*/
|
|
723
914
|
validateArguments?: boolean;
|
|
915
|
+
/**
|
|
916
|
+
* Tool metadata forwarded verbatim on `tools/list`.
|
|
917
|
+
*
|
|
918
|
+
* The MCP Apps extension links a tool to the view its result is rendered
|
|
919
|
+
* with through this field; build the bag with `toolMeta()` from
|
|
920
|
+
* `registerAppResource` rather than writing the keys by hand.
|
|
921
|
+
*
|
|
922
|
+
* @example
|
|
923
|
+
* ```typescript
|
|
924
|
+
* _meta: dashboard.toolMeta({ visibility: ['app'] }),
|
|
925
|
+
* ```
|
|
926
|
+
*/
|
|
927
|
+
_meta?: Record<string, unknown>;
|
|
724
928
|
/**
|
|
725
929
|
* Tool handler invoked when the AI client calls the tool.
|
|
726
930
|
* Receives the request arguments, validated against `inputSchema` only when
|
|
@@ -744,7 +948,7 @@ interface RegisterToolFromSchemaParams {
|
|
|
744
948
|
*
|
|
745
949
|
* @param server - The `McpServer` instance to register the tool on.
|
|
746
950
|
* @param params - Tool configuration including name, description, inputSchema,
|
|
747
|
-
* validateArguments, and handler.
|
|
951
|
+
* validateArguments, `_meta`, and handler.
|
|
748
952
|
*
|
|
749
953
|
* @example
|
|
750
954
|
* ```typescript
|
|
@@ -801,7 +1005,12 @@ type GatedToolDef = {
|
|
|
801
1005
|
* Each gate receives the full {@link ToolCallContext} (identity + args).
|
|
802
1006
|
* Throw to reject the call; return (or resolve) to continue.
|
|
803
1007
|
*/
|
|
804
|
-
gates?: Array<(ctx: ToolCallContext) => void | Promise<void>>;
|
|
1008
|
+
gates?: Array<(ctx: ToolCallContext) => void | Promise<void>>;
|
|
1009
|
+
/**
|
|
1010
|
+
* Tool metadata forwarded verbatim on `tools/list` — how a gated tool links
|
|
1011
|
+
* to an MCP Apps view (`registerAppResource(...).toolMeta()`).
|
|
1012
|
+
*/
|
|
1013
|
+
_meta?: Record<string, unknown>; /** The tool handler. Receives merged call args + `buildContext` output. */
|
|
805
1014
|
method: (args: Record<string, unknown>) => Promise<unknown>;
|
|
806
1015
|
};
|
|
807
1016
|
/** Options for {@link createGatedToolRegistrar}. */
|
|
@@ -1210,4 +1419,4 @@ interface McpRouterOptions {
|
|
|
1210
1419
|
*/
|
|
1211
1420
|
declare const createMcpRouter: (server: McpServer$1, options?: McpRouterOptions) => Router<Application.DefaultState, Application.DefaultContext>;
|
|
1212
1421
|
//#endregion
|
|
1213
|
-
export { ApiCallOptions, CognitoUserPoolConfig, type CreateGatedToolRegistrarOptions, type GatedToolDef, type JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, type RegisterToolFromSchemaParams, type ToolCallContext, type ToolIdentity, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerToolFromSchema, z };
|
|
1422
|
+
export { ApiCallOptions, CognitoUserPoolConfig, type CreateGatedToolRegistrarOptions, type GatedToolDef, type JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, type RegisterAppResourceParams, type RegisterToolFromSchemaParams, type RegisteredAppResource, type ToolCallContext, type ToolIdentity, type ToolMetaParams, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions, type UiToolVisibility, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerAppResource, registerToolFromSchema, z };
|
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { Router } from "@ttoss/http-server";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
-
import { CallToolResult, McpServer, McpServer as McpServer$1 } from "@modelcontextprotocol/server";
|
|
5
|
+
import { CallToolResult, McpServer, McpServer as McpServer$1, RegisteredResource } from "@modelcontextprotocol/server";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import accepts from "accepts";
|
|
8
8
|
import { AsyncLocalStorage as AsyncLocalStorage$1 } from "async_hooks";
|
|
@@ -676,6 +676,197 @@ declare namespace Application {
|
|
|
676
676
|
*/
|
|
677
677
|
declare const getIdentity: <T = unknown>() => T | undefined;
|
|
678
678
|
//#endregion
|
|
679
|
+
//#region src/registerAppResource.d.ts
|
|
680
|
+
/**
|
|
681
|
+
* Identifier of the MCP Apps extension (`io.modelcontextprotocol/ui`), used as
|
|
682
|
+
* the key under `capabilities.extensions` on both sides of the handshake.
|
|
683
|
+
*
|
|
684
|
+
* A client declares Apps support with it; a server may advertise the same key
|
|
685
|
+
* so a host can discover the support without inspecting tool metadata:
|
|
686
|
+
*
|
|
687
|
+
* ```typescript
|
|
688
|
+
* new McpServer(
|
|
689
|
+
* { name: 'weather', version: '1.0.0' },
|
|
690
|
+
* {
|
|
691
|
+
* capabilities: {
|
|
692
|
+
* extensions: {
|
|
693
|
+
* [UI_EXTENSION_ID]: { mimeTypes: [UI_RESOURCE_MIME_TYPE] },
|
|
694
|
+
* },
|
|
695
|
+
* },
|
|
696
|
+
* }
|
|
697
|
+
* );
|
|
698
|
+
* ```
|
|
699
|
+
*/
|
|
700
|
+
declare const UI_EXTENSION_ID = "io.modelcontextprotocol/ui";
|
|
701
|
+
/** MIME type every MCP Apps HTML resource is served as. */
|
|
702
|
+
declare const UI_RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
703
|
+
/**
|
|
704
|
+
* Origins an app's view is allowed to reach, mapped by the host onto the
|
|
705
|
+
* iframe's Content Security Policy. Anything not declared is blocked: an
|
|
706
|
+
* omitted list is the restrictive default, not "no restriction".
|
|
707
|
+
*/
|
|
708
|
+
interface UiResourceCsp {
|
|
709
|
+
/** Origins for network requests (`connect-src`). */
|
|
710
|
+
connectDomains?: string[];
|
|
711
|
+
/**
|
|
712
|
+
* Origins for scripts, styles, images, fonts and media (`script-src`,
|
|
713
|
+
* `style-src`, `img-src`, `font-src`, `media-src`).
|
|
714
|
+
*/
|
|
715
|
+
resourceDomains?: string[];
|
|
716
|
+
/** Origins for nested iframes (`frame-src`). */
|
|
717
|
+
frameDomains?: string[];
|
|
718
|
+
/** Allowed document base URIs (`base-uri`). */
|
|
719
|
+
baseUriDomains?: string[];
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Browser capabilities the view asks for. A host *may* grant them, so a view
|
|
723
|
+
* should feature-detect rather than assume: each key is a request, not a
|
|
724
|
+
* guarantee.
|
|
725
|
+
*/
|
|
726
|
+
interface UiResourcePermissions {
|
|
727
|
+
camera?: Record<string, never>;
|
|
728
|
+
microphone?: Record<string, never>;
|
|
729
|
+
geolocation?: Record<string, never>;
|
|
730
|
+
clipboardWrite?: Record<string, never>;
|
|
731
|
+
}
|
|
732
|
+
/** Rendering and security configuration for an app's view (`_meta.ui`). */
|
|
733
|
+
interface UiResourceMeta {
|
|
734
|
+
/** Origins the view may reach; omitted means none. */
|
|
735
|
+
csp?: UiResourceCsp;
|
|
736
|
+
/** Browser capabilities requested for the view. */
|
|
737
|
+
permissions?: UiResourcePermissions;
|
|
738
|
+
/**
|
|
739
|
+
* Dedicated sandbox origin for the view, for cases needing a stable origin
|
|
740
|
+
* (OAuth callbacks, CORS, API key allowlists). The accepted format is
|
|
741
|
+
* host-specific; consult the host's documentation rather than deriving one.
|
|
742
|
+
*/
|
|
743
|
+
domain?: string;
|
|
744
|
+
/** Whether the host should draw a border and background around the view. */
|
|
745
|
+
prefersBorder?: boolean;
|
|
746
|
+
}
|
|
747
|
+
/** Who may reach a tool. Defaults to both when left unset. */
|
|
748
|
+
type UiToolVisibility = 'model' | 'app';
|
|
749
|
+
/** Parameters for {@link registerAppResource}. */
|
|
750
|
+
interface RegisterAppResourceParams {
|
|
751
|
+
/** The `McpServer` instance to register the app resource on. */
|
|
752
|
+
server: McpServer$1;
|
|
753
|
+
/** Resource name, as listed by `resources/list`. */
|
|
754
|
+
name: string;
|
|
755
|
+
/** Resource URI. Must use the `ui://` scheme. */
|
|
756
|
+
uri: string;
|
|
757
|
+
/** What the view does and when a host should render it. */
|
|
758
|
+
description?: string;
|
|
759
|
+
/**
|
|
760
|
+
* The view's HTML5 document, or a builder invoked per `resources/read`.
|
|
761
|
+
* A builder receives the requested URI so one registration can serve
|
|
762
|
+
* variants of a document.
|
|
763
|
+
*/
|
|
764
|
+
html: string | ((args: {
|
|
765
|
+
uri: URL;
|
|
766
|
+
}) => string | Promise<string>);
|
|
767
|
+
/** Rendering and security configuration relayed to the host as `_meta.ui`. */
|
|
768
|
+
ui?: UiResourceMeta;
|
|
769
|
+
}
|
|
770
|
+
/** Parameters for {@link RegisteredAppResource.toolMeta}. */
|
|
771
|
+
interface ToolMetaParams {
|
|
772
|
+
/**
|
|
773
|
+
* Who may reach the tool. Left unset, the spec's default applies — visible
|
|
774
|
+
* to the model *and* callable by the app.
|
|
775
|
+
*/
|
|
776
|
+
visibility?: UiToolVisibility[];
|
|
777
|
+
/** Further `_meta` entries to merge in, e.g. vendor-namespaced keys. */
|
|
778
|
+
_meta?: Record<string, unknown>;
|
|
779
|
+
}
|
|
780
|
+
/** An app resource registered by {@link registerAppResource}. */
|
|
781
|
+
interface RegisteredAppResource {
|
|
782
|
+
/** The URI tools link to. */
|
|
783
|
+
uri: string;
|
|
784
|
+
/** The SDK's registration handle, for `update`/`disable`/`remove`. */
|
|
785
|
+
resource: RegisteredResource;
|
|
786
|
+
/**
|
|
787
|
+
* Builds the `_meta` bag that links a tool to this view. Pass it to
|
|
788
|
+
* `registerTool`, {@link registerToolFromSchema}, or a `GatedToolDef` —
|
|
789
|
+
* whichever registration path the tool uses.
|
|
790
|
+
*/
|
|
791
|
+
toolMeta: (params?: ToolMetaParams) => Record<string, unknown>;
|
|
792
|
+
}
|
|
793
|
+
/**
|
|
794
|
+
* Registers an MCP Apps view: a `ui://` resource a tool result is rendered
|
|
795
|
+
* with, per the `io.modelcontextprotocol/ui` extension.
|
|
796
|
+
*
|
|
797
|
+
* The extension asks nothing of the transport — a view is an ordinary MCP
|
|
798
|
+
* resource, and the tool linkage is ordinary tool `_meta` — so this is
|
|
799
|
+
* registration sugar over `registerResource`, not new protocol surface. What
|
|
800
|
+
* it owns is the part that is easy to get wrong: the `ui://` scheme, the exact
|
|
801
|
+
* MIME type, `_meta.ui` on both the declaration and the read result, and
|
|
802
|
+
* writing the deprecated flat linkage key alongside the current one.
|
|
803
|
+
*
|
|
804
|
+
* **Register the linkage unconditionally.** The extension's negotiation is
|
|
805
|
+
* per-request and only reliably readable on the `2026-07-28` revision; in this
|
|
806
|
+
* package's default stateless 2025-era mode there is no remembered
|
|
807
|
+
* `initialize` to read a client capability from. Gating registration on that
|
|
808
|
+
* capability would therefore silently drop the view for every 2025-era client.
|
|
809
|
+
* A host without Apps support simply ignores `_meta`, so keep each tool's
|
|
810
|
+
* `content` meaningful on its own and let the text result be the fallback.
|
|
811
|
+
*
|
|
812
|
+
* @example
|
|
813
|
+
* ```typescript
|
|
814
|
+
* import {
|
|
815
|
+
* McpServer,
|
|
816
|
+
* registerAppResource,
|
|
817
|
+
* UI_EXTENSION_ID,
|
|
818
|
+
* UI_RESOURCE_MIME_TYPE,
|
|
819
|
+
* z,
|
|
820
|
+
* } from '@ttoss/http-server-mcp';
|
|
821
|
+
*
|
|
822
|
+
* const server = new McpServer(
|
|
823
|
+
* { name: 'weather', version: '1.0.0' },
|
|
824
|
+
* {
|
|
825
|
+
* capabilities: {
|
|
826
|
+
* extensions: { [UI_EXTENSION_ID]: { mimeTypes: [UI_RESOURCE_MIME_TYPE] } },
|
|
827
|
+
* },
|
|
828
|
+
* }
|
|
829
|
+
* );
|
|
830
|
+
*
|
|
831
|
+
* const dashboard = registerAppResource({
|
|
832
|
+
* server,
|
|
833
|
+
* name: 'weather_dashboard',
|
|
834
|
+
* uri: 'ui://weather/dashboard',
|
|
835
|
+
* description: 'Interactive weather dashboard',
|
|
836
|
+
* html: dashboardHtml,
|
|
837
|
+
* ui: {
|
|
838
|
+
* csp: { connectDomains: ['https://api.openweathermap.org'] },
|
|
839
|
+
* prefersBorder: true,
|
|
840
|
+
* },
|
|
841
|
+
* });
|
|
842
|
+
*
|
|
843
|
+
* server.registerTool(
|
|
844
|
+
* 'get-weather',
|
|
845
|
+
* {
|
|
846
|
+
* description: 'Get the weather for a location',
|
|
847
|
+
* inputSchema: { location: z.string() },
|
|
848
|
+
* _meta: dashboard.toolMeta(),
|
|
849
|
+
* },
|
|
850
|
+
* async ({ location }) => {
|
|
851
|
+
* const forecast = await fetchForecast(location);
|
|
852
|
+
* return {
|
|
853
|
+
* // Text stays meaningful: it is what a host without Apps renders.
|
|
854
|
+
* content: [{ type: 'text', text: summarise(forecast) }],
|
|
855
|
+
* structuredContent: forecast,
|
|
856
|
+
* };
|
|
857
|
+
* }
|
|
858
|
+
* );
|
|
859
|
+
* ```
|
|
860
|
+
*/
|
|
861
|
+
declare const registerAppResource: ({
|
|
862
|
+
server,
|
|
863
|
+
name,
|
|
864
|
+
uri,
|
|
865
|
+
description,
|
|
866
|
+
html,
|
|
867
|
+
ui
|
|
868
|
+
}: RegisterAppResourceParams) => RegisteredAppResource;
|
|
869
|
+
//#endregion
|
|
679
870
|
//#region src/registerToolFromSchema.d.ts
|
|
680
871
|
/**
|
|
681
872
|
* A plain JSON Schema object (draft-07 compatible) describing the shape of a
|
|
@@ -722,6 +913,19 @@ interface RegisterToolFromSchemaParams {
|
|
|
722
913
|
* @default false
|
|
723
914
|
*/
|
|
724
915
|
validateArguments?: boolean;
|
|
916
|
+
/**
|
|
917
|
+
* Tool metadata forwarded verbatim on `tools/list`.
|
|
918
|
+
*
|
|
919
|
+
* The MCP Apps extension links a tool to the view its result is rendered
|
|
920
|
+
* with through this field; build the bag with `toolMeta()` from
|
|
921
|
+
* `registerAppResource` rather than writing the keys by hand.
|
|
922
|
+
*
|
|
923
|
+
* @example
|
|
924
|
+
* ```typescript
|
|
925
|
+
* _meta: dashboard.toolMeta({ visibility: ['app'] }),
|
|
926
|
+
* ```
|
|
927
|
+
*/
|
|
928
|
+
_meta?: Record<string, unknown>;
|
|
725
929
|
/**
|
|
726
930
|
* Tool handler invoked when the AI client calls the tool.
|
|
727
931
|
* Receives the request arguments, validated against `inputSchema` only when
|
|
@@ -745,7 +949,7 @@ interface RegisterToolFromSchemaParams {
|
|
|
745
949
|
*
|
|
746
950
|
* @param server - The `McpServer` instance to register the tool on.
|
|
747
951
|
* @param params - Tool configuration including name, description, inputSchema,
|
|
748
|
-
* validateArguments, and handler.
|
|
952
|
+
* validateArguments, `_meta`, and handler.
|
|
749
953
|
*
|
|
750
954
|
* @example
|
|
751
955
|
* ```typescript
|
|
@@ -802,7 +1006,12 @@ type GatedToolDef = {
|
|
|
802
1006
|
* Each gate receives the full {@link ToolCallContext} (identity + args).
|
|
803
1007
|
* Throw to reject the call; return (or resolve) to continue.
|
|
804
1008
|
*/
|
|
805
|
-
gates?: Array<(ctx: ToolCallContext) => void | Promise<void>>;
|
|
1009
|
+
gates?: Array<(ctx: ToolCallContext) => void | Promise<void>>;
|
|
1010
|
+
/**
|
|
1011
|
+
* Tool metadata forwarded verbatim on `tools/list` — how a gated tool links
|
|
1012
|
+
* to an MCP Apps view (`registerAppResource(...).toolMeta()`).
|
|
1013
|
+
*/
|
|
1014
|
+
_meta?: Record<string, unknown>; /** The tool handler. Receives merged call args + `buildContext` output. */
|
|
806
1015
|
method: (args: Record<string, unknown>) => Promise<unknown>;
|
|
807
1016
|
};
|
|
808
1017
|
/** Options for {@link createGatedToolRegistrar}. */
|
|
@@ -1211,4 +1420,4 @@ interface McpRouterOptions {
|
|
|
1211
1420
|
*/
|
|
1212
1421
|
declare const createMcpRouter: (server: McpServer$1, options?: McpRouterOptions) => Router<Application.DefaultState, Application.DefaultContext>;
|
|
1213
1422
|
//#endregion
|
|
1214
|
-
export { ApiCallOptions, CognitoUserPoolConfig, type CreateGatedToolRegistrarOptions, type GatedToolDef, type JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, type RegisterToolFromSchemaParams, type ToolCallContext, type ToolIdentity, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerToolFromSchema, z };
|
|
1423
|
+
export { ApiCallOptions, CognitoUserPoolConfig, type CreateGatedToolRegistrarOptions, type GatedToolDef, type JsonObjectSchema, McpAuthOptions, McpRouterOptions, McpServer, type RegisterAppResourceParams, type RegisterToolFromSchemaParams, type RegisteredAppResource, type ToolCallContext, type ToolIdentity, type ToolMetaParams, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, type UiResourceCsp, type UiResourceMeta, type UiResourcePermissions, type UiToolVisibility, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerAppResource, registerToolFromSchema, z };
|
package/dist/index.mjs
CHANGED
|
@@ -117,6 +117,160 @@ var createMcpRequestServer = ({
|
|
|
117
117
|
};
|
|
118
118
|
};
|
|
119
119
|
|
|
120
|
+
//#endregion
|
|
121
|
+
//#region src/registerAppResource.ts
|
|
122
|
+
/**
|
|
123
|
+
* Identifier of the MCP Apps extension (`io.modelcontextprotocol/ui`), used as
|
|
124
|
+
* the key under `capabilities.extensions` on both sides of the handshake.
|
|
125
|
+
*
|
|
126
|
+
* A client declares Apps support with it; a server may advertise the same key
|
|
127
|
+
* so a host can discover the support without inspecting tool metadata:
|
|
128
|
+
*
|
|
129
|
+
* ```typescript
|
|
130
|
+
* new McpServer(
|
|
131
|
+
* { name: 'weather', version: '1.0.0' },
|
|
132
|
+
* {
|
|
133
|
+
* capabilities: {
|
|
134
|
+
* extensions: {
|
|
135
|
+
* [UI_EXTENSION_ID]: { mimeTypes: [UI_RESOURCE_MIME_TYPE] },
|
|
136
|
+
* },
|
|
137
|
+
* },
|
|
138
|
+
* }
|
|
139
|
+
* );
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
var UI_EXTENSION_ID = "io.modelcontextprotocol/ui";
|
|
143
|
+
/** MIME type every MCP Apps HTML resource is served as. */
|
|
144
|
+
var UI_RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
|
|
145
|
+
/**
|
|
146
|
+
* Deprecated flat tool metadata key for the linked resource URI. Written
|
|
147
|
+
* alongside `_meta.ui.resourceUri` because both forms are legal until the
|
|
148
|
+
* extension reaches GA, at which point this key is removed from the spec.
|
|
149
|
+
*/
|
|
150
|
+
var LEGACY_RESOURCE_URI_KEY = "ui/resourceUri";
|
|
151
|
+
/**
|
|
152
|
+
* Registers an MCP Apps view: a `ui://` resource a tool result is rendered
|
|
153
|
+
* with, per the `io.modelcontextprotocol/ui` extension.
|
|
154
|
+
*
|
|
155
|
+
* The extension asks nothing of the transport — a view is an ordinary MCP
|
|
156
|
+
* resource, and the tool linkage is ordinary tool `_meta` — so this is
|
|
157
|
+
* registration sugar over `registerResource`, not new protocol surface. What
|
|
158
|
+
* it owns is the part that is easy to get wrong: the `ui://` scheme, the exact
|
|
159
|
+
* MIME type, `_meta.ui` on both the declaration and the read result, and
|
|
160
|
+
* writing the deprecated flat linkage key alongside the current one.
|
|
161
|
+
*
|
|
162
|
+
* **Register the linkage unconditionally.** The extension's negotiation is
|
|
163
|
+
* per-request and only reliably readable on the `2026-07-28` revision; in this
|
|
164
|
+
* package's default stateless 2025-era mode there is no remembered
|
|
165
|
+
* `initialize` to read a client capability from. Gating registration on that
|
|
166
|
+
* capability would therefore silently drop the view for every 2025-era client.
|
|
167
|
+
* A host without Apps support simply ignores `_meta`, so keep each tool's
|
|
168
|
+
* `content` meaningful on its own and let the text result be the fallback.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* ```typescript
|
|
172
|
+
* import {
|
|
173
|
+
* McpServer,
|
|
174
|
+
* registerAppResource,
|
|
175
|
+
* UI_EXTENSION_ID,
|
|
176
|
+
* UI_RESOURCE_MIME_TYPE,
|
|
177
|
+
* z,
|
|
178
|
+
* } from '@ttoss/http-server-mcp';
|
|
179
|
+
*
|
|
180
|
+
* const server = new McpServer(
|
|
181
|
+
* { name: 'weather', version: '1.0.0' },
|
|
182
|
+
* {
|
|
183
|
+
* capabilities: {
|
|
184
|
+
* extensions: { [UI_EXTENSION_ID]: { mimeTypes: [UI_RESOURCE_MIME_TYPE] } },
|
|
185
|
+
* },
|
|
186
|
+
* }
|
|
187
|
+
* );
|
|
188
|
+
*
|
|
189
|
+
* const dashboard = registerAppResource({
|
|
190
|
+
* server,
|
|
191
|
+
* name: 'weather_dashboard',
|
|
192
|
+
* uri: 'ui://weather/dashboard',
|
|
193
|
+
* description: 'Interactive weather dashboard',
|
|
194
|
+
* html: dashboardHtml,
|
|
195
|
+
* ui: {
|
|
196
|
+
* csp: { connectDomains: ['https://api.openweathermap.org'] },
|
|
197
|
+
* prefersBorder: true,
|
|
198
|
+
* },
|
|
199
|
+
* });
|
|
200
|
+
*
|
|
201
|
+
* server.registerTool(
|
|
202
|
+
* 'get-weather',
|
|
203
|
+
* {
|
|
204
|
+
* description: 'Get the weather for a location',
|
|
205
|
+
* inputSchema: { location: z.string() },
|
|
206
|
+
* _meta: dashboard.toolMeta(),
|
|
207
|
+
* },
|
|
208
|
+
* async ({ location }) => {
|
|
209
|
+
* const forecast = await fetchForecast(location);
|
|
210
|
+
* return {
|
|
211
|
+
* // Text stays meaningful: it is what a host without Apps renders.
|
|
212
|
+
* content: [{ type: 'text', text: summarise(forecast) }],
|
|
213
|
+
* structuredContent: forecast,
|
|
214
|
+
* };
|
|
215
|
+
* }
|
|
216
|
+
* );
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
var registerAppResource = ({
|
|
220
|
+
server,
|
|
221
|
+
name,
|
|
222
|
+
uri,
|
|
223
|
+
description,
|
|
224
|
+
html,
|
|
225
|
+
ui
|
|
226
|
+
}) => {
|
|
227
|
+
if (!uri.startsWith("ui://")) throw new Error(`An MCP Apps resource URI must use the ui:// scheme (received "${uri}").`);
|
|
228
|
+
const meta = ui === void 0 ? void 0 : {
|
|
229
|
+
ui
|
|
230
|
+
};
|
|
231
|
+
const resource = server.registerResource(name, uri, {
|
|
232
|
+
description,
|
|
233
|
+
mimeType: UI_RESOURCE_MIME_TYPE,
|
|
234
|
+
...(meta === void 0 ? {} : {
|
|
235
|
+
_meta: meta
|
|
236
|
+
})
|
|
237
|
+
}, async requestedUri => {
|
|
238
|
+
const text = typeof html === "string" ? html : await html({
|
|
239
|
+
uri: requestedUri
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
contents: [{
|
|
243
|
+
uri: requestedUri.href,
|
|
244
|
+
mimeType: UI_RESOURCE_MIME_TYPE,
|
|
245
|
+
text,
|
|
246
|
+
...(meta === void 0 ? {} : {
|
|
247
|
+
_meta: meta
|
|
248
|
+
})
|
|
249
|
+
}]
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
const toolMeta = ({
|
|
253
|
+
visibility,
|
|
254
|
+
_meta
|
|
255
|
+
} = {}) => {
|
|
256
|
+
return {
|
|
257
|
+
..._meta,
|
|
258
|
+
ui: {
|
|
259
|
+
resourceUri: uri,
|
|
260
|
+
...(visibility === void 0 ? {} : {
|
|
261
|
+
visibility
|
|
262
|
+
})
|
|
263
|
+
},
|
|
264
|
+
[LEGACY_RESOURCE_URI_KEY]: uri
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
return {
|
|
268
|
+
uri,
|
|
269
|
+
resource,
|
|
270
|
+
toolMeta
|
|
271
|
+
};
|
|
272
|
+
};
|
|
273
|
+
|
|
120
274
|
//#endregion
|
|
121
275
|
//#region src/registerToolFromSchema.ts
|
|
122
276
|
/**
|
|
@@ -160,7 +314,7 @@ var toStandardSchema = ({
|
|
|
160
314
|
*
|
|
161
315
|
* @param server - The `McpServer` instance to register the tool on.
|
|
162
316
|
* @param params - Tool configuration including name, description, inputSchema,
|
|
163
|
-
* validateArguments, and handler.
|
|
317
|
+
* validateArguments, `_meta`, and handler.
|
|
164
318
|
*
|
|
165
319
|
* @example
|
|
166
320
|
* ```typescript
|
|
@@ -191,14 +345,16 @@ var registerToolFromSchema = (server, params) => {
|
|
|
191
345
|
properties: {}
|
|
192
346
|
},
|
|
193
347
|
validateArguments = false,
|
|
194
|
-
handler
|
|
348
|
+
handler,
|
|
349
|
+
_meta
|
|
195
350
|
} = params;
|
|
196
351
|
server.registerTool(name, {
|
|
197
352
|
description,
|
|
198
353
|
inputSchema: toStandardSchema({
|
|
199
354
|
inputSchema,
|
|
200
355
|
validateArguments
|
|
201
|
-
})
|
|
356
|
+
}),
|
|
357
|
+
_meta
|
|
202
358
|
}, async args => {
|
|
203
359
|
return handler(args);
|
|
204
360
|
});
|
|
@@ -313,7 +469,8 @@ var createGatedToolRegistrar = ({
|
|
|
313
469
|
};
|
|
314
470
|
server.registerTool(def.name, {
|
|
315
471
|
description: def.description,
|
|
316
|
-
inputSchema: def.inputSchema
|
|
472
|
+
inputSchema: def.inputSchema,
|
|
473
|
+
_meta: def._meta
|
|
317
474
|
}, handler);
|
|
318
475
|
};
|
|
319
476
|
return {
|
|
@@ -633,4 +790,4 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
633
790
|
};
|
|
634
791
|
|
|
635
792
|
//#endregion
|
|
636
|
-
export { McpServer, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerToolFromSchema, z };
|
|
793
|
+
export { McpServer, UI_EXTENSION_ID, UI_RESOURCE_MIME_TYPE, apiCall, checkScopes, createGatedToolRegistrar, createMcpRouter, getIdentity, registerAppResource, registerToolFromSchema, z };
|