@ttoss/http-server-mcp 0.27.0 → 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 +107 -5
- package/dist/index.cjs +179 -9
- package/dist/index.d.cts +225 -12
- package/dist/index.d.mts +225 -12
- package/dist/index.mjs +177 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -371,7 +371,7 @@ The `WWW-Authenticate: Bearer resource_metadata="…"` header is how MCP clients
|
|
|
371
371
|
|
|
372
372
|
The two behaviors the [MCP authorization spec](https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/authorization/) requires for client bootstrapping are built into the `auth` option, so you no longer need the hand-rolled middleware shown above:
|
|
373
373
|
|
|
374
|
-
- **`publicMethods`** — JSON-RPC methods that bypass verification, read from the request body's `method` field. Defaults to `['initialize']
|
|
374
|
+
- **`publicMethods`** — JSON-RPC methods that bypass verification, read from the request body's `method` field. Defaults to `['initialize', 'server/discover']` — the lifecycle handshake of each protocol era, which the spec sanctions so a client can complete it before it can discover the authorization server. Pass `[]` to require a token for every method, or a custom list to change the exempt set. Adding `tools/list` serves the full tool catalogue — every tool name, description, and input schema — to unauthenticated callers; see [`publicMethods` and OAuth clients](#publicmethods-and-oauth-clients) before doing so.
|
|
375
375
|
- **`resourceMetadataUrl`** — the URL a `401` advertises as `WWW-Authenticate: Bearer resource_metadata="<url>"` (RFC 9728) instead of a bare `Bearer`, pointing MCP clients at the protected-resource metadata document. **You normally do not set it**: when the document is served — i.e. both `resourceServerUrl` and `authorizationServerUrl` are configured — it defaults to the RFC 9728 location this router serves it at, so the header and the routes cannot drift apart. When the document is not served, the header stays a bare `Bearer` rather than naming a location with no route. The field exists for the one configuration where this router deliberately does not serve the document — an `oauthServer()` in the same deployment already answers that path, so this router is mounted without `resourceServerUrl`/`authorizationServerUrl` to avoid two routers on one path, and has nothing to derive from. Compute the value with `protectedResourceMetadataUrl` from [`@ttoss/auth-core`](https://ttoss.dev/docs/modules/packages/auth-core) rather than typing it, so both halves apply the same §3.1 rule.
|
|
376
376
|
|
|
377
377
|
```typescript
|
|
@@ -383,7 +383,7 @@ createMcpRouter(mcpServer, {
|
|
|
383
383
|
resourceServerUrl: 'https://mcp.example.com',
|
|
384
384
|
authorizationServerUrl:
|
|
385
385
|
'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_xxx',
|
|
386
|
-
// publicMethods defaults to ['initialize'].
|
|
386
|
+
// publicMethods defaults to ['initialize', 'server/discover'].
|
|
387
387
|
},
|
|
388
388
|
});
|
|
389
389
|
```
|
|
@@ -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.
|
|
@@ -502,7 +574,7 @@ Creates a Koa router configured to handle MCP protocol requests.
|
|
|
502
574
|
- `auth.verifyToken` — Custom async token verifier `(token: string) => Promise<unknown>`
|
|
503
575
|
- `auth.requiredScopes` — Router-level scope guard; returns 403 if any scope is missing
|
|
504
576
|
- `auth.resourceServerUrl` + `auth.authorizationServerUrl` — Enable the protected-resource metadata document, served at both `/.well-known/oauth-protected-resource` and the RFC 9728 path-derived `/.well-known/oauth-protected-resource<path>`; the metadata `resource` is set to `resourceServerUrl + path` so clients following `resource` land on the actual MCP endpoint
|
|
505
|
-
- `auth.publicMethods` — JSON-RPC methods that bypass verification (default `['initialize']`)
|
|
577
|
+
- `auth.publicMethods` — JSON-RPC methods that bypass verification (default `['initialize', 'server/discover']`)
|
|
506
578
|
- `auth.resourceMetadataUrl` — URL advertised in the RFC 9728 `WWW-Authenticate: Bearer resource_metadata="…"` header on a 401. Defaults to the location derived from `resourceServerUrl` + `path` (the one this router serves), so the header cannot drift from the routes; set it only when a separate `oauthServer()` serves the document and this router is mounted without `resourceServerUrl`/`authorizationServerUrl`
|
|
507
579
|
- `auth.resourceIndicator` — Expected `aud` value(s) (RFC 8707); rejects tokens minted for a different resource. See [Resource indicator validation](#resource-indicator-validation-rfc-8707)
|
|
508
580
|
|
|
@@ -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`
|
|
@@ -873,7 +974,7 @@ expect(call.status).toBe(200);
|
|
|
873
974
|
|
|
874
975
|
### `publicMethods` and OAuth clients
|
|
875
976
|
|
|
876
|
-
`auth.publicMethods` defaults to `['initialize']`: the handshake is reachable without a token, nothing else is.
|
|
977
|
+
`auth.publicMethods` defaults to `['initialize', 'server/discover']`: the lifecycle handshake is reachable without a token, nothing else is. There are two entries because the exemption is about _the handshake_, not the method name — `initialize` is the 2025-era handshake and `server/discover` its `2026-07-28` replacement, since that revision removed `initialize` outright. Naming only one leaves the other era unable to negotiate at all.
|
|
877
978
|
|
|
878
979
|
**Adding `tools/list` is a deliberate exposure.** It serves the full tool catalogue — every name, description, and input schema — to anyone who can reach the endpoint. Those leak internal resource names and domain vocabulary, and for an OpenAPI-derived server the schemas mirror the REST surface, so the catalogue becomes an unauthenticated map of the whole API including operations the caller could never invoke. It buys an OAuth client nothing, because the `401` is what starts the authorization flow and a client that lists tools anonymously still cannot call one. Open it only to serve callers that will never authenticate:
|
|
879
980
|
|
|
@@ -885,7 +986,7 @@ publicMethods: ['initialize', 'tools/list'],
|
|
|
885
986
|
|
|
886
987
|
Either way the authorization flow works. Driving the official MCP client SDK against this router shows the RFC 9728 challenge resolving to the protected-resource document, the authorization server, and the redirect, whether the `401` arrives on `initialize` (`publicMethods: []`) or on the request after it (the default). `tests/unit/tests/oauth-client-flow.test.ts` asserts it.
|
|
887
988
|
|
|
888
|
-
|
|
989
|
+
**Whether the handshake should be public at all is a separate question.** `publicMethods: []` closes both eras, and the authorization flow still works — RFC 9728 discovery is driven by the challenge itself, so a client authenticates from its very first request instead of one step later.
|
|
889
990
|
|
|
890
991
|
## AWS Lambda Deployment
|
|
891
992
|
|
|
@@ -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 {
|
|
@@ -327,13 +484,23 @@ var createGatedToolRegistrar = ({
|
|
|
327
484
|
//#endregion
|
|
328
485
|
//#region src/index.ts
|
|
329
486
|
/**
|
|
330
|
-
* MCP lifecycle
|
|
487
|
+
* MCP lifecycle handshakes reachable before a client authenticates.
|
|
488
|
+
*
|
|
489
|
+
* One entry per protocol era, because the exemption the MCP authorization spec
|
|
490
|
+
* sanctions is about *the handshake* — a client has to complete it before it
|
|
491
|
+
* can discover the authorization server — not about a method name:
|
|
492
|
+
*
|
|
493
|
+
* - `initialize` is the 2025-era handshake.
|
|
494
|
+
* - `server/discover` is its `2026-07-28` replacement. That revision removed
|
|
495
|
+
* `initialize` entirely, so naming only `initialize` left the modern era
|
|
496
|
+
* with no public method at all — the asymmetry ttoss/ttoss#1222 fixed.
|
|
497
|
+
*
|
|
498
|
+
* A future revision that renames the handshake again needs a matching entry
|
|
499
|
+
* here; nothing detects that automatically.
|
|
331
500
|
*
|
|
332
|
-
*
|
|
333
|
-
* to complete the handshake before it can discover the authorization server.
|
|
334
|
-
* `tools/list` is not here — see {@link McpAuthOptions.publicMethods}.
|
|
501
|
+
* `tools/list` is deliberately absent — see {@link McpAuthOptions.publicMethods}.
|
|
335
502
|
*/
|
|
336
|
-
var DEFAULT_PUBLIC_METHODS = ["initialize"];
|
|
503
|
+
var DEFAULT_PUBLIC_METHODS = ["initialize", "server/discover"];
|
|
337
504
|
/**
|
|
338
505
|
* Asserts that a verified token's `aud` claim includes at least one of the
|
|
339
506
|
* expected resource indicator values. `aud` may be a single string or an
|
|
@@ -632,11 +799,14 @@ Object.defineProperty(exports, 'McpServer', {
|
|
|
632
799
|
return _modelcontextprotocol_server.McpServer;
|
|
633
800
|
}
|
|
634
801
|
});
|
|
802
|
+
exports.UI_EXTENSION_ID = UI_EXTENSION_ID;
|
|
803
|
+
exports.UI_RESOURCE_MIME_TYPE = UI_RESOURCE_MIME_TYPE;
|
|
635
804
|
exports.apiCall = apiCall;
|
|
636
805
|
exports.checkScopes = checkScopes;
|
|
637
806
|
exports.createGatedToolRegistrar = createGatedToolRegistrar;
|
|
638
807
|
exports.createMcpRouter = createMcpRouter;
|
|
639
808
|
exports.getIdentity = getIdentity;
|
|
809
|
+
exports.registerAppResource = registerAppResource;
|
|
640
810
|
exports.registerToolFromSchema = registerToolFromSchema;
|
|
641
811
|
Object.defineProperty(exports, 'z', {
|
|
642
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}. */
|
|
@@ -893,10 +1102,13 @@ interface McpAuthOptions {
|
|
|
893
1102
|
/**
|
|
894
1103
|
* JSON-RPC methods (read from `body.method`) that bypass verification.
|
|
895
1104
|
*
|
|
896
|
-
*
|
|
897
|
-
* client completes
|
|
898
|
-
* authorization server.
|
|
899
|
-
*
|
|
1105
|
+
* The lifecycle handshake is public because the MCP authorization spec
|
|
1106
|
+
* sanctions it: a client completes it before it can discover the
|
|
1107
|
+
* authorization server. That means one entry per protocol era —
|
|
1108
|
+
* `initialize` on 2025, `server/discover` on `2026-07-28`, which removed
|
|
1109
|
+
* `initialize` — so neither era is left unable to negotiate. Nothing else
|
|
1110
|
+
* is public, so a server with `auth` configured serves no tool metadata to
|
|
1111
|
+
* an unauthenticated caller.
|
|
900
1112
|
*
|
|
901
1113
|
* Opening `tools/list` is a deliberate choice, not a default. It serves the
|
|
902
1114
|
* full tool catalogue — every name, description, and input schema — to
|
|
@@ -907,7 +1119,7 @@ interface McpAuthOptions {
|
|
|
907
1119
|
* cannot call one. Set it only to serve callers that will never
|
|
908
1120
|
* authenticate.
|
|
909
1121
|
*
|
|
910
|
-
* @default ['initialize']
|
|
1122
|
+
* @default ['initialize', 'server/discover']
|
|
911
1123
|
*
|
|
912
1124
|
* @example
|
|
913
1125
|
* ```typescript
|
|
@@ -1135,9 +1347,10 @@ interface McpRouterOptions {
|
|
|
1135
1347
|
* OAuth / JWT authentication configuration for the MCP endpoint.
|
|
1136
1348
|
*
|
|
1137
1349
|
* When set, incoming MCP requests must include a valid Bearer token in the
|
|
1138
|
-
* `Authorization` header — except for `publicMethods` (by default
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1350
|
+
* `Authorization` header — except for `publicMethods` (by default the
|
|
1351
|
+
* lifecycle handshake of each protocol era: `initialize` and
|
|
1352
|
+
* `server/discover`), which bypass verification so a client can complete
|
|
1353
|
+
* the handshake before authenticating. Invalid or missing tokens
|
|
1141
1354
|
* receive a `401` response with `WWW-Authenticate: Bearer` (or
|
|
1142
1355
|
* `Bearer resource_metadata="..."` when `resourceMetadataUrl` is set, per
|
|
1143
1356
|
* RFC 9728). Tokens that fail a `requiredScopes` check receive `403`.
|
|
@@ -1206,4 +1419,4 @@ interface McpRouterOptions {
|
|
|
1206
1419
|
*/
|
|
1207
1420
|
declare const createMcpRouter: (server: McpServer$1, options?: McpRouterOptions) => Router<Application.DefaultState, Application.DefaultContext>;
|
|
1208
1421
|
//#endregion
|
|
1209
|
-
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}. */
|
|
@@ -894,10 +1103,13 @@ interface McpAuthOptions {
|
|
|
894
1103
|
/**
|
|
895
1104
|
* JSON-RPC methods (read from `body.method`) that bypass verification.
|
|
896
1105
|
*
|
|
897
|
-
*
|
|
898
|
-
* client completes
|
|
899
|
-
* authorization server.
|
|
900
|
-
*
|
|
1106
|
+
* The lifecycle handshake is public because the MCP authorization spec
|
|
1107
|
+
* sanctions it: a client completes it before it can discover the
|
|
1108
|
+
* authorization server. That means one entry per protocol era —
|
|
1109
|
+
* `initialize` on 2025, `server/discover` on `2026-07-28`, which removed
|
|
1110
|
+
* `initialize` — so neither era is left unable to negotiate. Nothing else
|
|
1111
|
+
* is public, so a server with `auth` configured serves no tool metadata to
|
|
1112
|
+
* an unauthenticated caller.
|
|
901
1113
|
*
|
|
902
1114
|
* Opening `tools/list` is a deliberate choice, not a default. It serves the
|
|
903
1115
|
* full tool catalogue — every name, description, and input schema — to
|
|
@@ -908,7 +1120,7 @@ interface McpAuthOptions {
|
|
|
908
1120
|
* cannot call one. Set it only to serve callers that will never
|
|
909
1121
|
* authenticate.
|
|
910
1122
|
*
|
|
911
|
-
* @default ['initialize']
|
|
1123
|
+
* @default ['initialize', 'server/discover']
|
|
912
1124
|
*
|
|
913
1125
|
* @example
|
|
914
1126
|
* ```typescript
|
|
@@ -1136,9 +1348,10 @@ interface McpRouterOptions {
|
|
|
1136
1348
|
* OAuth / JWT authentication configuration for the MCP endpoint.
|
|
1137
1349
|
*
|
|
1138
1350
|
* When set, incoming MCP requests must include a valid Bearer token in the
|
|
1139
|
-
* `Authorization` header — except for `publicMethods` (by default
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1351
|
+
* `Authorization` header — except for `publicMethods` (by default the
|
|
1352
|
+
* lifecycle handshake of each protocol era: `initialize` and
|
|
1353
|
+
* `server/discover`), which bypass verification so a client can complete
|
|
1354
|
+
* the handshake before authenticating. Invalid or missing tokens
|
|
1142
1355
|
* receive a `401` response with `WWW-Authenticate: Bearer` (or
|
|
1143
1356
|
* `Bearer resource_metadata="..."` when `resourceMetadataUrl` is set, per
|
|
1144
1357
|
* RFC 9728). Tokens that fail a `requiredScopes` check receive `403`.
|
|
@@ -1207,4 +1420,4 @@ interface McpRouterOptions {
|
|
|
1207
1420
|
*/
|
|
1208
1421
|
declare const createMcpRouter: (server: McpServer$1, options?: McpRouterOptions) => Router<Application.DefaultState, Application.DefaultContext>;
|
|
1209
1422
|
//#endregion
|
|
1210
|
-
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 {
|
|
@@ -324,13 +481,23 @@ var createGatedToolRegistrar = ({
|
|
|
324
481
|
//#endregion
|
|
325
482
|
//#region src/index.ts
|
|
326
483
|
/**
|
|
327
|
-
* MCP lifecycle
|
|
484
|
+
* MCP lifecycle handshakes reachable before a client authenticates.
|
|
485
|
+
*
|
|
486
|
+
* One entry per protocol era, because the exemption the MCP authorization spec
|
|
487
|
+
* sanctions is about *the handshake* — a client has to complete it before it
|
|
488
|
+
* can discover the authorization server — not about a method name:
|
|
489
|
+
*
|
|
490
|
+
* - `initialize` is the 2025-era handshake.
|
|
491
|
+
* - `server/discover` is its `2026-07-28` replacement. That revision removed
|
|
492
|
+
* `initialize` entirely, so naming only `initialize` left the modern era
|
|
493
|
+
* with no public method at all — the asymmetry ttoss/ttoss#1222 fixed.
|
|
494
|
+
*
|
|
495
|
+
* A future revision that renames the handshake again needs a matching entry
|
|
496
|
+
* here; nothing detects that automatically.
|
|
328
497
|
*
|
|
329
|
-
*
|
|
330
|
-
* to complete the handshake before it can discover the authorization server.
|
|
331
|
-
* `tools/list` is not here — see {@link McpAuthOptions.publicMethods}.
|
|
498
|
+
* `tools/list` is deliberately absent — see {@link McpAuthOptions.publicMethods}.
|
|
332
499
|
*/
|
|
333
|
-
var DEFAULT_PUBLIC_METHODS = ["initialize"];
|
|
500
|
+
var DEFAULT_PUBLIC_METHODS = ["initialize", "server/discover"];
|
|
334
501
|
/**
|
|
335
502
|
* Asserts that a verified token's `aud` claim includes at least one of the
|
|
336
503
|
* expected resource indicator values. `aud` may be a single string or an
|
|
@@ -623,4 +790,4 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
623
790
|
};
|
|
624
791
|
|
|
625
792
|
//#endregion
|
|
626
|
-
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 };
|