@casys/mcp-server 0.12.0 → 0.14.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/src/types.ts CHANGED
@@ -45,9 +45,9 @@ export interface RateLimitContext {
45
45
  }
46
46
 
47
47
  /**
48
- * Configuration options for ConcurrentMCPServer
48
+ * Configuration options for McpApp
49
49
  */
50
- export interface ConcurrentServerOptions {
50
+ export interface McpAppOptions {
51
51
  /** Server name (shown in MCP protocol) */
52
52
  name: string;
53
53
 
@@ -251,6 +251,117 @@ export const MCP_APP_MIME_TYPE = "text/html;profile=mcp-app" as const;
251
251
  /** URI scheme for MCP Apps resources */
252
252
  export const MCP_APP_URI_SCHEME = "ui:" as const;
253
253
 
254
+ /**
255
+ * Well-known extension identifier for the MCP Apps protocol.
256
+ *
257
+ * Clients advertise MCP Apps support by including this key in
258
+ * `clientCapabilities.extensions` (per the MCP SDK 1.29 extensions
259
+ * feature). Servers read it via {@link getMcpAppsCapability} to decide
260
+ * whether to register UI-rendering tools or fall back to text-only.
261
+ *
262
+ * @see {@link https://github.com/modelcontextprotocol/ext-apps | MCP Apps spec}
263
+ */
264
+ export const MCP_APPS_EXTENSION_ID = "io.modelcontextprotocol/ui" as const;
265
+
266
+ /**
267
+ * MCP Apps protocol spec version this package targets.
268
+ *
269
+ * Matches the dated spec at
270
+ * https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/2026-01-26/apps.mdx
271
+ *
272
+ * Bump this constant in the same commit that adopts a newer dated spec.
273
+ */
274
+ export const MCP_APPS_PROTOCOL_VERSION = "2026-01-26" as const;
275
+
276
+ /**
277
+ * MCP Apps capability advertised by a client.
278
+ *
279
+ * Returned by {@link getMcpAppsCapability} after reading
280
+ * `clientCapabilities.extensions[MCP_APPS_EXTENSION_ID]`.
281
+ *
282
+ * The capability object is intentionally minimal — the spec keeps it
283
+ * extensible by adding fields rather than removing them, so consumers
284
+ * MUST tolerate unknown fields.
285
+ */
286
+ export interface McpAppsClientCapability {
287
+ /**
288
+ * MIME types the client can render as MCP Apps.
289
+ *
290
+ * Typically includes `"text/html;profile=mcp-app"`. An empty or
291
+ * absent array means the client advertised support but listed no
292
+ * concrete mime types — defensively assume nothing.
293
+ */
294
+ mimeTypes?: string[];
295
+ }
296
+
297
+ /**
298
+ * Read the MCP Apps capability from a client's advertised capabilities.
299
+ *
300
+ * Best-effort, defensive reader. Returns `undefined` for any of:
301
+ * - `null` / `undefined` input
302
+ * - `clientCapabilities` without an `extensions` field
303
+ * - `extensions` without the {@link MCP_APPS_EXTENSION_ID} key
304
+ * - extension value that is not a plain object (string, number, null, ...)
305
+ *
306
+ * Malformed `mimeTypes` (wrong type, non-string entries) are silently
307
+ * filtered rather than thrown — agents reading this function need a
308
+ * predictable contract that never crashes downstream consumers on
309
+ * untrusted client data.
310
+ *
311
+ * **Validation scope:** this function only validates the *type* of
312
+ * `mimeTypes` entries (must be string). It does NOT validate that the
313
+ * strings look like valid mime types (e.g. empty strings or garbage
314
+ * content pass through). Consumers should compare against known
315
+ * constants like {@link MCP_APP_MIME_TYPE} via `.includes()` rather
316
+ * than treating the array as a generic allowlist.
317
+ *
318
+ * @param clientCapabilities - The `ClientCapabilities` object from the
319
+ * MCP SDK initialize handshake. May be `null` or `undefined` if the
320
+ * client never sent capabilities.
321
+ * @returns The MCP Apps capability if the client advertised support,
322
+ * otherwise `undefined`.
323
+ *
324
+ * @example
325
+ * ```typescript
326
+ * const cap = getMcpAppsCapability(client.getClientCapabilities());
327
+ * if (cap?.mimeTypes?.includes(MCP_APP_MIME_TYPE)) {
328
+ * // register UI-rendering tools
329
+ * } else {
330
+ * // register text-only fallback tools
331
+ * }
332
+ * ```
333
+ */
334
+ export function getMcpAppsCapability(
335
+ clientCapabilities:
336
+ | (Record<string, unknown> & { extensions?: Record<string, unknown> })
337
+ | null
338
+ | undefined,
339
+ ): McpAppsClientCapability | undefined {
340
+ if (clientCapabilities === null || clientCapabilities === undefined) {
341
+ return undefined;
342
+ }
343
+ const extensions = clientCapabilities.extensions;
344
+ if (extensions === null || typeof extensions !== "object") {
345
+ return undefined;
346
+ }
347
+ const raw = extensions[MCP_APPS_EXTENSION_ID];
348
+ if (raw === null || typeof raw !== "object") {
349
+ return undefined;
350
+ }
351
+ // We have a capability object — extract known fields defensively.
352
+ const result: McpAppsClientCapability = {};
353
+ const rawMimeTypes = (raw as Record<string, unknown>).mimeTypes;
354
+ if (Array.isArray(rawMimeTypes)) {
355
+ const validMimeTypes = rawMimeTypes.filter(
356
+ (m): m is string => typeof m === "string",
357
+ );
358
+ if (validMimeTypes.length > 0) {
359
+ result.mimeTypes = validMimeTypes;
360
+ }
361
+ }
362
+ return result;
363
+ }
364
+
254
365
  // ============================================
255
366
  // MCP Tool Types
256
367
  // ============================================
@@ -485,8 +596,15 @@ export interface HttpRateLimitOptions {
485
596
  /**
486
597
  * Options for starting an HTTP server
487
598
  */
599
+ // Re-exported from the runtime port so consumers can import the canonical
600
+ // fetch handler type from the same module as HttpServerOptions.
601
+ export type { FetchHandler } from "./runtime/types.js";
602
+ import type { FetchHandler } from "./runtime/types.js";
603
+
488
604
  export interface HttpServerOptions {
489
- /** Port to listen on */
605
+ /**
606
+ * Port to listen on. Ignored when {@link embedded} is `true`.
607
+ */
490
608
  port: number;
491
609
 
492
610
  /** Hostname to bind to (default: "0.0.0.0") */
@@ -532,6 +650,27 @@ export interface HttpServerOptions {
532
650
  * @param info - Server address info
533
651
  */
534
652
  onListen?: (info: { hostname: string; port: number }) => void;
653
+
654
+ /**
655
+ * Embedded mode: skip binding a port and instead surface the Hono fetch
656
+ * handler via the {@link embeddedHandlerCallback} option. Used by
657
+ * {@link McpApp.getFetchHandler} so consumers (Fresh, Hono,
658
+ * Express, etc.) can mount the MCP HTTP stack inside their own server
659
+ * without giving up port ownership.
660
+ *
661
+ * When `true`, `port`, `hostname`, and `onListen` are ignored.
662
+ */
663
+ embedded?: boolean;
664
+
665
+ /**
666
+ * Receives the Hono fetch handler when running in embedded mode.
667
+ * Required when {@link embedded} is `true`. Called exactly once,
668
+ * synchronously, before `startHttp` returns.
669
+ *
670
+ * Most consumers should use {@link McpApp.getFetchHandler}
671
+ * instead of setting this directly.
672
+ */
673
+ embeddedHandlerCallback?: (handler: FetchHandler) => void;
535
674
  }
536
675
 
537
676
  /**
@@ -8,7 +8,13 @@
8
8
  */
9
9
 
10
10
  /** Directories to skip during auto-discovery */
11
- const SKIP_DIRS = new Set(["shared", "dist", "node_modules", ".cache", ".vite"]);
11
+ const SKIP_DIRS = new Set([
12
+ "shared",
13
+ "dist",
14
+ "node_modules",
15
+ ".cache",
16
+ ".vite",
17
+ ]);
12
18
 
13
19
  /**
14
20
  * Convert a file:// URL to a filesystem path.