@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.
@@ -1,21 +1,23 @@
1
1
  /**
2
- * Concurrent MCP Server Framework
2
+ * McpApp — Hono-style framework for MCP servers
3
3
  *
4
4
  * High-performance MCP server with built-in concurrency control,
5
5
  * backpressure, and optional sampling support.
6
6
  *
7
7
  * Wraps the official @modelcontextprotocol/sdk with production-ready
8
- * concurrency features.
8
+ * middleware, auth, and observability features.
9
9
  *
10
- * @module lib/server/concurrent-server
10
+ * @module lib/server/mcp-app
11
11
  */
12
12
 
13
13
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
14
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
15
15
  import {
16
+ type CallToolRequest,
16
17
  CallToolRequestSchema,
17
- ListToolsRequestSchema,
18
18
  ListResourcesRequestSchema,
19
+ ListToolsRequestSchema,
20
+ type ReadResourceRequest,
19
21
  ReadResourceRequestSchema,
20
22
  } from "@modelcontextprotocol/sdk/types.js";
21
23
  import { Hono } from "hono";
@@ -45,9 +47,11 @@ import { createScopeMiddleware } from "./auth/scope-middleware.js";
45
47
  import { createAuthProviderFromConfig, loadAuthConfig } from "./auth/config.js";
46
48
  import type { AuthProvider } from "./auth/provider.js";
47
49
  import type {
48
- ConcurrentServerOptions,
50
+ FetchHandler,
49
51
  HttpRateLimitContext,
50
52
  HttpServerOptions,
53
+ McpAppOptions,
54
+ McpAppsClientCapability,
51
55
  MCPResource,
52
56
  MCPTool,
53
57
  QueueMetrics,
@@ -56,8 +60,12 @@ import type {
56
60
  StructuredToolResult,
57
61
  ToolHandler,
58
62
  } from "./types.js";
59
- import { MCP_APP_MIME_TYPE, MCP_APP_URI_SCHEME } from "./types.js";
60
- import { resolveViewerDistPath, discoverViewers } from "./ui/viewer-utils.js";
63
+ import {
64
+ getMcpAppsCapability,
65
+ MCP_APP_MIME_TYPE,
66
+ MCP_APP_URI_SCHEME,
67
+ } from "./types.js";
68
+ import { discoverViewers, resolveViewerDistPath } from "./ui/viewer-utils.js";
61
69
  import type { DirEntry, DiscoverViewersFS } from "./ui/viewer-utils.js";
62
70
  import { buildCspHeader, injectCspMetaTag } from "./security/csp.js";
63
71
  import { ServerMetrics } from "./observability/metrics.js";
@@ -157,7 +165,7 @@ async function readBodyWithLimit(
157
165
  }
158
166
 
159
167
  /**
160
- * ConcurrentMCPServer provides a high-performance MCP server
168
+ * McpApp provides a high-performance MCP server
161
169
  *
162
170
  * Features:
163
171
  * - Wraps official @modelcontextprotocol/sdk
@@ -169,7 +177,7 @@ async function readBodyWithLimit(
169
177
  *
170
178
  * @example
171
179
  * ```typescript
172
- * const server = new ConcurrentMCPServer({
180
+ * const server = new McpApp({
173
181
  * name: "my-server",
174
182
  * version: "1.0.0",
175
183
  * maxConcurrent: 5,
@@ -180,7 +188,7 @@ async function readBodyWithLimit(
180
188
  * await server.start();
181
189
  * ```
182
190
  */
183
- export class ConcurrentMCPServer {
191
+ export class McpApp {
184
192
  private mcpServer: McpServer;
185
193
  private requestQueue: RequestQueue;
186
194
  private rateLimiter: RateLimiter | null = null;
@@ -188,7 +196,7 @@ export class ConcurrentMCPServer {
188
196
  private samplingBridge: SamplingBridge | null = null;
189
197
  private tools = new Map<string, ToolWithHandler>();
190
198
  private resources = new Map<string, RegisteredResourceInfo>();
191
- private options: ConcurrentServerOptions;
199
+ private options: McpAppOptions;
192
200
  private started = false;
193
201
  private resourceHandlersInstalled = false;
194
202
 
@@ -222,7 +230,7 @@ export class ConcurrentMCPServer {
222
230
  windowMs: 60_000,
223
231
  });
224
232
 
225
- constructor(options: ConcurrentServerOptions) {
233
+ constructor(options: McpAppOptions) {
226
234
  this.options = options;
227
235
 
228
236
  // Create SDK MCP server
@@ -300,26 +308,29 @@ export class ConcurrentMCPServer {
300
308
  });
301
309
 
302
310
  // resources/read — serve resource content by URI
303
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
304
- const uri = request.params.uri;
305
- const info = this.resources.get(uri);
306
- if (!info) {
307
- throw new Error(`Resource not found: ${uri}`);
308
- }
311
+ server.setRequestHandler(
312
+ ReadResourceRequestSchema,
313
+ async (request: ReadResourceRequest) => {
314
+ const uri = request.params.uri;
315
+ const info = this.resources.get(uri);
316
+ if (!info) {
317
+ throw new Error(`Resource not found: ${uri}`);
318
+ }
309
319
 
310
- try {
311
- const content = await info.handler(new URL(uri));
312
- const finalContent = this.applyResourceCsp(content);
313
- return { contents: [finalContent] };
314
- } catch (error) {
315
- this.log(
316
- `[ERROR] Resource handler failed for ${uri}: ${
317
- error instanceof Error ? error.message : String(error)
318
- }`,
319
- );
320
- throw error;
321
- }
322
- });
320
+ try {
321
+ const content = await info.handler(new URL(uri));
322
+ const finalContent = this.applyResourceCsp(content);
323
+ return { contents: [finalContent] };
324
+ } catch (error) {
325
+ this.log(
326
+ `[ERROR] Resource handler failed for ${uri}: ${
327
+ error instanceof Error ? error.message : String(error)
328
+ }`,
329
+ );
330
+ throw error;
331
+ }
332
+ },
333
+ );
323
334
 
324
335
  this.resourceHandlersInstalled = true;
325
336
  this.log("Resources capability pre-declared (expectResources: true)");
@@ -342,20 +353,24 @@ export class ConcurrentMCPServer {
342
353
  });
343
354
 
344
355
  // tools/call handler (delegates to middleware pipeline)
345
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
346
- const toolName = request.params.name;
347
- const args = request.params.arguments || {};
356
+ server.setRequestHandler(
357
+ CallToolRequestSchema,
358
+ async (request: CallToolRequest) => {
359
+ const toolName = request.params.name;
360
+ const args = request.params.arguments || {};
348
361
 
349
- let result: unknown;
350
- try {
351
- result = await this.executeToolCall(toolName, args);
352
- } catch (error) {
353
- return this.handleToolError(error, toolName);
354
- }
362
+ let result: unknown;
363
+ try {
364
+ result = await this.executeToolCall(toolName, args);
365
+ } catch (error) {
366
+ return this.handleToolError(error, toolName);
367
+ }
355
368
 
356
- // Serialization errors are framework bugs, not tool errors — let them propagate
357
- return this.buildToolCallResult(toolName, result);
358
- });
369
+ // Serialization errors are framework bugs, not tool errors —
370
+ // let them propagate
371
+ return this.buildToolCallResult(toolName, result);
372
+ },
373
+ );
359
374
  }
360
375
 
361
376
  /**
@@ -370,7 +385,7 @@ export class ConcurrentMCPServer {
370
385
  ): void {
371
386
  if (this.started) {
372
387
  throw new Error(
373
- "[ConcurrentMCPServer] Cannot register tools after server started. " +
388
+ "[McpApp] Cannot register tools after server started. " +
374
389
  "Call registerTools() before start() or startHttp().",
375
390
  );
376
391
  }
@@ -403,7 +418,7 @@ export class ConcurrentMCPServer {
403
418
  registerTool(tool: MCPTool, handler: ToolHandler): void {
404
419
  if (this.started) {
405
420
  throw new Error(
406
- "[ConcurrentMCPServer] Cannot register tools after server started. " +
421
+ "[McpApp] Cannot register tools after server started. " +
407
422
  "Call registerTool() before start() or startHttp().",
408
423
  );
409
424
  }
@@ -481,7 +496,9 @@ export class ConcurrentMCPServer {
481
496
  unregisterTool(toolName: string): boolean {
482
497
  const deleted = this.tools.delete(toolName);
483
498
  if (deleted) {
484
- this.log(`Unregistered tool: ${toolName} (remaining: ${this.tools.size})`);
499
+ this.log(
500
+ `Unregistered tool: ${toolName} (remaining: ${this.tools.size})`,
501
+ );
485
502
  }
486
503
  return deleted;
487
504
  }
@@ -513,7 +530,7 @@ export class ConcurrentMCPServer {
513
530
  use(middleware: Middleware): this {
514
531
  if (this.started) {
515
532
  throw new Error(
516
- "[ConcurrentMCPServer] Cannot add middleware after server started. " +
533
+ "[McpApp] Cannot add middleware after server started. " +
517
534
  "Call use() before start() or startHttp().",
518
535
  );
519
536
  }
@@ -593,7 +610,7 @@ export class ConcurrentMCPServer {
593
610
  ): Promise<MiddlewareResult> {
594
611
  if (!this.middlewareRunner) {
595
612
  throw new Error(
596
- "[ConcurrentMCPServer] Pipeline not built. Call start() or startHttp() first.",
613
+ "[McpApp] Pipeline not built. Call start() or startHttp() first.",
597
614
  );
598
615
  }
599
616
 
@@ -698,7 +715,7 @@ export class ConcurrentMCPServer {
698
715
  // Check for duplicate
699
716
  if (this.resources.has(resource.uri)) {
700
717
  throw new Error(
701
- `[ConcurrentMCPServer] Resource already registered: ${resource.uri}`,
718
+ `[McpApp] Resource already registered: ${resource.uri}`,
702
719
  );
703
720
  }
704
721
 
@@ -760,7 +777,7 @@ export class ConcurrentMCPServer {
760
777
 
761
778
  if (missingHandlers.length > 0) {
762
779
  throw new Error(
763
- `[ConcurrentMCPServer] Missing handlers for resources:\n` +
780
+ `[McpApp] Missing handlers for resources:\n` +
764
781
  missingHandlers.map((uri) => ` - ${uri}`).join("\n"),
765
782
  );
766
783
  }
@@ -775,7 +792,7 @@ export class ConcurrentMCPServer {
775
792
 
776
793
  if (duplicateUris.length > 0) {
777
794
  throw new Error(
778
- `[ConcurrentMCPServer] Resources already registered:\n` +
795
+ `[McpApp] Resources already registered:\n` +
779
796
  duplicateUris.map((uri) => ` - ${uri}`).join("\n"),
780
797
  );
781
798
  }
@@ -786,7 +803,7 @@ export class ConcurrentMCPServer {
786
803
  if (!handler) {
787
804
  // Should never happen after validation, but defensive check
788
805
  throw new Error(
789
- `[ConcurrentMCPServer] Handler disappeared for ${resource.uri}`,
806
+ `[McpApp] Handler disappeared for ${resource.uri}`,
790
807
  );
791
808
  }
792
809
  this.registerResource(resource, handler);
@@ -808,7 +825,9 @@ export class ConcurrentMCPServer {
808
825
  */
809
826
  registerViewers(config: RegisterViewersConfig): RegisterViewersSummary {
810
827
  if (!config.prefix) {
811
- throw new Error("[ConcurrentMCPServer] registerViewers: prefix is required");
828
+ throw new Error(
829
+ "[McpApp] registerViewers: prefix is required",
830
+ );
812
831
  }
813
832
 
814
833
  // Resolve viewer list: explicit or auto-discovered
@@ -826,7 +845,11 @@ export class ConcurrentMCPServer {
826
845
  const skipped: string[] = [];
827
846
 
828
847
  for (const viewerName of viewerNames) {
829
- const distPath = resolveViewerDistPath(config.moduleUrl, viewerName, config.exists);
848
+ const distPath = resolveViewerDistPath(
849
+ config.moduleUrl,
850
+ viewerName,
851
+ config.exists,
852
+ );
830
853
 
831
854
  if (!distPath) {
832
855
  this.log(
@@ -856,7 +879,9 @@ export class ConcurrentMCPServer {
856
879
  text: html,
857
880
  };
858
881
  if (config.csp) {
859
- (content as unknown as Record<string, unknown>)._meta = { ui: { csp: config.csp } };
882
+ (content as unknown as Record<string, unknown>)._meta = {
883
+ ui: { csp: config.csp },
884
+ };
860
885
  }
861
886
  return content;
862
887
  },
@@ -866,7 +891,9 @@ export class ConcurrentMCPServer {
866
891
  }
867
892
 
868
893
  if (registered.length > 0) {
869
- this.log(`Registered ${registered.length} viewer(s): ${registered.join(", ")}`);
894
+ this.log(
895
+ `Registered ${registered.length} viewer(s): ${registered.join(", ")}`,
896
+ );
870
897
  }
871
898
 
872
899
  return { registered, skipped };
@@ -911,8 +938,8 @@ export class ConcurrentMCPServer {
911
938
  */
912
939
  private cleanupSessions(): void {
913
940
  const now = Date.now();
914
- const ttlWithGrace = ConcurrentMCPServer.SESSION_TTL_MS +
915
- ConcurrentMCPServer.SESSION_GRACE_PERIOD_MS;
941
+ const ttlWithGrace = McpApp.SESSION_TTL_MS +
942
+ McpApp.SESSION_GRACE_PERIOD_MS;
916
943
  let cleaned = 0;
917
944
  for (const [sessionId, session] of this.sessions) {
918
945
  if (now - session.lastActivity > ttlWithGrace) {
@@ -998,7 +1025,7 @@ export class ConcurrentMCPServer {
998
1025
  *
999
1026
  * @example
1000
1027
  * ```typescript
1001
- * const server = new ConcurrentMCPServer({ name: "my-server", version: "1.0.0" });
1028
+ * const server = new McpApp({ name: "my-server", version: "1.0.0" });
1002
1029
  * server.registerTools(tools, handlers);
1003
1030
  * server.registerResource(resource, handler);
1004
1031
  *
@@ -1038,7 +1065,7 @@ export class ConcurrentMCPServer {
1038
1065
  const requireAuth = options.requireAuth ?? false;
1039
1066
  if (requireAuth && !this.authProvider) {
1040
1067
  throw new Error(
1041
- "[ConcurrentMCPServer] HTTP auth is required (requireAuth=true) but no auth provider is configured.",
1068
+ "[McpApp] HTTP auth is required (requireAuth=true) but no auth provider is configured.",
1042
1069
  );
1043
1070
  }
1044
1071
  if (!this.authProvider && !requireAuth) {
@@ -1411,9 +1438,9 @@ export class ConcurrentMCPServer {
1411
1438
  }
1412
1439
 
1413
1440
  // Guard against session exhaustion
1414
- if (this.sessions.size >= ConcurrentMCPServer.MAX_SESSIONS) {
1441
+ if (this.sessions.size >= McpApp.MAX_SESSIONS) {
1415
1442
  this.cleanupSessions();
1416
- if (this.sessions.size >= ConcurrentMCPServer.MAX_SESSIONS) {
1443
+ if (this.sessions.size >= McpApp.MAX_SESSIONS) {
1417
1444
  return c.json({
1418
1445
  jsonrpc: "2.0",
1419
1446
  id,
@@ -1442,7 +1469,9 @@ export class ConcurrentMCPServer {
1442
1469
  name: this.options.name,
1443
1470
  version: this.options.version,
1444
1471
  },
1445
- ...(this.options.instructions ? { instructions: this.options.instructions } : {}),
1472
+ ...(this.options.instructions
1473
+ ? { instructions: this.options.instructions }
1474
+ : {}),
1446
1475
  },
1447
1476
  }),
1448
1477
  {
@@ -1513,7 +1542,9 @@ export class ConcurrentMCPServer {
1513
1542
  } catch (rethrown) {
1514
1543
  this.log(
1515
1544
  `Error executing tool ${toolName}: ${
1516
- rethrown instanceof Error ? rethrown.message : String(rethrown)
1545
+ rethrown instanceof Error
1546
+ ? rethrown.message
1547
+ : String(rethrown)
1517
1548
  }`,
1518
1549
  );
1519
1550
  const errorMessage = rethrown instanceof Error
@@ -1663,6 +1694,37 @@ export class ConcurrentMCPServer {
1663
1694
  // deno-lint-ignore no-explicit-any
1664
1695
  app.post("/", handleMcpPost as any);
1665
1696
 
1697
+ // Embedded mode: skip serve(), surface the Hono fetch handler to the
1698
+ // caller and let them mount it inside their own framework (Fresh, Hono,
1699
+ // Express, etc.). The session cleanup timer + post-init still runs so
1700
+ // SSE clients and sessions are managed identically to the serve() path.
1701
+ if (options.embedded) {
1702
+ if (!options.embeddedHandlerCallback) {
1703
+ throw new Error(
1704
+ "[McpApp] embedded=true requires embeddedHandlerCallback",
1705
+ );
1706
+ }
1707
+ // deno-lint-ignore no-explicit-any
1708
+ options.embeddedHandlerCallback(app.fetch as any);
1709
+ this.started = true;
1710
+ this.sessionCleanupTimer = setInterval(
1711
+ () => this.cleanupSessions(),
1712
+ McpApp.SESSION_CLEANUP_INTERVAL_MS,
1713
+ );
1714
+ unrefTimer(this.sessionCleanupTimer as unknown as number);
1715
+ this.log(
1716
+ `HTTP handler ready (embedded mode — no port bound, max concurrent: ${
1717
+ this.options.maxConcurrent ?? 10
1718
+ })`,
1719
+ );
1720
+ return {
1721
+ shutdown: async () => {
1722
+ await this.stop();
1723
+ },
1724
+ addr: { hostname: "embedded", port: 0 },
1725
+ };
1726
+ }
1727
+
1666
1728
  // Start server
1667
1729
  this.httpServer = serve(
1668
1730
  {
@@ -1683,7 +1745,7 @@ export class ConcurrentMCPServer {
1683
1745
  // Start session cleanup timer (prevents unbounded memory growth)
1684
1746
  this.sessionCleanupTimer = setInterval(
1685
1747
  () => this.cleanupSessions(),
1686
- ConcurrentMCPServer.SESSION_CLEANUP_INTERVAL_MS,
1748
+ McpApp.SESSION_CLEANUP_INTERVAL_MS,
1687
1749
  );
1688
1750
  // Don't block Deno from exiting because of cleanup timer
1689
1751
  unrefTimer(this.sessionCleanupTimer as unknown as number);
@@ -1714,6 +1776,67 @@ export class ConcurrentMCPServer {
1714
1776
  };
1715
1777
  }
1716
1778
 
1779
+ /**
1780
+ * Build the HTTP middleware stack and return its fetch handler without
1781
+ * binding a port. Use this when you want to mount the MCP HTTP layer
1782
+ * inside another HTTP framework (Fresh, Hono, Express, Cloudflare Workers,
1783
+ * etc.) instead of giving up port ownership to {@link startHttp}.
1784
+ *
1785
+ * The returned handler accepts a Web Standard {@link Request} and returns
1786
+ * a Web Standard {@link Response}. It exposes the same routes as
1787
+ * {@link startHttp}: `POST /mcp`, `GET /mcp` (SSE), `GET /health`,
1788
+ * `GET /metrics`, and `GET /.well-known/oauth-protected-resource`.
1789
+ *
1790
+ * Auth, multi-tenant middleware, scope checks, and rate limiting are all
1791
+ * applied identically. The session cleanup timer and OTel hooks are
1792
+ * started, so the server is fully live after this returns — just without
1793
+ * its own listening socket.
1794
+ *
1795
+ * Multi-tenant SaaS pattern: cache one `McpApp` per tenant
1796
+ * and call `getFetchHandler()` once per server, then dispatch each
1797
+ * inbound request to the right cached handler from your framework's
1798
+ * routing layer.
1799
+ *
1800
+ * @example
1801
+ * ```typescript
1802
+ * // In a Fresh route at routes/mcp/[...path].tsx
1803
+ * const server = new McpApp({ name: "my-mcp", version: "1.0.0" });
1804
+ * server.registerTools(tools, handlers);
1805
+ * const handler = await server.getFetchHandler({
1806
+ * requireAuth: true,
1807
+ * auth: { provider: myAuthProvider },
1808
+ * });
1809
+ * // Later, in your route handler:
1810
+ * return await handler(ctx.req);
1811
+ * ```
1812
+ *
1813
+ * @param options - Same as {@link startHttp}, minus `port`/`hostname`/`onListen`.
1814
+ * @returns A Web Standard fetch handler.
1815
+ */
1816
+ async getFetchHandler(
1817
+ options: Omit<HttpServerOptions, "port" | "hostname" | "onListen"> = {},
1818
+ ): Promise<FetchHandler> {
1819
+ let captured: FetchHandler | null = null;
1820
+ await this.startHttp({
1821
+ // port/hostname are unused in embedded mode but the type requires them.
1822
+ // Pass sentinel values that would never bind even if used.
1823
+ port: 0,
1824
+ ...options,
1825
+ embedded: true,
1826
+ embeddedHandlerCallback: (handler) => {
1827
+ captured = handler;
1828
+ },
1829
+ });
1830
+ if (!captured) {
1831
+ // Defensive: startHttp should always invoke the callback synchronously
1832
+ // before returning. If it didn't, something is structurally wrong.
1833
+ throw new Error(
1834
+ "[McpApp] getFetchHandler: embedded callback was not invoked",
1835
+ );
1836
+ }
1837
+ return captured;
1838
+ }
1839
+
1717
1840
  /**
1718
1841
  * Send a JSON-RPC message to all SSE clients in a session
1719
1842
  * Used for server-initiated notifications and requests
@@ -1790,6 +1913,43 @@ export class ConcurrentMCPServer {
1790
1913
  return this.samplingBridge;
1791
1914
  }
1792
1915
 
1916
+ /**
1917
+ * Read the MCP Apps capability advertised by the connected client.
1918
+ *
1919
+ * Returns the capability object (possibly empty `{}`) when the client
1920
+ * advertised support for MCP Apps via its `extensions` capability,
1921
+ * or `undefined` when:
1922
+ * - the client did not send capabilities yet (called before initialize)
1923
+ * - the client did not advertise the MCP Apps extension at all
1924
+ * - the client sent a malformed extension value
1925
+ *
1926
+ * Use this from a tool handler to decide whether to return a UI
1927
+ * resource (`_meta.ui`) or a text-only fallback. Hosts that don't
1928
+ * support MCP Apps will silently drop the `_meta.ui` field, but
1929
+ * checking explicitly lets you serve a richer text response when
1930
+ * the UI path isn't available.
1931
+ *
1932
+ * @returns MCP Apps capability or `undefined` if not supported.
1933
+ *
1934
+ * @example
1935
+ * ```typescript
1936
+ * const cap = app.getClientMcpAppsCapability();
1937
+ * if (cap?.mimeTypes?.includes(MCP_APP_MIME_TYPE)) {
1938
+ * return {
1939
+ * content: [{ type: "text", text: summary }],
1940
+ * _meta: { ui: { resourceUri: "ui://my-app/dashboard" } },
1941
+ * };
1942
+ * }
1943
+ * return { content: [{ type: "text", text: detailedTextFallback }] };
1944
+ * ```
1945
+ *
1946
+ * @see {@link getMcpAppsCapability} for the standalone reader
1947
+ * @see {@link MCP_APPS_EXTENSION_ID} for the extension key
1948
+ */
1949
+ getClientMcpAppsCapability(): McpAppsClientCapability | undefined {
1950
+ return getMcpAppsCapability(this.mcpServer.server.getClientCapabilities());
1951
+ }
1952
+
1793
1953
  /**
1794
1954
  * Get queue metrics for monitoring
1795
1955
  */
@@ -1948,7 +2108,12 @@ export class ConcurrentMCPServer {
1948
2108
  * without re-wrapping. This supports proxy/gateway patterns.
1949
2109
  */
1950
2110
  // deno-lint-ignore no-explicit-any
1951
- private isPreformattedResult(result: unknown): result is { content: Array<{ type: string; text: string }>; _meta?: Record<string, unknown> } {
2111
+ private isPreformattedResult(
2112
+ result: unknown,
2113
+ ): result is {
2114
+ content: Array<{ type: string; text: string }>;
2115
+ _meta?: Record<string, unknown>;
2116
+ } {
1952
2117
  if (!result || typeof result !== "object") return false;
1953
2118
  const obj = result as Record<string, unknown>;
1954
2119
  return Array.isArray(obj.content) &&
@@ -2055,7 +2220,9 @@ export class ConcurrentMCPServer {
2055
2220
  } catch (mapperError) {
2056
2221
  this.log(
2057
2222
  `toolErrorMapper threw for tool ${toolName}: ${
2058
- mapperError instanceof Error ? mapperError.message : String(mapperError)
2223
+ mapperError instanceof Error
2224
+ ? mapperError.message
2225
+ : String(mapperError)
2059
2226
  } (original error: ${
2060
2227
  error instanceof Error ? error.message : String(error)
2061
2228
  })`,
@@ -2114,7 +2281,11 @@ export interface RegisterViewersConfig {
2114
2281
  humanName?: (viewerName: string) => string;
2115
2282
  /** MCP Apps CSP — declares external domains the viewer needs (tiles, APIs, CDNs).
2116
2283
  * Uses McpUiCsp from @casys/mcp-compose (resourceDomains, connectDomains). */
2117
- csp?: { resourceDomains?: string[]; connectDomains?: string[]; frameDomains?: string[] };
2284
+ csp?: {
2285
+ resourceDomains?: string[];
2286
+ connectDomains?: string[];
2287
+ frameDomains?: string[];
2288
+ };
2118
2289
  }
2119
2290
 
2120
2291
  /** Summary returned by registerViewers() */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Middleware module for ConcurrentMCPServer.
2
+ * Middleware module for McpApp.
3
3
  *
4
4
  * @module lib/server/middleware
5
5
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Rate limiting middleware.
3
3
  *
4
- * Extracted from ConcurrentMCPServer's inline rate limit logic.
4
+ * Extracted from McpApp's inline rate limit logic.
5
5
  *
6
6
  * @module lib/server/middleware/rate-limit
7
7
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Middleware pipeline types for ConcurrentMCPServer.
2
+ * Middleware pipeline types for McpApp.
3
3
  *
4
4
  * Provides an onion-model middleware system (similar to Koa/Hono)
5
5
  * where each middleware wraps the next, enabling before/after logic.
@@ -2,7 +2,7 @@
2
2
  * Server Metrics Collector for @casys/mcp-server
3
3
  *
4
4
  * In-memory counters, histograms, and gauges with Prometheus text format export.
5
- * Designed to be embedded in ConcurrentMCPServer — no external dependencies.
5
+ * Designed to be embedded in McpApp — no external dependencies.
6
6
  *
7
7
  * @module lib/server/observability/metrics
8
8
  */