@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 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>>; /** The tool handler. Receives merged call args + `buildContext` output. */
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>>; /** The tool handler. Receives merged call args + `buildContext` output. */
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ttoss/http-server-mcp",
3
- "version": "0.27.1",
3
+ "version": "0.28.0",
4
4
  "description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
5
5
  "keywords": [
6
6
  "ai",