@webpieces/mcp-server 0.4.789 → 0.4.790

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
@@ -120,15 +120,31 @@ mcp.bind(expressApp, new McpBindOptions(
120
120
  ));
121
121
  ```
122
122
 
123
- `bind(...)` registers POST at that path plus a 405 (`Allow: POST`) for every other method there — MCP
124
- 2026-07-28 has no session GET, so a probe gets a method error rather than a 404. It may be called only
123
+ `bind(...)` registers POST at that path plus a 405 (`Allow: POST`) for every other method there —
124
+ neither served era has a session GET here (2026-07-28 has none at all, and the 2025 leg is the SDK's
125
+ STATELESS fallback, which answers the 2025 session GET/DELETE with the same 405), so a probe gets a
126
+ method error rather than a 404. It may be called only
125
127
  once: one server serves exactly one canonical resource URI. `WpMcpServerConfig.resource` is that URI,
126
128
  fixed for the process lifetime and never derived from the request `Host` (a host-derived audience would
127
129
  make the boundary's own audience check circular — a confused deputy), and its path must equal
128
130
  `endpointPath`, which `bind(...)` verifies. A second hostname is a second deployment, not a second
129
131
  audience.
130
132
 
131
- The official MCP v2 server and Node adapter own MCP 2026-07-28 JSON-RPC validation, JSON versus
133
+ ## Protocol revisions: negotiated, never pinned
134
+
135
+ One endpoint serves BOTH MCP wire eras from one tool registry — modern (2026-07-28, per-request
136
+ `_meta` envelope) and legacy (the 2025-11-25 family, negotiated by `initialize`). The official SDK
137
+ classifies each request and answers it on the matching leg, calling the SAME server factory for both,
138
+ so the two eras can never drift apart. There is no configuration for this and no way to turn it off:
139
+ a revision an app can pin is a revision an app can pin itself out of reach with, which is exactly what
140
+ happened when this bridge was modern-only and every shipping client (all still on the 2025
141
+ generation) got `-32022 Unsupported protocol version` at `initialize`. Only a revision NO era knows is
142
+ refused, which is what the MCP lifecycle spec asks of a server.
143
+
144
+ The external bearer is verified identically on both legs, at the `bind(...)` HTTP boundary, before the
145
+ SDK is involved — the era is the SDK's decision, the authentication is not.
146
+
147
+ The official MCP v2 server and Node adapter own JSON-RPC validation for both eras, JSON versus
132
148
  request-scoped SSE responses, backpressure, cancellation, keepalives, and subscriptions. Webpieces
133
149
  owns the Express path, root `RequestContext`, authentication boundary, schema-derived tool registry,
134
150
  and dispatch through the normal API proxy.
@@ -154,7 +170,7 @@ level-triggered invalidation, and `close()` drains active response streams. A re
154
170
  re-listen and refresh its authoritative lists; notifications are not a durable or replayable event log.
155
171
 
156
172
  The authority verifies issuer, signature/token state, expiry, scopes, the exact resource URI, and
157
- current account state. MCP 2026-07-28 has no sessions, so the bearer is verified exactly once per POST
173
+ current account state. Neither served era keeps a session here, so the bearer is verified once per POST
158
174
  at the `bind(...)` HTTP boundary, before the SDK is involved; tool handlers never re-verify it. A
159
175
  rejected token must be thrown as `ApiUnauthorizedError` (answered 401 + `WWW-Authenticate`); any
160
176
  other throw from the authority is an implementation failure (500). Endpoint JWTs are capped at one
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/mcp-server",
3
- "version": "0.4.789",
3
+ "version": "0.4.790",
4
4
  "description": "Secure MCP tool generation from annotated Webpieces API contracts",
5
5
  "type": "commonjs",
6
6
  "main": "./src/index.js",
@@ -24,15 +24,15 @@
24
24
  "dependencies": {
25
25
  "@modelcontextprotocol/node": "2.0.0",
26
26
  "@modelcontextprotocol/server": "2.0.0",
27
- "@webpieces/core-context": "0.4.789",
28
- "@webpieces/core-util": "0.4.789",
29
- "@webpieces/http-routing": "0.4.789",
30
- "@webpieces/http-server": "0.4.789",
27
+ "@webpieces/core-context": "0.4.790",
28
+ "@webpieces/core-util": "0.4.790",
29
+ "@webpieces/http-routing": "0.4.790",
30
+ "@webpieces/http-server": "0.4.790",
31
31
  "express": "5.1.0",
32
32
  "reflect-metadata": "0.2.2",
33
33
  "tslib": "2.8.1"
34
34
  },
35
35
  "devDependencies": {
36
- "@webpieces/http-client-node": "0.4.789"
36
+ "@webpieces/http-client-node": "0.4.790"
37
37
  }
38
38
  }
@@ -137,7 +137,7 @@ export declare class WpMcpErrorTranslator {
137
137
  * error's operator text to the caller.
138
138
  */
139
139
  toListError(error: Error): never;
140
- /** tools/call with a name no binding registered: a JSON-RPC protocol error per MCP 2026-07-28. */
140
+ /** tools/call with a name no binding registered: a JSON-RPC protocol error in either MCP era. */
141
141
  unknownTool(toolName: string): ProtocolError;
142
142
  /**
143
143
  * Boundary #3: every tools/call failure after the tool is found — an `isError: true` result the
@@ -206,7 +206,7 @@ class WpMcpErrorTranslator {
206
206
  const message = payload.kind === 'end-user' ? payload.message : 'Internal Error';
207
207
  throw new server_1.ProtocolError(server_1.ProtocolErrorCode.InternalError, message, new McpErrorData(requestId));
208
208
  }
209
- /** tools/call with a name no binding registered: a JSON-RPC protocol error per MCP 2026-07-28. */
209
+ /** tools/call with a name no binding registered: a JSON-RPC protocol error in either MCP era. */
210
210
  unknownTool(toolName) {
211
211
  const requestId = McpCorrelation.requestId();
212
212
  log.info(`Unknown MCP tool: ${toolName} requestId=${requestId}`);
@@ -1 +1 @@
1
- {"version":3,"file":"WpMcpErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpErrorTranslator.ts"],"names":[],"mappings":"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","sourcesContent":["import { CallToolResult, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';\nimport {\n ApiErrorBoundary,\n ApiErrorPayload,\n ContextKey,\n DtoValue,\n HttpHeader,\n HttpResponseDto,\n HttpResponseStatus,\n LogManager,\n toError,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\n\nconst log = LogManager.getLogger('WpMcpErrorTranslator');\n\n/** `_meta` key carrying the Webpieces requestId on every tools/call result, success or failure. */\nexport const MCP_REQUEST_ID_META_KEY = 'webpieces/requestId';\n\n/** JSON-RPC error `data` so a user can quote the requestId of any protocol-level failure. */\nexport class McpErrorData {\n constructor(public readonly requestId: string) {}\n}\n\n/**\n * The two per-request facts a renderer needs that are NOT the error: the JSON-RPC `id` it must echo,\n * and the tool being called. They are stamped into the request scope by `WpMcpServer` rather than\n * threaded through every renderer signature, because `requestId` already travels that way\n * ({@link WebpiecesCoreHeaders.REQUEST_ID}) and a second, parallel correlation parameter was a COPY\n * of the context — which is what the deleted `McpFailureScope` was.\n *\n * Context-only and never logged: the logging backends read `RequestContext` for correlation on every\n * record already, and this object exists purely so the renderers can be one-argument functions.\n */\nexport class McpCorrelation {\n // webpieces-disable no-any-unknown -- ContextKey value types are per-key; this one is context-only\n static readonly KEY = ContextKey.untrusted<McpCorrelation>(\n 'webpieces-mcp-correlation',\n undefined,\n false,\n /*isLogged*/ false,\n );\n\n constructor(\n public readonly jsonRpcId: string | number | null,\n public readonly toolName?: string,\n ) {}\n\n /** Stamp what this request knows so far; `WpMcpServer` re-stamps once the tool name is known. */\n // webpieces-disable no-function-outside-class -- static accessor trio for one context key\n static stamp(correlation: McpCorrelation): void {\n RequestContext.putUntrusted(McpCorrelation.KEY, correlation);\n }\n\n /** A renderer reached before anything stamped (a failure with no parseable body) still renders. */\n // webpieces-disable no-function-outside-class -- static accessor trio for one context key\n static current(): McpCorrelation {\n return RequestContext.getUntrusted(McpCorrelation.KEY) ?? new McpCorrelation(null);\n }\n\n /** The webpieces requestId of the request being served, as every MCP reply reports it. */\n // webpieces-disable no-function-outside-class -- static accessor trio for one context key\n static requestId(): string {\n return RequestContext.getUntrusted(WebpiecesCoreHeaders.REQUEST_ID) ?? 'missing-request-id';\n }\n}\n\n/** The only error shape rendered into model-visible MCP tool content. */\nexport class ModelVisibleToolError {\n constructor(\n public readonly kind: string,\n public readonly message: string,\n public readonly requestId: string,\n public readonly field?: string,\n public readonly callerMessage?: string,\n public readonly errorCode?: string,\n public readonly retryAfterSeconds?: number,\n public readonly statusCode?: number,\n ) {}\n}\n\n/** JSON-RPC error body written by the HTTP boundary before the SDK is involved. */\nexport class McpHttpErrorBody {\n readonly jsonrpc = '2.0';\n constructor(\n public readonly error: McpHttpErrorDetail,\n public readonly id: string | number | null,\n ) {}\n}\n\nexport class McpHttpErrorDetail {\n constructor(\n public readonly code: number,\n public readonly message: string,\n public readonly data: McpErrorData,\n ) {}\n}\n\n/**\n * The application's own `tools/call` error translation — the MCP twin of the `ErrorTranslators` an\n * app registers on `ClientRegistry` for the HTTP path. Register one with\n * `WpMcpServerConfig.setErrorTranslator(...)`; it is NOT a global, because `WpMcpServer` is\n * constructed by app code and two servers may run in one process.\n *\n * An app owns its error taxonomy and owns how those errors should be explained to a model, so a\n * claimed error yields the ENTIRE `CallToolResult` — content, `structuredContent`, `isError`, the lot.\n *\n * Contract:\n * - `error` is the RAW thrown value: an app must be able to `instanceof` its own error classes.\n * `ApiErrorBoundary` never substitutes an object for the thrown error, so there is exactly one\n * error value on this path.\n * - There is no \"not mine\" return. A registered translator REPLACES the webpieces default and\n * declines by DELEGATING to it: `new McpDefaultToolCallRenderer().toToolCallResult(error)`.\n * - webpieces default-fills `_meta['webpieces/requestId']` only when the returned result has NO\n * `_meta`. An app that sets `_meta` owns it untouched.\n * - scope is `tools/call` ONLY. The pre-SDK HTTP boundary and `tools/list` stay framework-owned:\n * that boundary emits the `401 + WWW-Authenticate: Bearer resource_metadata=...` MCP clients\n * depend on for OAuth discovery, and an app rewriting it breaks connector onboarding.\n */\nexport interface McpErrorTranslators {\n toToolCallResult(error: Error): CallToolResult;\n}\n\n/**\n * webpieces' DEFAULT `tools/call` rendering, as a public class so an app's own\n * {@link McpErrorTranslators} can DECLINE an error by delegating to it — the MCP twin of\n * `ApiErrorHttpMapper.toResponse` on the HTTP side.\n *\n * Published text comes only from {@link ApiErrorBoundary.encode}: this never puts an error's own\n * message in front of a model unless it is an `ApiEndUserError`.\n */\nexport class McpDefaultToolCallRenderer implements McpErrorTranslators {\n private readonly boundary = new ApiErrorBoundary();\n\n toToolCallResult(error: Error): CallToolResult {\n const requestId = McpCorrelation.requestId();\n const visible = this.modelVisible(this.boundary.encode(error), requestId);\n const text = JSON.stringify(visible as DtoValue);\n return {\n content: [{ type: 'text', text }],\n isError: true,\n _meta: { [MCP_REQUEST_ID_META_KEY]: requestId },\n };\n }\n\n private modelVisible(payload: ApiErrorPayload, requestId: string): ModelVisibleToolError {\n switch (payload.kind) {\n case 'end-user':\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n requestId,\n undefined,\n undefined,\n payload.errorCode,\n );\n case 'bad-request':\n return new ModelVisibleToolError(\n payload.kind,\n payload.callerMessage ?? payload.message,\n requestId,\n payload.field,\n payload.callerMessage,\n );\n case 'coded':\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n requestId,\n undefined,\n undefined,\n payload.errorCode,\n undefined,\n payload.statusCode,\n );\n case 'implementation':\n return new ModelVisibleToolError(\n payload.kind,\n `Internal error in tool ${McpCorrelation.current().toolName ?? 'unknown'} ` +\n `(requestId ${requestId}). This is a bug in the tool, not in your ` +\n 'arguments; retrying with different arguments will not help.',\n requestId,\n );\n default:\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n requestId,\n undefined,\n undefined,\n undefined,\n payload.retryAfterSeconds,\n );\n }\n }\n}\n\n/**\n * The ONE place every MCP failure becomes a reply, mirroring `ApiErrorHttpMapper` for HTTP: an error\n * in, the exact wire shape out. There is one method per BOUNDARY, and the MCP spec is what makes\n * them three rather than one:\n *\n * | Boundary | Who writes HTTP | Failure shape |\n * |--------------------------------|-----------------|-------------------------------------|\n * | pre-SDK bearer / Origin / body | **webpieces** | `HttpResponseDto<McpHttpErrorBody>` |\n * | `tools/list` | the SDK | `throw ProtocolError` |\n * | `tools/call` | the SDK | `CallToolResult` with `isError` |\n *\n * Classification and published text are the shared {@link ApiErrorBoundary} rules. Operator logging\n * is NOT here: `LogApiCall` wraps each of those boundaries in `WpMcpServer`, so every failure gets\n * exactly one `[API-server-resp-*]` line with the request and the identity on it.\n */\nexport class WpMcpErrorTranslator {\n private readonly boundary = new ApiErrorBoundary();\n private readonly defaultToolCall = new McpDefaultToolCallRenderer();\n\n /**\n * @param challenge - the `WWW-Authenticate` value for a 401, read lazily because\n * `WpMcpServerConfig` is only fully validated at `bind(...)` time.\n * @param appTranslators - the app's `tools/call` seam; `undefined` means webpieces renders all.\n */\n constructor(\n private readonly challenge: () => string,\n private readonly appTranslators?: McpErrorTranslators,\n ) {}\n\n /**\n * Boundary #1: everything webpieces itself rejects before the SDK is involved — bearer\n * verification, `Origin`, a malformed body. This is 401/403/400/500 today and any status\n * tomorrow, so it produces a VALUE like every other API in the framework and `WpMcpServer` hands\n * it to the shared express writer. The BODY stays JSON-RPC shaped because an MCP client expects\n * that; `HttpResponseDto` is generic, and its header LIST carries `WWW-Authenticate`.\n */\n toBearerBoundaryResponse(error: Error): HttpResponseDto<McpHttpErrorBody> {\n const payload = this.boundary.encode(error);\n const generic = payload.message;\n switch (payload.kind) {\n case 'unauthorized':\n return this.httpError(401, -32_000, generic, [\n new HttpHeader('WWW-Authenticate', this.challenge()),\n ]);\n case 'forbidden':\n return this.httpError(403, -32_000, generic, []);\n case 'bad-request':\n return this.httpError(400, ProtocolErrorCode.ParseError, generic, []);\n default:\n return this.httpError(500, ProtocolErrorCode.InternalError, 'Internal Error', []);\n }\n }\n\n /**\n * Boundary #2: `tools/list` has no tool-result channel, so a failure IS a JSON-RPC error. It\n * THROWS rather than returning a value the caller must remember to throw, and is typed `never`\n * so the compiler knows the call is terminal.\n *\n * This is LOAD-BEARING for disclosure, not defence in depth. Measured on\n * `@modelcontextprotocol/server` 2.0.0 and pinned by `McpSdkForeignThrow.spec.ts`: the SDK wraps\n * a foreign (non-`ProtocolError`) throw in `-32603` but copies its `message` onto the wire\n * VERBATIM. So a `tools/list` handler that lets any error escape un-translated publishes that\n * error's operator text to the caller.\n */\n toListError(error: Error): never {\n const payload = this.boundary.encode(error);\n const requestId = McpCorrelation.requestId();\n if (payload.kind === 'bad-request') {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n payload.callerMessage ?? payload.message,\n new McpErrorData(requestId),\n );\n }\n const message = payload.kind === 'end-user' ? payload.message : 'Internal Error';\n throw new ProtocolError(\n ProtocolErrorCode.InternalError,\n message,\n new McpErrorData(requestId),\n );\n }\n\n /** tools/call with a name no binding registered: a JSON-RPC protocol error per MCP 2026-07-28. */\n unknownTool(toolName: string): ProtocolError {\n const requestId = McpCorrelation.requestId();\n log.info(`Unknown MCP tool: ${toolName} requestId=${requestId}`);\n return new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n `Unknown tool: ${toolName}`,\n new McpErrorData(requestId),\n );\n }\n\n /**\n * Boundary #3: every tools/call failure after the tool is found — an `isError: true` result the\n * model sees.\n *\n * A registered {@link McpErrorTranslators} REPLACES the default and declines by delegating to\n * {@link McpDefaultToolCallRenderer}, so \"no translator registered\" and \"a translator that does\n * not claim this error\" produce byte-identical results.\n */\n toToolCallResult(failure: Error): CallToolResult {\n const renderer = this.appTranslators;\n if (!renderer) return this.defaultToolCall.toToolCallResult(failure);\n return this.withDefaultMeta(this.appOrDefault(renderer, failure));\n }\n\n /** The `_meta` every tools/call result carries so a user can quote its requestId. */\n resultMeta(requestId: string): Record<string, string> {\n return { [MCP_REQUEST_ID_META_KEY]: requestId };\n }\n\n /**\n * An app translator that THROWS must not replace the failure being reported: its own bug is\n * logged here and the ORIGINAL error still renders through the webpieces default, so the model\n * still gets a reply carrying the requestId.\n */\n private appOrDefault(renderer: McpErrorTranslators, failure: Error): CallToolResult {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an app translator's own bug must not replace the failure it was asked to render\n try {\n return renderer.toToolCallResult(failure);\n } catch (err: unknown) {\n const error = toError(err);\n log.error(\n 'Application McpErrorTranslators.toToolCallResult threw; rendering the webpieces default.',\n error,\n );\n return this.defaultToolCall.toToolCallResult(failure);\n }\n }\n\n /**\n * webpieces fills `_meta` only when the app left it off, keeping the README's \"every reply\n * carries the requestId\" promise a DEFAULT rather than a restriction: an app that set `_meta`\n * keeps it byte-for-byte.\n */\n private withDefaultMeta(result: CallToolResult): CallToolResult {\n if (result._meta !== undefined) return result;\n return { ...result, _meta: this.resultMeta(McpCorrelation.requestId()) };\n }\n\n private httpError(\n status: number,\n code: number,\n message: string,\n headers: HttpHeader[],\n ): HttpResponseDto<McpHttpErrorBody> {\n const correlation = McpCorrelation.current();\n const requestId = McpCorrelation.requestId();\n return new HttpResponseDto<McpHttpErrorBody>(\n new HttpResponseStatus(status, message),\n headers,\n new McpHttpErrorBody(\n new McpHttpErrorDetail(code, message, new McpErrorData(requestId)),\n correlation.jsonRpcId,\n ),\n );\n }\n}\n"]}
1
+ {"version":3,"file":"WpMcpErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpErrorTranslator.ts"],"names":[],"mappings":"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","sourcesContent":["import { CallToolResult, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';\nimport {\n ApiErrorBoundary,\n ApiErrorPayload,\n ContextKey,\n DtoValue,\n HttpHeader,\n HttpResponseDto,\n HttpResponseStatus,\n LogManager,\n toError,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\n\nconst log = LogManager.getLogger('WpMcpErrorTranslator');\n\n/** `_meta` key carrying the Webpieces requestId on every tools/call result, success or failure. */\nexport const MCP_REQUEST_ID_META_KEY = 'webpieces/requestId';\n\n/** JSON-RPC error `data` so a user can quote the requestId of any protocol-level failure. */\nexport class McpErrorData {\n constructor(public readonly requestId: string) {}\n}\n\n/**\n * The two per-request facts a renderer needs that are NOT the error: the JSON-RPC `id` it must echo,\n * and the tool being called. They are stamped into the request scope by `WpMcpServer` rather than\n * threaded through every renderer signature, because `requestId` already travels that way\n * ({@link WebpiecesCoreHeaders.REQUEST_ID}) and a second, parallel correlation parameter was a COPY\n * of the context — which is what the deleted `McpFailureScope` was.\n *\n * Context-only and never logged: the logging backends read `RequestContext` for correlation on every\n * record already, and this object exists purely so the renderers can be one-argument functions.\n */\nexport class McpCorrelation {\n // webpieces-disable no-any-unknown -- ContextKey value types are per-key; this one is context-only\n static readonly KEY = ContextKey.untrusted<McpCorrelation>(\n 'webpieces-mcp-correlation',\n undefined,\n false,\n /*isLogged*/ false,\n );\n\n constructor(\n public readonly jsonRpcId: string | number | null,\n public readonly toolName?: string,\n ) {}\n\n /** Stamp what this request knows so far; `WpMcpServer` re-stamps once the tool name is known. */\n // webpieces-disable no-function-outside-class -- static accessor trio for one context key\n static stamp(correlation: McpCorrelation): void {\n RequestContext.putUntrusted(McpCorrelation.KEY, correlation);\n }\n\n /** A renderer reached before anything stamped (a failure with no parseable body) still renders. */\n // webpieces-disable no-function-outside-class -- static accessor trio for one context key\n static current(): McpCorrelation {\n return RequestContext.getUntrusted(McpCorrelation.KEY) ?? new McpCorrelation(null);\n }\n\n /** The webpieces requestId of the request being served, as every MCP reply reports it. */\n // webpieces-disable no-function-outside-class -- static accessor trio for one context key\n static requestId(): string {\n return RequestContext.getUntrusted(WebpiecesCoreHeaders.REQUEST_ID) ?? 'missing-request-id';\n }\n}\n\n/** The only error shape rendered into model-visible MCP tool content. */\nexport class ModelVisibleToolError {\n constructor(\n public readonly kind: string,\n public readonly message: string,\n public readonly requestId: string,\n public readonly field?: string,\n public readonly callerMessage?: string,\n public readonly errorCode?: string,\n public readonly retryAfterSeconds?: number,\n public readonly statusCode?: number,\n ) {}\n}\n\n/** JSON-RPC error body written by the HTTP boundary before the SDK is involved. */\nexport class McpHttpErrorBody {\n readonly jsonrpc = '2.0';\n constructor(\n public readonly error: McpHttpErrorDetail,\n public readonly id: string | number | null,\n ) {}\n}\n\nexport class McpHttpErrorDetail {\n constructor(\n public readonly code: number,\n public readonly message: string,\n public readonly data: McpErrorData,\n ) {}\n}\n\n/**\n * The application's own `tools/call` error translation — the MCP twin of the `ErrorTranslators` an\n * app registers on `ClientRegistry` for the HTTP path. Register one with\n * `WpMcpServerConfig.setErrorTranslator(...)`; it is NOT a global, because `WpMcpServer` is\n * constructed by app code and two servers may run in one process.\n *\n * An app owns its error taxonomy and owns how those errors should be explained to a model, so a\n * claimed error yields the ENTIRE `CallToolResult` — content, `structuredContent`, `isError`, the lot.\n *\n * Contract:\n * - `error` is the RAW thrown value: an app must be able to `instanceof` its own error classes.\n * `ApiErrorBoundary` never substitutes an object for the thrown error, so there is exactly one\n * error value on this path.\n * - There is no \"not mine\" return. A registered translator REPLACES the webpieces default and\n * declines by DELEGATING to it: `new McpDefaultToolCallRenderer().toToolCallResult(error)`.\n * - webpieces default-fills `_meta['webpieces/requestId']` only when the returned result has NO\n * `_meta`. An app that sets `_meta` owns it untouched.\n * - scope is `tools/call` ONLY. The pre-SDK HTTP boundary and `tools/list` stay framework-owned:\n * that boundary emits the `401 + WWW-Authenticate: Bearer resource_metadata=...` MCP clients\n * depend on for OAuth discovery, and an app rewriting it breaks connector onboarding.\n */\nexport interface McpErrorTranslators {\n toToolCallResult(error: Error): CallToolResult;\n}\n\n/**\n * webpieces' DEFAULT `tools/call` rendering, as a public class so an app's own\n * {@link McpErrorTranslators} can DECLINE an error by delegating to it — the MCP twin of\n * `ApiErrorHttpMapper.toResponse` on the HTTP side.\n *\n * Published text comes only from {@link ApiErrorBoundary.encode}: this never puts an error's own\n * message in front of a model unless it is an `ApiEndUserError`.\n */\nexport class McpDefaultToolCallRenderer implements McpErrorTranslators {\n private readonly boundary = new ApiErrorBoundary();\n\n toToolCallResult(error: Error): CallToolResult {\n const requestId = McpCorrelation.requestId();\n const visible = this.modelVisible(this.boundary.encode(error), requestId);\n const text = JSON.stringify(visible as DtoValue);\n return {\n content: [{ type: 'text', text }],\n isError: true,\n _meta: { [MCP_REQUEST_ID_META_KEY]: requestId },\n };\n }\n\n private modelVisible(payload: ApiErrorPayload, requestId: string): ModelVisibleToolError {\n switch (payload.kind) {\n case 'end-user':\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n requestId,\n undefined,\n undefined,\n payload.errorCode,\n );\n case 'bad-request':\n return new ModelVisibleToolError(\n payload.kind,\n payload.callerMessage ?? payload.message,\n requestId,\n payload.field,\n payload.callerMessage,\n );\n case 'coded':\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n requestId,\n undefined,\n undefined,\n payload.errorCode,\n undefined,\n payload.statusCode,\n );\n case 'implementation':\n return new ModelVisibleToolError(\n payload.kind,\n `Internal error in tool ${McpCorrelation.current().toolName ?? 'unknown'} ` +\n `(requestId ${requestId}). This is a bug in the tool, not in your ` +\n 'arguments; retrying with different arguments will not help.',\n requestId,\n );\n default:\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n requestId,\n undefined,\n undefined,\n undefined,\n payload.retryAfterSeconds,\n );\n }\n }\n}\n\n/**\n * The ONE place every MCP failure becomes a reply, mirroring `ApiErrorHttpMapper` for HTTP: an error\n * in, the exact wire shape out. There is one method per BOUNDARY, and the MCP spec is what makes\n * them three rather than one:\n *\n * | Boundary | Who writes HTTP | Failure shape |\n * |--------------------------------|-----------------|-------------------------------------|\n * | pre-SDK bearer / Origin / body | **webpieces** | `HttpResponseDto<McpHttpErrorBody>` |\n * | `tools/list` | the SDK | `throw ProtocolError` |\n * | `tools/call` | the SDK | `CallToolResult` with `isError` |\n *\n * Classification and published text are the shared {@link ApiErrorBoundary} rules. Operator logging\n * is NOT here: `LogApiCall` wraps each of those boundaries in `WpMcpServer`, so every failure gets\n * exactly one `[API-server-resp-*]` line with the request and the identity on it.\n */\nexport class WpMcpErrorTranslator {\n private readonly boundary = new ApiErrorBoundary();\n private readonly defaultToolCall = new McpDefaultToolCallRenderer();\n\n /**\n * @param challenge - the `WWW-Authenticate` value for a 401, read lazily because\n * `WpMcpServerConfig` is only fully validated at `bind(...)` time.\n * @param appTranslators - the app's `tools/call` seam; `undefined` means webpieces renders all.\n */\n constructor(\n private readonly challenge: () => string,\n private readonly appTranslators?: McpErrorTranslators,\n ) {}\n\n /**\n * Boundary #1: everything webpieces itself rejects before the SDK is involved — bearer\n * verification, `Origin`, a malformed body. This is 401/403/400/500 today and any status\n * tomorrow, so it produces a VALUE like every other API in the framework and `WpMcpServer` hands\n * it to the shared express writer. The BODY stays JSON-RPC shaped because an MCP client expects\n * that; `HttpResponseDto` is generic, and its header LIST carries `WWW-Authenticate`.\n */\n toBearerBoundaryResponse(error: Error): HttpResponseDto<McpHttpErrorBody> {\n const payload = this.boundary.encode(error);\n const generic = payload.message;\n switch (payload.kind) {\n case 'unauthorized':\n return this.httpError(401, -32_000, generic, [\n new HttpHeader('WWW-Authenticate', this.challenge()),\n ]);\n case 'forbidden':\n return this.httpError(403, -32_000, generic, []);\n case 'bad-request':\n return this.httpError(400, ProtocolErrorCode.ParseError, generic, []);\n default:\n return this.httpError(500, ProtocolErrorCode.InternalError, 'Internal Error', []);\n }\n }\n\n /**\n * Boundary #2: `tools/list` has no tool-result channel, so a failure IS a JSON-RPC error. It\n * THROWS rather than returning a value the caller must remember to throw, and is typed `never`\n * so the compiler knows the call is terminal.\n *\n * This is LOAD-BEARING for disclosure, not defence in depth. Measured on\n * `@modelcontextprotocol/server` 2.0.0 and pinned by `McpSdkForeignThrow.spec.ts`: the SDK wraps\n * a foreign (non-`ProtocolError`) throw in `-32603` but copies its `message` onto the wire\n * VERBATIM. So a `tools/list` handler that lets any error escape un-translated publishes that\n * error's operator text to the caller.\n */\n toListError(error: Error): never {\n const payload = this.boundary.encode(error);\n const requestId = McpCorrelation.requestId();\n if (payload.kind === 'bad-request') {\n throw new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n payload.callerMessage ?? payload.message,\n new McpErrorData(requestId),\n );\n }\n const message = payload.kind === 'end-user' ? payload.message : 'Internal Error';\n throw new ProtocolError(\n ProtocolErrorCode.InternalError,\n message,\n new McpErrorData(requestId),\n );\n }\n\n /** tools/call with a name no binding registered: a JSON-RPC protocol error in either MCP era. */\n unknownTool(toolName: string): ProtocolError {\n const requestId = McpCorrelation.requestId();\n log.info(`Unknown MCP tool: ${toolName} requestId=${requestId}`);\n return new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n `Unknown tool: ${toolName}`,\n new McpErrorData(requestId),\n );\n }\n\n /**\n * Boundary #3: every tools/call failure after the tool is found — an `isError: true` result the\n * model sees.\n *\n * A registered {@link McpErrorTranslators} REPLACES the default and declines by delegating to\n * {@link McpDefaultToolCallRenderer}, so \"no translator registered\" and \"a translator that does\n * not claim this error\" produce byte-identical results.\n */\n toToolCallResult(failure: Error): CallToolResult {\n const renderer = this.appTranslators;\n if (!renderer) return this.defaultToolCall.toToolCallResult(failure);\n return this.withDefaultMeta(this.appOrDefault(renderer, failure));\n }\n\n /** The `_meta` every tools/call result carries so a user can quote its requestId. */\n resultMeta(requestId: string): Record<string, string> {\n return { [MCP_REQUEST_ID_META_KEY]: requestId };\n }\n\n /**\n * An app translator that THROWS must not replace the failure being reported: its own bug is\n * logged here and the ORIGINAL error still renders through the webpieces default, so the model\n * still gets a reply carrying the requestId.\n */\n private appOrDefault(renderer: McpErrorTranslators, failure: Error): CallToolResult {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an app translator's own bug must not replace the failure it was asked to render\n try {\n return renderer.toToolCallResult(failure);\n } catch (err: unknown) {\n const error = toError(err);\n log.error(\n 'Application McpErrorTranslators.toToolCallResult threw; rendering the webpieces default.',\n error,\n );\n return this.defaultToolCall.toToolCallResult(failure);\n }\n }\n\n /**\n * webpieces fills `_meta` only when the app left it off, keeping the README's \"every reply\n * carries the requestId\" promise a DEFAULT rather than a restriction: an app that set `_meta`\n * keeps it byte-for-byte.\n */\n private withDefaultMeta(result: CallToolResult): CallToolResult {\n if (result._meta !== undefined) return result;\n return { ...result, _meta: this.resultMeta(McpCorrelation.requestId()) };\n }\n\n private httpError(\n status: number,\n code: number,\n message: string,\n headers: HttpHeader[],\n ): HttpResponseDto<McpHttpErrorBody> {\n const correlation = McpCorrelation.current();\n const requestId = McpCorrelation.requestId();\n return new HttpResponseDto<McpHttpErrorBody>(\n new HttpResponseStatus(status, message),\n headers,\n new McpHttpErrorBody(\n new McpHttpErrorDetail(code, message, new McpErrorData(requestId)),\n correlation.jsonRpcId,\n ),\n );\n }\n}\n"]}
@@ -2,7 +2,12 @@ import { Express } from 'express';
2
2
  import { McpProtectedResourceMetadata, WpMcpServerConfig } from './McpAuth';
3
3
  import { McpBindOptions } from './McpBindOptions';
4
4
  /**
5
- * Modern-only MCP 2026-07-28 adapter around the official SDK's request-scoped HTTP handler.
5
+ * MCP adapter around the official SDK's request-scoped HTTP handler, serving BOTH wire eras from
6
+ * one tool registry: modern (2026-07-28, per-request `_meta` envelope) and legacy (the 2025-11-25
7
+ * family, negotiated by `initialize`). The SDK routes each request to the leg its shape selects and
8
+ * negotiates the revision; only a revision no era knows is refused. Pinning one era instead made
9
+ * every shipping client unreachable (issue #969), and the MCP lifecycle spec is explicit that a
10
+ * server answers with a revision it supports rather than erroring.
6
11
  *
7
12
  * Error boundary: every failure is mapped by the one `WpMcpErrorTranslator`, and each entry point
8
13
  * has exactly one catch that only delegates to it: the bind HTTP handler
@@ -56,7 +56,12 @@ class WpSdkMcpServer extends server_1.McpServer {
56
56
  }
57
57
  }
58
58
  /**
59
- * Modern-only MCP 2026-07-28 adapter around the official SDK's request-scoped HTTP handler.
59
+ * MCP adapter around the official SDK's request-scoped HTTP handler, serving BOTH wire eras from
60
+ * one tool registry: modern (2026-07-28, per-request `_meta` envelope) and legacy (the 2025-11-25
61
+ * family, negotiated by `initialize`). The SDK routes each request to the leg its shape selects and
62
+ * negotiates the revision; only a revision no era knows is refused. Pinning one era instead made
63
+ * every shipping client unreachable (issue #969), and the MCP lifecycle spec is explicit that a
64
+ * server answers with a revision it supports rather than erroring.
60
65
  *
61
66
  * Error boundary: every failure is mapped by the one `WpMcpErrorTranslator`, and each entry point
62
67
  * has exactly one catch that only delegates to it: the bind HTTP handler
@@ -131,9 +136,11 @@ class WpMcpServer {
131
136
  (bodyError, req, res, _next) => {
132
137
  void this.handleBodyFailure(bodyError, req, res);
133
138
  });
134
- // Registered AFTER the POST route so POST still wins. MCP 2026-07-28 has no session GET
135
- // (`subscriptions/listen` is a POST method), so every other method is a method error — and a
136
- // discovery probe gets that instead of Express' bare 404.
139
+ // Registered AFTER the POST route so POST still wins. NEITHER era served here has a session
140
+ // GET: 2026-07-28 has none at all (`subscriptions/listen` is a POST method), and the legacy
141
+ // leg is the SDK's STATELESS fallback, which answers the 2025 session GET/DELETE with the
142
+ // same 405. So every other method is a method error — and a discovery probe gets that
143
+ // instead of Express' bare 404.
137
144
  app.all(options.endpointPath, (_req, res) => {
138
145
  res.setHeader('Allow', 'POST');
139
146
  res.status(405).json(new McpMethodNotAllowedBody());
@@ -155,7 +162,9 @@ class WpMcpServer {
155
162
  }
156
163
  createHandler(options, responseMode) {
157
164
  return (0, server_1.createMcpHandler)((context) => this.buildSdkServer(context, options), {
158
- legacy: 'reject',
165
+ // Both eras, one factory: `stateless` serves 2025-era traffic through a fresh
166
+ // instance of the SAME McpServerFactory, so the two eras can never drift apart.
167
+ legacy: 'stateless',
159
168
  responseMode,
160
169
  bus: options.deployment.bus,
161
170
  maxSubscriptions: options.maxSubscriptions,
@@ -254,9 +263,12 @@ class WpMcpServer {
254
263
  return disconnected.signal;
255
264
  }
256
265
  buildSdkServer(context, options) {
266
+ // The era is the SDK's to decide and BOTH are served; what is not negotiable is that the
267
+ // request came through `bind`'s HTTP boundary, which is where the external bearer is
268
+ // verified. A factory call without it is a wiring mistake, never a client's doing.
257
269
  const authentication = context.authInfo?.extra?.['webpiecesAuthentication'];
258
- if (context.era !== 'modern' || !(authentication instanceof McpPostAuthentication)) {
259
- throw new core_util_1.ApiImplementationError('WpMcpServer serves only MCP 2026-07-28 requests authenticated by its bind boundary.');
270
+ if (!(authentication instanceof McpPostAuthentication)) {
271
+ throw new core_util_1.ApiImplementationError('WpMcpServer serves only MCP requests authenticated by its bind boundary.');
260
272
  }
261
273
  const registry = this.requireRegistry();
262
274
  const server = new WpSdkMcpServer({ name: this.config.name, version: `${this.config.version}+${this.revision}` }, {
@@ -1 +1 @@
1
- {"version":3,"file":"WpMcpServer.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpServer.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport {\n AuthInfo,\n CallToolRequest,\n CallToolResult,\n createMcpHandler,\n ListToolsResult,\n McpHttpHandler,\n McpRequestContext,\n McpServer,\n ProgressToken,\n ServerContext,\n ServerOptions,\n Tool,\n Implementation,\n} from '@modelcontextprotocol/server';\nimport { NodeMcpRequestHandler, toNodeHandler } from '@modelcontextprotocol/node';\nimport { Express, json, NextFunction, Request, Response } from 'express';\nimport {\n ApiBadRequestError,\n ApiForbiddenError,\n ApiImplementationError,\n ApiMethodInfo,\n ApiUnauthorizedError,\n DtoValue,\n LogApiCallImpl,\n LogManager,\n toError,\n} from '@webpieces/core-util';\nimport {\n HttpRequest,\n RequestContext,\n RequestContextApiCallContext,\n RequestContextHeaders,\n} from '@webpieces/core-context';\nimport { ExpressResponseWriter } from '@webpieces/http-server';\nimport { MintedJwt } from '@webpieces/http-routing';\nimport {\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS,\n McpEndpointDescriptor,\n McpProtectedResourceMetadata,\n VerifiedMcpCredential,\n WpMcpServerConfig,\n} from './McpAuth';\nimport { McpApiDispatcher } from './McpApiDispatcher';\nimport { McpBindOptions } from './McpBindOptions';\nimport { McpInvocationContext, McpProgressReporter } from './McpInvocationContext';\nimport { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';\nimport { McpCorrelation, WpMcpErrorTranslator } from './WpMcpErrorTranslator';\n\nconst log = LogManager.getLogger('WpMcpServer');\n\ntype AuthenticatedExpressRequest = Request & { auth?: AuthInfo };\n\n/**\n * The request DTO the edge `LogApiCall` lines report. These boundaries run BEFORE (or outside) any\n * API dispatch, so there is no contract DTO to log; the step name is the fact worth having.\n */\nclass McpEdgeRequest {\n constructor(public readonly step: string) {}\n}\n\n/** Body of the 405 answered for every non-POST method at the bound MCP endpoint path. */\nclass McpMethodNotAllowedBody {\n readonly error = 'method_not_allowed';\n}\n\n/** Facts established once per POST at the bind boundary and handed to the SDK handlers. */\nclass McpPostAuthentication {\n constructor(\n public readonly accessToken: string,\n public readonly credential: VerifiedMcpCredential,\n public readonly disconnectSignal: AbortSignal,\n ) {}\n}\n\n/**\n * The official SDK server with Webpieces-owned tools/list and tools/call handlers. It answers\n * `toolInputSchemaJson` from the Webpieces registry so the SDK's pre-dispatch SEP-2243\n * `Mcp-Param-*` header validation still runs without registering SDK-side tool callbacks.\n */\nclass WpSdkMcpServer extends McpServer {\n constructor(\n info: Implementation,\n options: ServerOptions,\n private readonly registry: McpToolRegistry,\n ) {\n super(info, options);\n }\n\n // webpieces-disable no-any-unknown -- signature is fixed by the SDK's JSON Schema record type\n override toolInputSchemaJson(name: string): Record<string, unknown> | undefined {\n return this.registry.find(name)?.inputSchema as Record<string, DtoValue> | undefined;\n }\n}\n\n/**\n * Modern-only MCP 2026-07-28 adapter around the official SDK's request-scoped HTTP handler.\n *\n * Error boundary: every failure is mapped by the one `WpMcpErrorTranslator`, and each entry point\n * has exactly one catch that only delegates to it: the bind HTTP handler\n * (`toBearerBoundaryResponse`), tools/list (`toListError`) and tools/call (`toToolCallResult`).\n * `subscriptions/listen` is served entirely by the SDK's listen router, so Webpieces has no handler\n * (and no catch) there. The external bearer is verified exactly once per POST, at the HTTP\n * boundary, before the SDK is involved.\n */\nexport class WpMcpServer<TGrant, TMintRequest> {\n private readonly dispatcher = new McpApiDispatcher();\n /**\n * These edges have NO filter chain above them — `LogApiFilter` never sees a rejected bearer, a\n * bad `Origin`, a body express could not parse, or a `tools/list` the SDK renders itself.\n * Wrapping each in `LogApiCall` is this repo's one line on logging, and it is why\n * `ApiErrorBoundary` no longer writes a second, barer line of its own.\n */\n private readonly logApiCall = new LogApiCallImpl(new RequestContextApiCallContext());\n private readonly responseWriter = new ExpressResponseWriter();\n /** tools/call gives the app's translators first refusal; every other reply stays framework-owned. */\n private readonly translator: WpMcpErrorTranslator;\n private registry?: McpToolRegistry;\n private handler?: McpHttpHandler;\n private streamingHandler?: McpHttpHandler;\n private nodeHandler?: NodeMcpRequestHandler;\n private streamingNodeHandler?: NodeMcpRequestHandler;\n private revision?: string;\n\n constructor(private readonly config: WpMcpServerConfig<TGrant, TMintRequest>) {\n this.translator = new WpMcpErrorTranslator(\n (): string => this.challenge(),\n config.errorTranslator,\n );\n }\n\n /**\n * Mounts this MCP endpoint on `app`: POST at `options.endpointPath`, plus a 405 (`Allow: POST`)\n * for every other method there.\n *\n * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource, so this may be\n * called only once and `WpMcpServerConfig.setResource(...)`'s path must equal `endpointPath`.\n * That resource URI is fixed for the lifetime of the process and is never derived from the\n * request `Host` — a host-derived audience would make the boundary's own audience check compare\n * two caller-controlled values, which is the confused deputy this check exists to prevent.\n * Serving a second hostname is a second deployment, not a second audience.\n *\n * The OAuth/discovery half stays app-owned: publish `protectedResourceMetadata()` at\n * `WpMcpServerConfig.resourceMetadataUrl`, which is where the 401 challenge sends clients.\n */\n bind(app: Express, options: McpBindOptions): void {\n if (this.handler) {\n throw new Error(\n 'WpMcpServer.bind(...) may be called only once; an MCP resource has exactly one ' +\n 'canonical URI, fixed for the lifetime of the process.',\n );\n }\n // Fail at boot naming the forgotten setter, never on the first request with a 401 the client\n // answers by re-authenticating (see WpMcpServerConfig).\n this.config.validate();\n const resourcePath = new URL(this.config.resource).pathname;\n if (resourcePath !== options.endpointPath) {\n throw new Error(\n `MCP resource path '${resourcePath}' must equal endpointPath ` +\n `'${options.endpointPath}'; the canonical resource URI ` +\n `'${this.config.resource}' and the bound route are the same endpoint.`,\n );\n }\n this.registry = new McpToolRegistry(options.bindings);\n this.revision = this.calculateRegistryRevision(this.registry);\n this.handler = this.createHandler(options, 'auto');\n this.streamingHandler = this.createHandler(options, 'sse');\n this.nodeHandler = toNodeHandler(this.handler, {\n onerror: (error: Error) => log.error('MCP Node transport failed', error),\n });\n this.streamingNodeHandler = toNodeHandler(this.streamingHandler, {\n onerror: (error: Error) => log.error('MCP streaming Node transport failed', error),\n });\n app.post(\n options.endpointPath,\n json(),\n async (req: Request, res: Response): Promise<void> =>\n this.handleHttp(req, res, options),\n // Express routes body-parser failures (bad JSON, body too large) to an error handler.\n (bodyError: Error, req: Request, res: Response, _next: NextFunction): void => {\n void this.handleBodyFailure(bodyError, req, res);\n },\n );\n // Registered AFTER the POST route so POST still wins. MCP 2026-07-28 has no session GET\n // (`subscriptions/listen` is a POST method), so every other method is a method error — and a\n // discovery probe gets that instead of Express' bare 404.\n app.all(options.endpointPath, (_req: Request, res: Response): void => {\n res.setHeader('Allow', 'POST');\n res.status(405).json(new McpMethodNotAllowedBody());\n });\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return this.config.protectedResourceMetadata();\n }\n\n /** Gracefully closes active listen/request streams; clients must reconnect and refresh lists. */\n async close(): Promise<void> {\n await Promise.all([this.handler?.close(), this.streamingHandler?.close()]);\n }\n\n /** Level-triggered invalidation; the authoritative list is always fetched again by clients. */\n toolsChanged(): void {\n this.handler?.notify.toolsChanged();\n }\n\n get registryRevision(): string | undefined {\n return this.revision;\n }\n\n private createHandler(options: McpBindOptions, responseMode: 'auto' | 'sse'): McpHttpHandler {\n return createMcpHandler(\n (context: McpRequestContext) => this.buildSdkServer(context, options),\n {\n legacy: 'reject',\n responseMode,\n bus: options.deployment.bus,\n maxSubscriptions: options.maxSubscriptions,\n keepAliveMs: options.keepAliveMs,\n onerror: (error: Error) => log.warn('MCP protocol request rejected', error),\n },\n );\n }\n\n /** Entry point #1: the one catch for everything before and around the SDK exchange. */\n private async handleHttp(req: Request, res: Response, options: McpBindOptions): Promise<void> {\n res.setHeader('X-Accel-Buffering', 'no');\n await RequestContext.run(async () => {\n new RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));\n McpCorrelation.stamp(this.correlationOf(req));\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP HTTP entry point; delegates only to WpMcpErrorTranslator\n try {\n await this.logEdge('serve', () => this.serveExpress(req, res, options));\n } catch (err: unknown) {\n const error = toError(err);\n this.writeBoundaryError(res, error);\n }\n });\n }\n\n /** Express hands body-parser failures (bad JSON, body too large) here, outside every filter. */\n private async handleBodyFailure(cause: Error, req: Request, res: Response): Promise<void> {\n await RequestContext.run(async () => {\n new RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));\n McpCorrelation.stamp(this.correlationOf(req));\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP body entry point; delegates only to WpMcpErrorTranslator\n try {\n await this.logEdge('body', () => this.rejectBody(cause));\n } catch (err: unknown) {\n const error = toError(err);\n this.writeBoundaryError(res, error);\n }\n });\n }\n\n /** A body express could not parse is a caller input error, reported through the same edge. */\n private async rejectBody(cause: Error): Promise<never> {\n throw new ApiBadRequestError(\n `MCP request body rejected: ${cause.message}`,\n undefined,\n undefined,\n cause,\n );\n }\n\n /**\n * The pre-SDK boundary owns the socket, so it produces a VALUE and writes it through the SAME\n * express writer `ExpressWrapper` uses. A failure after headers were sent can only end the\n * stream.\n */\n private writeBoundaryError(res: Response, error: Error): void {\n if (res.headersSent) {\n res.end();\n return;\n }\n this.responseWriter.write(res, this.translator.toBearerBoundaryResponse(error));\n }\n\n /** Run one un-filtered edge step under `LogApiCall`, so its failure gets exactly one log line. */\n private async logEdge<R>(step: string, work: () => Promise<R>): Promise<R> {\n return this.logApiCall.execute(\n new ApiMethodInfo('server', 'McpEndpoint', step),\n new McpEdgeRequest(step),\n work,\n );\n }\n\n private async serveExpress(\n req: Request,\n res: Response,\n options: McpBindOptions,\n ): Promise<void> {\n const origin = req.header('origin');\n if (origin && !options.allowedOrigins.includes(origin)) {\n throw new ApiForbiddenError(`MCP request Origin '${origin}' is not allowed.`);\n }\n const authentication = await this.authenticate(req);\n const request = req as AuthenticatedExpressRequest;\n request.auth = this.authInfo(authentication);\n const serve = this.hasProgressToken(req.body)\n ? this.streamingNodeHandler\n : this.nodeHandler;\n if (!serve) throw new ApiImplementationError('MCP Node handlers are not initialized.');\n await serve(request, res, req.body);\n }\n\n /** Verifies the external bearer once for this POST. Handlers never re-verify it. */\n private async authenticate(req: Request): Promise<McpPostAuthentication> {\n const match = req.header('authorization')?.match(/^Bearer ([^\\s]+)$/i);\n const token = match?.[1];\n if (!token) throw new ApiUnauthorizedError('MCP request has no bearer access token.');\n const credential = await this.config.accessTokenAuthority.verifyAccessToken(\n token,\n this.config.resource,\n );\n this.validateCredential(credential);\n return new McpPostAuthentication(token, credential, this.disconnectSignal(req));\n }\n\n private disconnectSignal(req: Request): AbortSignal {\n const disconnected = new AbortController();\n const res = req.res;\n const cancel = (): void => disconnected.abort();\n req.once('aborted', cancel);\n req.socket.once('close', cancel);\n res?.once('close', cancel);\n res?.once('finish', (): void => {\n req.off('aborted', cancel);\n req.socket.off('close', cancel);\n res.off('close', cancel);\n });\n return disconnected.signal;\n }\n\n private buildSdkServer(context: McpRequestContext, options: McpBindOptions): McpServer {\n const authentication = context.authInfo?.extra?.['webpiecesAuthentication'];\n if (context.era !== 'modern' || !(authentication instanceof McpPostAuthentication)) {\n throw new ApiImplementationError(\n 'WpMcpServer serves only MCP 2026-07-28 requests authenticated by its bind boundary.',\n );\n }\n const registry = this.requireRegistry();\n const server = new WpSdkMcpServer(\n { name: this.config.name, version: `${this.config.version}+${this.revision}` },\n {\n capabilities: { tools: { listChanged: true } },\n cacheHints: {\n 'tools/list': { ttlMs: options.deployment.ttlMs, cacheScope: 'private' },\n 'server/discover': { ttlMs: options.deployment.ttlMs, cacheScope: 'private' },\n },\n },\n registry,\n );\n server.server.removeRequestHandler('tools/list');\n server.server.removeRequestHandler('tools/call');\n server.server.setRequestHandler(\n 'tools/list',\n async (_request: object): Promise<ListToolsResult> =>\n this.handleListTools(authentication),\n );\n server.server.setRequestHandler(\n 'tools/call',\n async (request: CallToolRequest, sdkContext: ServerContext): Promise<CallToolResult> =>\n this.handleCallTool(server, request, authentication, sdkContext),\n );\n return server;\n }\n\n /** Entry point #2: tools/list has no tool-result channel, so failures are JSON-RPC errors. */\n private async handleListTools(authentication: McpPostAuthentication): Promise<ListToolsResult> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP tools/list entry point; delegates only to WpMcpErrorTranslator\n try {\n return await this.logEdge('tools/list', async () =>\n this.listTools(authentication.credential),\n );\n } catch (err: unknown) {\n const error = toError(err);\n this.translator.toListError(error);\n }\n }\n\n private listTools(credential: VerifiedMcpCredential): ListToolsResult {\n const tools: Tool[] = [];\n for (const tool of this.requireRegistry().tools) {\n if (tool.isVisibleTo(credential.listingRoles)) tools.push(this.toolDefinition(tool));\n }\n return { tools };\n }\n\n private toolDefinition(tool: RegisteredMcpTool): Tool {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema as Tool['inputSchema'],\n outputSchema: tool.outputSchema as Tool['outputSchema'],\n annotations: tool.annotations,\n _meta: { webpiecesRegistryRevision: this.revision },\n };\n }\n\n /**\n * Entry point #3: an unknown tool is a JSON-RPC -32602; every failure after the tool is found\n * is an `isError: true` result, identically for local and remote bindings.\n */\n private async handleCallTool(\n server: WpSdkMcpServer,\n request: CallToolRequest,\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<CallToolResult> {\n const name = request.params.name;\n McpCorrelation.stamp(new McpCorrelation(sdkContext.mcpReq.id, name));\n const tool = this.requireRegistry().find(name);\n if (!tool) throw this.translator.unknownTool(name);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP tools/call entry point; delegates only to WpMcpErrorTranslator\n try {\n const result = await this.callTool(tool, request, authentication, sdkContext);\n result._meta = this.translator.resultMeta(McpCorrelation.requestId());\n return server.server.projectCallToolResult(\n result,\n tool.outputSchema as Record<string, DtoValue>,\n );\n } catch (err: unknown) {\n const error = toError(err);\n return this.translator.toToolCallResult(error);\n }\n }\n\n /** Everything a tool call does. No catch in here: failures propagate to handleCallTool. */\n private async callTool(\n tool: RegisteredMcpTool,\n request: CallToolRequest,\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<CallToolResult> {\n const credential = authentication.credential;\n const endpointJwt =\n tool.binding.topology === 'local'\n ? await this.mintEndpointJwt(tool, authentication)\n : undefined;\n const invocation = new McpInvocationContext(\n sdkContext.mcpReq.id,\n tool.name,\n credential.subject,\n credential.listingRoles,\n AbortSignal.any([sdkContext.mcpReq.signal, authentication.disconnectSignal]),\n this.progressReporter(sdkContext),\n );\n const args = (request.params.arguments ?? {}) as DtoValue;\n const value = await this.dispatcher.call(\n tool,\n args,\n credential,\n invocation,\n endpointJwt?.token,\n );\n const outputFailure = this.requireRegistry().schemaBuilder.validate(\n tool.responseClass,\n value,\n );\n if (outputFailure) {\n throw new ApiImplementationError(\n `MCP output schema violation for ${tool.apiClass.name}.${tool.methodName}: ${outputFailure.message}`,\n );\n }\n const structured = this.record(value);\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n structuredContent: structured,\n };\n }\n\n private async mintEndpointJwt(\n tool: RegisteredMcpTool,\n authentication: McpPostAuthentication,\n ): Promise<MintedJwt> {\n const descriptor = new McpEndpointDescriptor(\n tool.name,\n tool.apiClass.name,\n tool.methodName,\n );\n const minted = await this.config.endpointJwtAuthority.mint(\n this.config.endpointMintRequest(authentication.credential, descriptor),\n );\n const now = Math.floor(Date.now() / 1000);\n if (minted.token === authentication.accessToken) {\n throw new ApiImplementationError(\n `MCP access-token passthrough is forbidden (endpoint JWT for ${tool.name}).`,\n );\n }\n if (\n minted.expiresAtEpochSeconds <= now ||\n minted.expiresAtEpochSeconds - now > this.config.maxEndpointJwtLifetimeSeconds\n ) {\n throw new ApiImplementationError(\n `MCP endpoint JWT lifetime is invalid for ${tool.name}.`,\n );\n }\n return minted;\n }\n\n private validateCredential(credential: VerifiedMcpCredential): void {\n const now = Math.floor(Date.now() / 1000);\n const reject = (reason: string): never => {\n throw new ApiUnauthorizedError(`MCP access token rejected: ${reason}`);\n };\n if (credential.subject.trim() === '') reject('no subject');\n const stamps = [\n credential.issuedAtEpochSeconds,\n credential.expiresAtEpochSeconds,\n credential.accountValidatedAtEpochSeconds,\n ];\n if (!stamps.every(Number.isFinite)) reject('security timestamps must be finite');\n if (credential.resource !== this.config.resource) reject('resource mismatch');\n if (!this.config.authorizationServers.includes(credential.issuer)) {\n reject('issuer is not trusted');\n }\n if (credential.issuedAtEpochSeconds > now || credential.expiresAtEpochSeconds <= now) {\n reject('outside its valid time window');\n }\n const lifetime = credential.expiresAtEpochSeconds - credential.issuedAtEpochSeconds;\n if (lifetime > MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) reject('lifetime exceeds 30 days');\n for (const scope of this.config.requiredScopes) {\n if (!credential.scopes.includes(scope)) reject(`missing required scope '${scope}'`);\n }\n const validatedAge = now - credential.accountValidatedAtEpochSeconds;\n if (\n credential.accountValidatedAtEpochSeconds > now ||\n validatedAge > this.config.maxAccountValidationAgeSeconds\n ) {\n reject('account authorization state is not fresh enough for dispatch');\n }\n }\n\n private authInfo(authentication: McpPostAuthentication): AuthInfo {\n const credential = authentication.credential;\n return {\n token: authentication.accessToken,\n clientId: credential.subject,\n scopes: [...credential.scopes],\n expiresAt: credential.expiresAtEpochSeconds,\n resource: new URL(credential.resource),\n extra: { webpiecesAuthentication: authentication },\n };\n }\n\n private progressReporter(context: ServerContext): McpProgressReporter | undefined {\n const token = context.mcpReq._meta?.progressToken;\n if (token === undefined) return undefined;\n return async (progress: number, total?: number, message?: string): Promise<void> => {\n await context.mcpReq.notify({\n method: 'notifications/progress',\n params: this.progressParams(token, progress, total, message),\n });\n };\n }\n\n private progressParams(\n progressToken: ProgressToken,\n progress: number,\n total?: number,\n message?: string,\n ): Record<string, DtoValue> {\n const params: Record<string, DtoValue> = { progressToken, progress };\n if (total !== undefined) params['total'] = total;\n if (message !== undefined) params['message'] = message;\n return params;\n }\n\n /** What the raw POST body tells us before the SDK has parsed anything: the id, and the tool. */\n private correlationOf(req: Request): McpCorrelation {\n const body = this.bodyObject(req.body);\n const id = body?.['id'];\n const params = this.bodyObject(body?.['params']);\n const name = params?.['name'];\n return new McpCorrelation(\n typeof id === 'string' || typeof id === 'number' ? id : null,\n typeof name === 'string' ? name : undefined,\n );\n }\n\n private challenge(): string {\n return `Bearer resource_metadata=\"${this.config.resourceMetadataUrl}\"`;\n }\n\n private bodyObject(value: DtoValue | undefined): Record<string, DtoValue> | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n return value as Record<string, DtoValue>;\n }\n\n private hasProgressToken(body: DtoValue): boolean {\n const params = this.bodyObject(this.bodyObject(body)?.['params']);\n return this.bodyObject(params?.['_meta'])?.['progressToken'] !== undefined;\n }\n\n private toHttpRequest(req: Request): HttpRequest {\n const headers = new Map<string, string[]>();\n for (const [name, value] of Object.entries(req.headers)) {\n if (typeof value === 'string') headers.set(name, [value]);\n else if (Array.isArray(value)) headers.set(name, value);\n }\n return new HttpRequest(req.method, req.originalUrl || req.path, headers);\n }\n\n private calculateRegistryRevision(registry: McpToolRegistry): string {\n const surface = registry.tools.map((tool: RegisteredMcpTool) => [\n tool.name,\n tool.description,\n tool.inputSchema,\n tool.outputSchema,\n tool.annotations,\n ]);\n return createHash('sha256').update(JSON.stringify(surface)).digest('hex').slice(0, 12);\n }\n\n private requireRegistry(): McpToolRegistry {\n if (!this.registry) throw new Error('Call WpMcpServer.bind(...) before serving requests.');\n return this.registry;\n }\n\n private record(value: DtoValue): Record<string, DtoValue> {\n const jsonValue = JSON.stringify(value);\n if (jsonValue === undefined) {\n throw new ApiImplementationError('MCP structured content is not JSON serializable.');\n }\n const parsed = JSON.parse(jsonValue) as DtoValue;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return { value: parsed };\n }\n return parsed as Record<string, DtoValue>;\n }\n}\n"]}
1
+ {"version":3,"file":"WpMcpServer.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpServer.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport {\n AuthInfo,\n CallToolRequest,\n CallToolResult,\n createMcpHandler,\n ListToolsResult,\n McpHttpHandler,\n McpRequestContext,\n McpServer,\n ProgressToken,\n ServerContext,\n ServerOptions,\n Tool,\n Implementation,\n} from '@modelcontextprotocol/server';\nimport { NodeMcpRequestHandler, toNodeHandler } from '@modelcontextprotocol/node';\nimport { Express, json, NextFunction, Request, Response } from 'express';\nimport {\n ApiBadRequestError,\n ApiForbiddenError,\n ApiImplementationError,\n ApiMethodInfo,\n ApiUnauthorizedError,\n DtoValue,\n LogApiCallImpl,\n LogManager,\n toError,\n} from '@webpieces/core-util';\nimport {\n HttpRequest,\n RequestContext,\n RequestContextApiCallContext,\n RequestContextHeaders,\n} from '@webpieces/core-context';\nimport { ExpressResponseWriter } from '@webpieces/http-server';\nimport { MintedJwt } from '@webpieces/http-routing';\nimport {\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS,\n McpEndpointDescriptor,\n McpProtectedResourceMetadata,\n VerifiedMcpCredential,\n WpMcpServerConfig,\n} from './McpAuth';\nimport { McpApiDispatcher } from './McpApiDispatcher';\nimport { McpBindOptions } from './McpBindOptions';\nimport { McpInvocationContext, McpProgressReporter } from './McpInvocationContext';\nimport { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';\nimport { McpCorrelation, WpMcpErrorTranslator } from './WpMcpErrorTranslator';\n\nconst log = LogManager.getLogger('WpMcpServer');\n\ntype AuthenticatedExpressRequest = Request & { auth?: AuthInfo };\n\n/**\n * The request DTO the edge `LogApiCall` lines report. These boundaries run BEFORE (or outside) any\n * API dispatch, so there is no contract DTO to log; the step name is the fact worth having.\n */\nclass McpEdgeRequest {\n constructor(public readonly step: string) {}\n}\n\n/** Body of the 405 answered for every non-POST method at the bound MCP endpoint path. */\nclass McpMethodNotAllowedBody {\n readonly error = 'method_not_allowed';\n}\n\n/** Facts established once per POST at the bind boundary and handed to the SDK handlers. */\nclass McpPostAuthentication {\n constructor(\n public readonly accessToken: string,\n public readonly credential: VerifiedMcpCredential,\n public readonly disconnectSignal: AbortSignal,\n ) {}\n}\n\n/**\n * The official SDK server with Webpieces-owned tools/list and tools/call handlers. It answers\n * `toolInputSchemaJson` from the Webpieces registry so the SDK's pre-dispatch SEP-2243\n * `Mcp-Param-*` header validation still runs without registering SDK-side tool callbacks.\n */\nclass WpSdkMcpServer extends McpServer {\n constructor(\n info: Implementation,\n options: ServerOptions,\n private readonly registry: McpToolRegistry,\n ) {\n super(info, options);\n }\n\n // webpieces-disable no-any-unknown -- signature is fixed by the SDK's JSON Schema record type\n override toolInputSchemaJson(name: string): Record<string, unknown> | undefined {\n return this.registry.find(name)?.inputSchema as Record<string, DtoValue> | undefined;\n }\n}\n\n/**\n * MCP adapter around the official SDK's request-scoped HTTP handler, serving BOTH wire eras from\n * one tool registry: modern (2026-07-28, per-request `_meta` envelope) and legacy (the 2025-11-25\n * family, negotiated by `initialize`). The SDK routes each request to the leg its shape selects and\n * negotiates the revision; only a revision no era knows is refused. Pinning one era instead made\n * every shipping client unreachable (issue #969), and the MCP lifecycle spec is explicit that a\n * server answers with a revision it supports rather than erroring.\n *\n * Error boundary: every failure is mapped by the one `WpMcpErrorTranslator`, and each entry point\n * has exactly one catch that only delegates to it: the bind HTTP handler\n * (`toBearerBoundaryResponse`), tools/list (`toListError`) and tools/call (`toToolCallResult`).\n * `subscriptions/listen` is served entirely by the SDK's listen router, so Webpieces has no handler\n * (and no catch) there. The external bearer is verified exactly once per POST, at the HTTP\n * boundary, before the SDK is involved.\n */\nexport class WpMcpServer<TGrant, TMintRequest> {\n private readonly dispatcher = new McpApiDispatcher();\n /**\n * These edges have NO filter chain above them — `LogApiFilter` never sees a rejected bearer, a\n * bad `Origin`, a body express could not parse, or a `tools/list` the SDK renders itself.\n * Wrapping each in `LogApiCall` is this repo's one line on logging, and it is why\n * `ApiErrorBoundary` no longer writes a second, barer line of its own.\n */\n private readonly logApiCall = new LogApiCallImpl(new RequestContextApiCallContext());\n private readonly responseWriter = new ExpressResponseWriter();\n /** tools/call gives the app's translators first refusal; every other reply stays framework-owned. */\n private readonly translator: WpMcpErrorTranslator;\n private registry?: McpToolRegistry;\n private handler?: McpHttpHandler;\n private streamingHandler?: McpHttpHandler;\n private nodeHandler?: NodeMcpRequestHandler;\n private streamingNodeHandler?: NodeMcpRequestHandler;\n private revision?: string;\n\n constructor(private readonly config: WpMcpServerConfig<TGrant, TMintRequest>) {\n this.translator = new WpMcpErrorTranslator(\n (): string => this.challenge(),\n config.errorTranslator,\n );\n }\n\n /**\n * Mounts this MCP endpoint on `app`: POST at `options.endpointPath`, plus a 405 (`Allow: POST`)\n * for every other method there.\n *\n * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource, so this may be\n * called only once and `WpMcpServerConfig.setResource(...)`'s path must equal `endpointPath`.\n * That resource URI is fixed for the lifetime of the process and is never derived from the\n * request `Host` — a host-derived audience would make the boundary's own audience check compare\n * two caller-controlled values, which is the confused deputy this check exists to prevent.\n * Serving a second hostname is a second deployment, not a second audience.\n *\n * The OAuth/discovery half stays app-owned: publish `protectedResourceMetadata()` at\n * `WpMcpServerConfig.resourceMetadataUrl`, which is where the 401 challenge sends clients.\n */\n bind(app: Express, options: McpBindOptions): void {\n if (this.handler) {\n throw new Error(\n 'WpMcpServer.bind(...) may be called only once; an MCP resource has exactly one ' +\n 'canonical URI, fixed for the lifetime of the process.',\n );\n }\n // Fail at boot naming the forgotten setter, never on the first request with a 401 the client\n // answers by re-authenticating (see WpMcpServerConfig).\n this.config.validate();\n const resourcePath = new URL(this.config.resource).pathname;\n if (resourcePath !== options.endpointPath) {\n throw new Error(\n `MCP resource path '${resourcePath}' must equal endpointPath ` +\n `'${options.endpointPath}'; the canonical resource URI ` +\n `'${this.config.resource}' and the bound route are the same endpoint.`,\n );\n }\n this.registry = new McpToolRegistry(options.bindings);\n this.revision = this.calculateRegistryRevision(this.registry);\n this.handler = this.createHandler(options, 'auto');\n this.streamingHandler = this.createHandler(options, 'sse');\n this.nodeHandler = toNodeHandler(this.handler, {\n onerror: (error: Error) => log.error('MCP Node transport failed', error),\n });\n this.streamingNodeHandler = toNodeHandler(this.streamingHandler, {\n onerror: (error: Error) => log.error('MCP streaming Node transport failed', error),\n });\n app.post(\n options.endpointPath,\n json(),\n async (req: Request, res: Response): Promise<void> =>\n this.handleHttp(req, res, options),\n // Express routes body-parser failures (bad JSON, body too large) to an error handler.\n (bodyError: Error, req: Request, res: Response, _next: NextFunction): void => {\n void this.handleBodyFailure(bodyError, req, res);\n },\n );\n // Registered AFTER the POST route so POST still wins. NEITHER era served here has a session\n // GET: 2026-07-28 has none at all (`subscriptions/listen` is a POST method), and the legacy\n // leg is the SDK's STATELESS fallback, which answers the 2025 session GET/DELETE with the\n // same 405. So every other method is a method error — and a discovery probe gets that\n // instead of Express' bare 404.\n app.all(options.endpointPath, (_req: Request, res: Response): void => {\n res.setHeader('Allow', 'POST');\n res.status(405).json(new McpMethodNotAllowedBody());\n });\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return this.config.protectedResourceMetadata();\n }\n\n /** Gracefully closes active listen/request streams; clients must reconnect and refresh lists. */\n async close(): Promise<void> {\n await Promise.all([this.handler?.close(), this.streamingHandler?.close()]);\n }\n\n /** Level-triggered invalidation; the authoritative list is always fetched again by clients. */\n toolsChanged(): void {\n this.handler?.notify.toolsChanged();\n }\n\n get registryRevision(): string | undefined {\n return this.revision;\n }\n\n private createHandler(options: McpBindOptions, responseMode: 'auto' | 'sse'): McpHttpHandler {\n return createMcpHandler(\n (context: McpRequestContext) => this.buildSdkServer(context, options),\n {\n // Both eras, one factory: `stateless` serves 2025-era traffic through a fresh\n // instance of the SAME McpServerFactory, so the two eras can never drift apart.\n legacy: 'stateless',\n responseMode,\n bus: options.deployment.bus,\n maxSubscriptions: options.maxSubscriptions,\n keepAliveMs: options.keepAliveMs,\n onerror: (error: Error) => log.warn('MCP protocol request rejected', error),\n },\n );\n }\n\n /** Entry point #1: the one catch for everything before and around the SDK exchange. */\n private async handleHttp(req: Request, res: Response, options: McpBindOptions): Promise<void> {\n res.setHeader('X-Accel-Buffering', 'no');\n await RequestContext.run(async () => {\n new RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));\n McpCorrelation.stamp(this.correlationOf(req));\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP HTTP entry point; delegates only to WpMcpErrorTranslator\n try {\n await this.logEdge('serve', () => this.serveExpress(req, res, options));\n } catch (err: unknown) {\n const error = toError(err);\n this.writeBoundaryError(res, error);\n }\n });\n }\n\n /** Express hands body-parser failures (bad JSON, body too large) here, outside every filter. */\n private async handleBodyFailure(cause: Error, req: Request, res: Response): Promise<void> {\n await RequestContext.run(async () => {\n new RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));\n McpCorrelation.stamp(this.correlationOf(req));\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP body entry point; delegates only to WpMcpErrorTranslator\n try {\n await this.logEdge('body', () => this.rejectBody(cause));\n } catch (err: unknown) {\n const error = toError(err);\n this.writeBoundaryError(res, error);\n }\n });\n }\n\n /** A body express could not parse is a caller input error, reported through the same edge. */\n private async rejectBody(cause: Error): Promise<never> {\n throw new ApiBadRequestError(\n `MCP request body rejected: ${cause.message}`,\n undefined,\n undefined,\n cause,\n );\n }\n\n /**\n * The pre-SDK boundary owns the socket, so it produces a VALUE and writes it through the SAME\n * express writer `ExpressWrapper` uses. A failure after headers were sent can only end the\n * stream.\n */\n private writeBoundaryError(res: Response, error: Error): void {\n if (res.headersSent) {\n res.end();\n return;\n }\n this.responseWriter.write(res, this.translator.toBearerBoundaryResponse(error));\n }\n\n /** Run one un-filtered edge step under `LogApiCall`, so its failure gets exactly one log line. */\n private async logEdge<R>(step: string, work: () => Promise<R>): Promise<R> {\n return this.logApiCall.execute(\n new ApiMethodInfo('server', 'McpEndpoint', step),\n new McpEdgeRequest(step),\n work,\n );\n }\n\n private async serveExpress(\n req: Request,\n res: Response,\n options: McpBindOptions,\n ): Promise<void> {\n const origin = req.header('origin');\n if (origin && !options.allowedOrigins.includes(origin)) {\n throw new ApiForbiddenError(`MCP request Origin '${origin}' is not allowed.`);\n }\n const authentication = await this.authenticate(req);\n const request = req as AuthenticatedExpressRequest;\n request.auth = this.authInfo(authentication);\n const serve = this.hasProgressToken(req.body)\n ? this.streamingNodeHandler\n : this.nodeHandler;\n if (!serve) throw new ApiImplementationError('MCP Node handlers are not initialized.');\n await serve(request, res, req.body);\n }\n\n /** Verifies the external bearer once for this POST. Handlers never re-verify it. */\n private async authenticate(req: Request): Promise<McpPostAuthentication> {\n const match = req.header('authorization')?.match(/^Bearer ([^\\s]+)$/i);\n const token = match?.[1];\n if (!token) throw new ApiUnauthorizedError('MCP request has no bearer access token.');\n const credential = await this.config.accessTokenAuthority.verifyAccessToken(\n token,\n this.config.resource,\n );\n this.validateCredential(credential);\n return new McpPostAuthentication(token, credential, this.disconnectSignal(req));\n }\n\n private disconnectSignal(req: Request): AbortSignal {\n const disconnected = new AbortController();\n const res = req.res;\n const cancel = (): void => disconnected.abort();\n req.once('aborted', cancel);\n req.socket.once('close', cancel);\n res?.once('close', cancel);\n res?.once('finish', (): void => {\n req.off('aborted', cancel);\n req.socket.off('close', cancel);\n res.off('close', cancel);\n });\n return disconnected.signal;\n }\n\n private buildSdkServer(context: McpRequestContext, options: McpBindOptions): McpServer {\n // The era is the SDK's to decide and BOTH are served; what is not negotiable is that the\n // request came through `bind`'s HTTP boundary, which is where the external bearer is\n // verified. A factory call without it is a wiring mistake, never a client's doing.\n const authentication = context.authInfo?.extra?.['webpiecesAuthentication'];\n if (!(authentication instanceof McpPostAuthentication)) {\n throw new ApiImplementationError(\n 'WpMcpServer serves only MCP requests authenticated by its bind boundary.',\n );\n }\n const registry = this.requireRegistry();\n const server = new WpSdkMcpServer(\n { name: this.config.name, version: `${this.config.version}+${this.revision}` },\n {\n capabilities: { tools: { listChanged: true } },\n cacheHints: {\n 'tools/list': { ttlMs: options.deployment.ttlMs, cacheScope: 'private' },\n 'server/discover': { ttlMs: options.deployment.ttlMs, cacheScope: 'private' },\n },\n },\n registry,\n );\n server.server.removeRequestHandler('tools/list');\n server.server.removeRequestHandler('tools/call');\n server.server.setRequestHandler(\n 'tools/list',\n async (_request: object): Promise<ListToolsResult> =>\n this.handleListTools(authentication),\n );\n server.server.setRequestHandler(\n 'tools/call',\n async (request: CallToolRequest, sdkContext: ServerContext): Promise<CallToolResult> =>\n this.handleCallTool(server, request, authentication, sdkContext),\n );\n return server;\n }\n\n /** Entry point #2: tools/list has no tool-result channel, so failures are JSON-RPC errors. */\n private async handleListTools(authentication: McpPostAuthentication): Promise<ListToolsResult> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP tools/list entry point; delegates only to WpMcpErrorTranslator\n try {\n return await this.logEdge('tools/list', async () =>\n this.listTools(authentication.credential),\n );\n } catch (err: unknown) {\n const error = toError(err);\n this.translator.toListError(error);\n }\n }\n\n private listTools(credential: VerifiedMcpCredential): ListToolsResult {\n const tools: Tool[] = [];\n for (const tool of this.requireRegistry().tools) {\n if (tool.isVisibleTo(credential.listingRoles)) tools.push(this.toolDefinition(tool));\n }\n return { tools };\n }\n\n private toolDefinition(tool: RegisteredMcpTool): Tool {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema as Tool['inputSchema'],\n outputSchema: tool.outputSchema as Tool['outputSchema'],\n annotations: tool.annotations,\n _meta: { webpiecesRegistryRevision: this.revision },\n };\n }\n\n /**\n * Entry point #3: an unknown tool is a JSON-RPC -32602; every failure after the tool is found\n * is an `isError: true` result, identically for local and remote bindings.\n */\n private async handleCallTool(\n server: WpSdkMcpServer,\n request: CallToolRequest,\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<CallToolResult> {\n const name = request.params.name;\n McpCorrelation.stamp(new McpCorrelation(sdkContext.mcpReq.id, name));\n const tool = this.requireRegistry().find(name);\n if (!tool) throw this.translator.unknownTool(name);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP tools/call entry point; delegates only to WpMcpErrorTranslator\n try {\n const result = await this.callTool(tool, request, authentication, sdkContext);\n result._meta = this.translator.resultMeta(McpCorrelation.requestId());\n return server.server.projectCallToolResult(\n result,\n tool.outputSchema as Record<string, DtoValue>,\n );\n } catch (err: unknown) {\n const error = toError(err);\n return this.translator.toToolCallResult(error);\n }\n }\n\n /** Everything a tool call does. No catch in here: failures propagate to handleCallTool. */\n private async callTool(\n tool: RegisteredMcpTool,\n request: CallToolRequest,\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<CallToolResult> {\n const credential = authentication.credential;\n const endpointJwt =\n tool.binding.topology === 'local'\n ? await this.mintEndpointJwt(tool, authentication)\n : undefined;\n const invocation = new McpInvocationContext(\n sdkContext.mcpReq.id,\n tool.name,\n credential.subject,\n credential.listingRoles,\n AbortSignal.any([sdkContext.mcpReq.signal, authentication.disconnectSignal]),\n this.progressReporter(sdkContext),\n );\n const args = (request.params.arguments ?? {}) as DtoValue;\n const value = await this.dispatcher.call(\n tool,\n args,\n credential,\n invocation,\n endpointJwt?.token,\n );\n const outputFailure = this.requireRegistry().schemaBuilder.validate(\n tool.responseClass,\n value,\n );\n if (outputFailure) {\n throw new ApiImplementationError(\n `MCP output schema violation for ${tool.apiClass.name}.${tool.methodName}: ${outputFailure.message}`,\n );\n }\n const structured = this.record(value);\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n structuredContent: structured,\n };\n }\n\n private async mintEndpointJwt(\n tool: RegisteredMcpTool,\n authentication: McpPostAuthentication,\n ): Promise<MintedJwt> {\n const descriptor = new McpEndpointDescriptor(\n tool.name,\n tool.apiClass.name,\n tool.methodName,\n );\n const minted = await this.config.endpointJwtAuthority.mint(\n this.config.endpointMintRequest(authentication.credential, descriptor),\n );\n const now = Math.floor(Date.now() / 1000);\n if (minted.token === authentication.accessToken) {\n throw new ApiImplementationError(\n `MCP access-token passthrough is forbidden (endpoint JWT for ${tool.name}).`,\n );\n }\n if (\n minted.expiresAtEpochSeconds <= now ||\n minted.expiresAtEpochSeconds - now > this.config.maxEndpointJwtLifetimeSeconds\n ) {\n throw new ApiImplementationError(\n `MCP endpoint JWT lifetime is invalid for ${tool.name}.`,\n );\n }\n return minted;\n }\n\n private validateCredential(credential: VerifiedMcpCredential): void {\n const now = Math.floor(Date.now() / 1000);\n const reject = (reason: string): never => {\n throw new ApiUnauthorizedError(`MCP access token rejected: ${reason}`);\n };\n if (credential.subject.trim() === '') reject('no subject');\n const stamps = [\n credential.issuedAtEpochSeconds,\n credential.expiresAtEpochSeconds,\n credential.accountValidatedAtEpochSeconds,\n ];\n if (!stamps.every(Number.isFinite)) reject('security timestamps must be finite');\n if (credential.resource !== this.config.resource) reject('resource mismatch');\n if (!this.config.authorizationServers.includes(credential.issuer)) {\n reject('issuer is not trusted');\n }\n if (credential.issuedAtEpochSeconds > now || credential.expiresAtEpochSeconds <= now) {\n reject('outside its valid time window');\n }\n const lifetime = credential.expiresAtEpochSeconds - credential.issuedAtEpochSeconds;\n if (lifetime > MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) reject('lifetime exceeds 30 days');\n for (const scope of this.config.requiredScopes) {\n if (!credential.scopes.includes(scope)) reject(`missing required scope '${scope}'`);\n }\n const validatedAge = now - credential.accountValidatedAtEpochSeconds;\n if (\n credential.accountValidatedAtEpochSeconds > now ||\n validatedAge > this.config.maxAccountValidationAgeSeconds\n ) {\n reject('account authorization state is not fresh enough for dispatch');\n }\n }\n\n private authInfo(authentication: McpPostAuthentication): AuthInfo {\n const credential = authentication.credential;\n return {\n token: authentication.accessToken,\n clientId: credential.subject,\n scopes: [...credential.scopes],\n expiresAt: credential.expiresAtEpochSeconds,\n resource: new URL(credential.resource),\n extra: { webpiecesAuthentication: authentication },\n };\n }\n\n private progressReporter(context: ServerContext): McpProgressReporter | undefined {\n const token = context.mcpReq._meta?.progressToken;\n if (token === undefined) return undefined;\n return async (progress: number, total?: number, message?: string): Promise<void> => {\n await context.mcpReq.notify({\n method: 'notifications/progress',\n params: this.progressParams(token, progress, total, message),\n });\n };\n }\n\n private progressParams(\n progressToken: ProgressToken,\n progress: number,\n total?: number,\n message?: string,\n ): Record<string, DtoValue> {\n const params: Record<string, DtoValue> = { progressToken, progress };\n if (total !== undefined) params['total'] = total;\n if (message !== undefined) params['message'] = message;\n return params;\n }\n\n /** What the raw POST body tells us before the SDK has parsed anything: the id, and the tool. */\n private correlationOf(req: Request): McpCorrelation {\n const body = this.bodyObject(req.body);\n const id = body?.['id'];\n const params = this.bodyObject(body?.['params']);\n const name = params?.['name'];\n return new McpCorrelation(\n typeof id === 'string' || typeof id === 'number' ? id : null,\n typeof name === 'string' ? name : undefined,\n );\n }\n\n private challenge(): string {\n return `Bearer resource_metadata=\"${this.config.resourceMetadataUrl}\"`;\n }\n\n private bodyObject(value: DtoValue | undefined): Record<string, DtoValue> | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n return value as Record<string, DtoValue>;\n }\n\n private hasProgressToken(body: DtoValue): boolean {\n const params = this.bodyObject(this.bodyObject(body)?.['params']);\n return this.bodyObject(params?.['_meta'])?.['progressToken'] !== undefined;\n }\n\n private toHttpRequest(req: Request): HttpRequest {\n const headers = new Map<string, string[]>();\n for (const [name, value] of Object.entries(req.headers)) {\n if (typeof value === 'string') headers.set(name, [value]);\n else if (Array.isArray(value)) headers.set(name, value);\n }\n return new HttpRequest(req.method, req.originalUrl || req.path, headers);\n }\n\n private calculateRegistryRevision(registry: McpToolRegistry): string {\n const surface = registry.tools.map((tool: RegisteredMcpTool) => [\n tool.name,\n tool.description,\n tool.inputSchema,\n tool.outputSchema,\n tool.annotations,\n ]);\n return createHash('sha256').update(JSON.stringify(surface)).digest('hex').slice(0, 12);\n }\n\n private requireRegistry(): McpToolRegistry {\n if (!this.registry) throw new Error('Call WpMcpServer.bind(...) before serving requests.');\n return this.registry;\n }\n\n private record(value: DtoValue): Record<string, DtoValue> {\n const jsonValue = JSON.stringify(value);\n if (jsonValue === undefined) {\n throw new ApiImplementationError('MCP structured content is not JSON serializable.');\n }\n const parsed = JSON.parse(jsonValue) as DtoValue;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return { value: parsed };\n }\n return parsed as Record<string, DtoValue>;\n }\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import { McpPostReply, RpcResponse } from './McpHttpTestHarness';
2
+ /**
3
+ * Speaks the 2025-era MCP wire (the `initialize` handshake, no per-request `_meta` envelope) to one
4
+ * bound endpoint, the way every shipping MCP client still does. Kept separate from
5
+ * `McpHttpTestHarness` on purpose: the two eras are different wires, and a harness that could emit
6
+ * either would hide which one a spec is actually proving.
7
+ */
8
+ export declare class LegacyMcpHttpTestHarness {
9
+ readonly baseUrl: string;
10
+ private readonly endpointPath;
11
+ private nextId;
12
+ constructor(baseUrl: string, endpointPath: string);
13
+ /** A legacy JSON-RPC request: having NO `_meta` envelope claim is what classifies it legacy. */
14
+ request(method: string, params?: Record<string, unknown>): Record<string, unknown>;
15
+ initialize(protocolVersion: string): Record<string, unknown>;
16
+ /**
17
+ * `token` null sends no Authorization header. No `mcp-protocol-version` header by default: a
18
+ * 2025 client only sends one after the handshake, and `negotiatedVersion` is how it does.
19
+ */
20
+ post(body: Record<string, unknown>, token?: string | null, negotiatedVersion?: string): Promise<McpPostReply>;
21
+ resultOf(payload: RpcResponse): Record<string, unknown>;
22
+ /** The 2025 leg may answer either JSON or a one-event SSE stream; both carry one JSON-RPC body. */
23
+ private payloadOf;
24
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LegacyMcpHttpTestHarness = void 0;
4
+ const vitest_1 = require("vitest");
5
+ const McpHttpTestHarness_1 = require("./McpHttpTestHarness");
6
+ /**
7
+ * Speaks the 2025-era MCP wire (the `initialize` handshake, no per-request `_meta` envelope) to one
8
+ * bound endpoint, the way every shipping MCP client still does. Kept separate from
9
+ * `McpHttpTestHarness` on purpose: the two eras are different wires, and a harness that could emit
10
+ * either would hide which one a spec is actually proving.
11
+ */
12
+ class LegacyMcpHttpTestHarness {
13
+ baseUrl;
14
+ endpointPath;
15
+ nextId = 0;
16
+ constructor(baseUrl, endpointPath) {
17
+ this.baseUrl = baseUrl;
18
+ this.endpointPath = endpointPath;
19
+ }
20
+ /** A legacy JSON-RPC request: having NO `_meta` envelope claim is what classifies it legacy. */
21
+ request(method, params = {}) {
22
+ return { jsonrpc: '2.0', id: ++this.nextId, method, params };
23
+ }
24
+ initialize(protocolVersion) {
25
+ return this.request('initialize', {
26
+ protocolVersion,
27
+ clientInfo: { name: 'webpieces-legacy-spec', version: '1.0.0' },
28
+ capabilities: {},
29
+ });
30
+ }
31
+ /**
32
+ * `token` null sends no Authorization header. No `mcp-protocol-version` header by default: a
33
+ * 2025 client only sends one after the handshake, and `negotiatedVersion` is how it does.
34
+ */
35
+ async post(body, token = 'mcp-user', negotiatedVersion) {
36
+ const headers = {
37
+ accept: 'application/json, text/event-stream',
38
+ 'content-type': 'application/json',
39
+ };
40
+ if (token !== null)
41
+ headers['authorization'] = `Bearer ${token}`;
42
+ if (negotiatedVersion !== undefined)
43
+ headers['mcp-protocol-version'] = negotiatedVersion;
44
+ const response = await fetch(`${this.baseUrl}${this.endpointPath}`, {
45
+ method: 'POST',
46
+ headers,
47
+ body: JSON.stringify(body),
48
+ });
49
+ return new McpHttpTestHarness_1.McpPostReply(response, await this.payloadOf(response));
50
+ }
51
+ resultOf(payload) {
52
+ (0, vitest_1.expect)(payload.error).toBeUndefined();
53
+ if (!payload.result)
54
+ throw new Error('legacy MCP response has no result');
55
+ return payload.result;
56
+ }
57
+ /** The 2025 leg may answer either JSON or a one-event SSE stream; both carry one JSON-RPC body. */
58
+ async payloadOf(response) {
59
+ const text = await response.text();
60
+ if (text.startsWith('{'))
61
+ return JSON.parse(text);
62
+ const line = text.split('\n').find((candidate) => candidate.startsWith('data: '));
63
+ if (!line)
64
+ return {};
65
+ return JSON.parse(line.slice('data: '.length));
66
+ }
67
+ }
68
+ exports.LegacyMcpHttpTestHarness = LegacyMcpHttpTestHarness;
69
+ //# sourceMappingURL=LegacyMcpHttpTestHarness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LegacyMcpHttpTestHarness.js","sourceRoot":"","sources":["../../../../../../packages/http/mcp-server/src/__tests__/LegacyMcpHttpTestHarness.ts"],"names":[],"mappings":";;;AAAA,mCAAgC;AAChC,6DAAiE;AAEjE;;;;;GAKG;AACH,MAAa,wBAAwB;IAIb;IACC;IAJb,MAAM,GAAG,CAAC,CAAC;IAEnB,YACoB,OAAe,EACd,YAAoB;QADrB,YAAO,GAAP,OAAO,CAAQ;QACd,iBAAY,GAAZ,YAAY,CAAQ;IACtC,CAAC;IAEJ,gGAAgG;IAChG,OAAO,CAAC,MAAc,EAAE,SAAkC,EAAE;QACxD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACjE,CAAC;IAED,UAAU,CAAC,eAAuB;QAC9B,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE;YAC9B,eAAe;YACf,UAAU,EAAE,EAAE,IAAI,EAAE,uBAAuB,EAAE,OAAO,EAAE,OAAO,EAAE;YAC/D,YAAY,EAAE,EAAE;SACnB,CAAC,CAAC;IACP,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI,CACN,IAA6B,EAC7B,QAAuB,UAAU,EACjC,iBAA0B;QAE1B,MAAM,OAAO,GAA2B;YACpC,MAAM,EAAE,qCAAqC;YAC7C,cAAc,EAAE,kBAAkB;SACrC,CAAC;QACF,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,CAAC,eAAe,CAAC,GAAG,UAAU,KAAK,EAAE,CAAC;QACjE,IAAI,iBAAiB,KAAK,SAAS;YAAE,OAAO,CAAC,sBAAsB,CAAC,GAAG,iBAAiB,CAAC;QACzF,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,EAAE;YAChE,MAAM,EAAE,MAAM;YACd,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC7B,CAAC,CAAC;QACH,OAAO,IAAI,iCAAY,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,QAAQ,CAAC,OAAoB;QACzB,IAAA,eAAM,EAAC,OAAO,CAAC,KAAK,CAAC,CAAC,aAAa,EAAE,CAAC;QACtC,IAAI,CAAC,OAAO,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QAC1E,OAAO,OAAO,CAAC,MAAM,CAAC;IAC1B,CAAC;IAED,mGAAmG;IAC3F,KAAK,CAAC,SAAS,CAAC,QAAkB;QACtC,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAgB,CAAC;QACjE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,SAAiB,EAAE,EAAE,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC1F,IAAI,CAAC,IAAI;YAAE,OAAO,EAAE,CAAC;QACrB,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAgB,CAAC;IAClE,CAAC;CACJ;AA1DD,4DA0DC","sourcesContent":["import { expect } from 'vitest';\nimport { McpPostReply, RpcResponse } from './McpHttpTestHarness';\n\n/**\n * Speaks the 2025-era MCP wire (the `initialize` handshake, no per-request `_meta` envelope) to one\n * bound endpoint, the way every shipping MCP client still does. Kept separate from\n * `McpHttpTestHarness` on purpose: the two eras are different wires, and a harness that could emit\n * either would hide which one a spec is actually proving.\n */\nexport class LegacyMcpHttpTestHarness {\n private nextId = 0;\n\n constructor(\n public readonly baseUrl: string,\n private readonly endpointPath: string,\n ) {}\n\n /** A legacy JSON-RPC request: having NO `_meta` envelope claim is what classifies it legacy. */\n request(method: string, params: Record<string, unknown> = {}): Record<string, unknown> {\n return { jsonrpc: '2.0', id: ++this.nextId, method, params };\n }\n\n initialize(protocolVersion: string): Record<string, unknown> {\n return this.request('initialize', {\n protocolVersion,\n clientInfo: { name: 'webpieces-legacy-spec', version: '1.0.0' },\n capabilities: {},\n });\n }\n\n /**\n * `token` null sends no Authorization header. No `mcp-protocol-version` header by default: a\n * 2025 client only sends one after the handshake, and `negotiatedVersion` is how it does.\n */\n async post(\n body: Record<string, unknown>,\n token: string | null = 'mcp-user',\n negotiatedVersion?: string,\n ): Promise<McpPostReply> {\n const headers: Record<string, string> = {\n accept: 'application/json, text/event-stream',\n 'content-type': 'application/json',\n };\n if (token !== null) headers['authorization'] = `Bearer ${token}`;\n if (negotiatedVersion !== undefined) headers['mcp-protocol-version'] = negotiatedVersion;\n const response = await fetch(`${this.baseUrl}${this.endpointPath}`, {\n method: 'POST',\n headers,\n body: JSON.stringify(body),\n });\n return new McpPostReply(response, await this.payloadOf(response));\n }\n\n resultOf(payload: RpcResponse): Record<string, unknown> {\n expect(payload.error).toBeUndefined();\n if (!payload.result) throw new Error('legacy MCP response has no result');\n return payload.result;\n }\n\n /** The 2025 leg may answer either JSON or a one-event SSE stream; both carry one JSON-RPC body. */\n private async payloadOf(response: Response): Promise<RpcResponse> {\n const text = await response.text();\n if (text.startsWith('{')) return JSON.parse(text) as RpcResponse;\n const line = text.split('\\n').find((candidate: string) => candidate.startsWith('data: '));\n if (!line) return {};\n return JSON.parse(line.slice('data: '.length)) as RpcResponse;\n }\n}\n"]}
@@ -5,6 +5,12 @@ import { McpAccessTokenAuthority, VerifiedMcpCredential } from '../McpAuth';
5
5
  export declare const USER_ID: ContextKey<string, "trusted">;
6
6
  export declare const ENDPOINT_PATH = "/app-owned/mcp";
7
7
  export declare const MODERN_VERSION = "2026-07-28";
8
+ /** What every shipping MCP client opens with today — the top of the 2025 negotiation table. */
9
+ export declare const LEGACY_VERSION = "2025-11-25";
10
+ /** An older 2025-era revision the negotiation table still carries. */
11
+ export declare const OLDER_LEGACY_VERSION = "2025-03-26";
12
+ /** A revision NO era knows: the one case a server is still right to refuse. */
13
+ export declare const UNKNOWN_VERSION = "1999-01-01";
8
14
  export declare class SearchRequest {
9
15
  query: string;
10
16
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  var SearchController_1;
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.RecordingLoggerFactory = exports.RecordedLogLine = exports.TestTokenAuthority = exports.TestJwtHook = exports.RemoteSearchClient = exports.SearchController = exports.RemoteSearchApi = exports.SearchApi = exports.SearchResponse = exports.SearchRequest = exports.MODERN_VERSION = exports.ENDPOINT_PATH = exports.USER_ID = void 0;
4
+ exports.RecordingLoggerFactory = exports.RecordedLogLine = exports.TestTokenAuthority = exports.TestJwtHook = exports.RemoteSearchClient = exports.SearchController = exports.RemoteSearchApi = exports.SearchApi = exports.SearchResponse = exports.SearchRequest = exports.UNKNOWN_VERSION = exports.OLDER_LEGACY_VERSION = exports.LEGACY_VERSION = exports.MODERN_VERSION = exports.ENDPOINT_PATH = exports.USER_ID = void 0;
5
5
  const tslib_1 = require("tslib");
6
6
  require("reflect-metadata");
7
7
  const inversify_1 = require("inversify");
@@ -13,6 +13,12 @@ const McpInvocationContext_1 = require("../McpInvocationContext");
13
13
  exports.USER_ID = core_util_1.ContextKey.trusted('mcpSpecUserId', 'proven by a credential verifier');
14
14
  exports.ENDPOINT_PATH = '/app-owned/mcp';
15
15
  exports.MODERN_VERSION = '2026-07-28';
16
+ /** What every shipping MCP client opens with today — the top of the 2025 negotiation table. */
17
+ exports.LEGACY_VERSION = '2025-11-25';
18
+ /** An older 2025-era revision the negotiation table still carries. */
19
+ exports.OLDER_LEGACY_VERSION = '2025-03-26';
20
+ /** A revision NO era knows: the one case a server is still right to refuse. */
21
+ exports.UNKNOWN_VERSION = '1999-01-01';
16
22
  let SearchRequest = class SearchRequest {
17
23
  query;
18
24
  };
@@ -1 +1 @@
1
- {"version":3,"file":"WpMcpServerTestFixtures.js","sourceRoot":"","sources":["../../../../../../packages/http/mcp-server/src/__tests__/WpMcpServerTestFixtures.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport { injectable } from 'inversify';\nimport {\n ApiBadRequestError,\n ApiConnectionError,\n ApiEndUserError,\n ApiPath,\n ApiUnauthorizedError,\n Logger,\n LoggerFactory,\n LogLevel,\n ContextKey,\n ContextTuple,\n Endpoint,\n WpAuthJwt,\n WpAuthOidc,\n WpDto,\n WpDtoField,\n WpDtoFieldOptions,\n WpMcpAuthJwt,\n WpMcpHeader,\n WpMcpTool,\n WpResponseDto,\n} from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\nimport { AuthenticatedCaller, JwtHook, MintedJwt } from '@webpieces/http-routing';\nimport { McpAccessTokenAuthority, VerifiedMcpCredential } from '../McpAuth';\nimport { MCP_INVOCATION_CONTEXT } from '../McpInvocationContext';\n\nexport const USER_ID = ContextKey.trusted<string>(\n 'mcpSpecUserId',\n 'proven by a credential verifier',\n);\nexport const ENDPOINT_PATH = '/app-owned/mcp';\nexport const MODERN_VERSION = '2026-07-28';\n\n@WpDto()\nexport class SearchRequest {\n @WpDtoField(\n new WpDtoFieldOptions(\n 'Search text',\n true,\n undefined,\n false,\n undefined,\n undefined,\n undefined,\n new WpMcpHeader('query'),\n ),\n )\n query!: string;\n}\n\n@WpDto()\nexport class SearchResponse {\n @WpDtoField(new WpDtoFieldOptions('Authenticated user', true))\n userId!: string;\n\n @WpDtoField(new WpDtoFieldOptions('Search result', true))\n result!: string;\n}\n\n@ApiPath('/mcp-spec')\nexport abstract class SearchApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthJwt({ allRolesAllowed: true })\n @Endpoint('/search', 'rpc')\n @WpResponseDto(() => SearchResponse)\n @WpMcpTool({\n name: 'account_search',\n description: 'Search records owned by the authenticated user.',\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n })\n search(_request: SearchRequest): Promise<SearchResponse> {\n throw new Error('contract only');\n }\n\n @WpMcpAuthJwt({ roles: ['admin'] })\n @WpAuthJwt({ roles: ['admin'] })\n @Endpoint('/admin', 'rpc')\n @WpResponseDto(() => SearchResponse)\n @WpMcpTool({\n name: 'admin_search',\n description: 'Administrative search.',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n })\n admin(_request: SearchRequest): Promise<SearchResponse> {\n throw new Error('contract only');\n }\n}\n\n@ApiPath('/remote-spec')\nexport abstract class RemoteSearchApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthOidc()\n @Endpoint('/search', 'rpc')\n @WpResponseDto(() => SearchResponse)\n @WpMcpTool({\n name: 'remote_search',\n description: 'Search a remote binding.',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n })\n search(_request: SearchRequest): Promise<SearchResponse> {\n throw new Error('contract only');\n }\n}\n\n@injectable()\nexport class SearchController extends SearchApi {\n static waitStartedHook?: () => void;\n static waitAbortedHook?: () => void;\n adminInvocations = 0;\n\n override async search(request: SearchRequest): Promise<SearchResponse> {\n if (request.query === 'internal') throw new Error('database password appeared here');\n // A DOWNSTREAM call of ours failed. From the model's seat that is OUR bug, never a kind of\n // its own on the wire — see #959 and ApiErrorBoundary.encode.\n if (request.query === 'downstream') {\n throw new ApiConnectionError('ECONNREFUSED private-host:8443');\n }\n if (request.query === 'human') throw new ApiEndUserError('Safe human message', 'SAFE');\n if (request.query === 'malformed') {\n throw new ApiBadRequestError(\n 'operator-only: column tenant_7 rejected',\n '$.query',\n 'query is malformed',\n );\n }\n const invocation = RequestContext.getTrusted(MCP_INVOCATION_CONTEXT);\n if (request.query === 'progress') {\n await invocation?.reportProgress?.(1, 2, 'halfway');\n }\n if (request.query === 'wait' && invocation) {\n await invocation.reportProgress?.(0, 1, 'waiting');\n SearchController.waitStartedHook?.();\n await new Promise<void>((resolve: () => void) => {\n const finish = (): void => {\n SearchController.waitAbortedHook?.();\n resolve();\n };\n if (invocation.signal.aborted) finish();\n else invocation.signal.addEventListener('abort', finish, { once: true });\n });\n }\n return { userId: RequestContext.getTrusted(USER_ID) ?? 'missing', result: request.query };\n }\n\n override async admin(request: SearchRequest): Promise<SearchResponse> {\n this.adminInvocations += 1;\n return this.search(request);\n }\n}\n\nexport class RemoteSearchClient extends RemoteSearchApi {\n seenAuthorization?: string;\n\n override async search(request: SearchRequest): Promise<SearchResponse> {\n this.seenAuthorization = RequestContext.getRequest()?.getHeader('authorization');\n return { userId: RequestContext.getTrusted(USER_ID) ?? 'missing', result: request.query };\n }\n}\n\nexport class TestJwtHook extends JwtHook<string> {\n mintedTokens: string[] = [];\n lifetimeSeconds = 60;\n failWith?: Error;\n\n override async mint(subject: string): Promise<MintedJwt> {\n if (this.failWith) throw this.failWith;\n const token =\n subject === 'passthrough'\n ? 'mcp-passthrough'\n : `${subject}-endpoint-${this.mintedTokens.length + 1}`;\n this.mintedTokens.push(token);\n return new MintedJwt(token, Math.floor(Date.now() / 1000) + this.lifetimeSeconds);\n }\n\n override async parseJwt(token: string): Promise<AuthenticatedCaller> {\n if (token.startsWith('admin-endpoint-')) {\n return new AuthenticatedCaller(\n 'admin-7',\n ['admin'],\n [new ContextTuple(USER_ID, 'admin-7')],\n );\n }\n if (!token.startsWith('user-endpoint-')) throw new Error('invalid endpoint token');\n return new AuthenticatedCaller('user-7', [], [new ContextTuple(USER_ID, 'user-7')]);\n }\n}\n\nexport class TestTokenAuthority implements McpAccessTokenAuthority<string> {\n verificationCount = 0;\n roles: readonly string[] = [];\n\n async mintAccessToken(_grant: string): Promise<never> {\n throw new Error('not used');\n }\n\n async verifyAccessToken(token: string, resource: string): Promise<VerifiedMcpCredential> {\n this.verificationCount += 1;\n if (token === 'invalid') throw new ApiUnauthorizedError('invalid token');\n if (token === 'authority-bug') throw new Error('SECRET-internal-detail');\n const now = Math.floor(Date.now() / 1000);\n const actualResource =\n token === 'wrong-resource' ? 'https://attacker.example/mcp' : resource;\n const subject = token === 'mcp-passthrough' ? 'passthrough' : 'user';\n const issuer =\n token === 'wrong-issuer' ? 'https://attacker.example' : 'https://login.example.test';\n const expiresAt = token === 'expired' ? now - 1 : now + 60;\n const scopes = token === 'missing-scope' ? [] : ['tools'];\n const validatedAt = token === 'stale-account' ? now - 3_601 : now;\n const credential = new VerifiedMcpCredential(\n subject,\n issuer,\n actualResource,\n token === 'expired' ? now - 61 : now,\n expiresAt,\n scopes,\n validatedAt,\n this.roles,\n [new ContextTuple(USER_ID, `${subject}-delegated`)],\n );\n if (token === 'list-bug') {\n Object.defineProperty(credential, 'listingRoles', {\n get: (): readonly string[] => {\n throw new Error('SECRET-internal-detail');\n },\n });\n }\n return credential;\n }\n}\n\nexport class RecordedLogLine {\n constructor(\n public readonly logger: string,\n public readonly level: LogLevel,\n public readonly message: string,\n public readonly error?: Error,\n ) {}\n\n /** The rendered line an operator would see, including an attached error's message and stack. */\n rendered(): string {\n return `${this.message} ${this.error?.message ?? ''} ${this.error?.stack ?? ''}`;\n }\n}\n\nclass RecordingLogger implements Logger {\n constructor(\n private readonly name: string,\n private readonly lines: RecordedLogLine[],\n ) {}\n\n trace(message: string, err?: Error): void {\n this.lines.push(new RecordedLogLine(this.name, 'trace', message, err));\n }\n\n debug(message: string, err?: Error): void {\n this.lines.push(new RecordedLogLine(this.name, 'debug', message, err));\n }\n\n info(message: string, err?: Error): void {\n this.lines.push(new RecordedLogLine(this.name, 'info', message, err));\n }\n\n warn(message: string, err?: Error): void {\n this.lines.push(new RecordedLogLine(this.name, 'warn', message, err));\n }\n\n error(message: string, err?: Error): void {\n this.lines.push(new RecordedLogLine(this.name, 'error', message, err));\n }\n}\n\n/** Captures every log line in-process so tests can count and level-check operator output. */\nexport class RecordingLoggerFactory implements LoggerFactory {\n readonly lines: RecordedLogLine[] = [];\n\n getLogger(name: string): Logger {\n return new RecordingLogger(name, this.lines);\n }\n\n containing(text: string): RecordedLogLine[] {\n return this.lines.filter((line: RecordedLogLine) => line.rendered().includes(text));\n }\n}\n"]}
1
+ {"version":3,"file":"WpMcpServerTestFixtures.js","sourceRoot":"","sources":["../../../../../../packages/http/mcp-server/src/__tests__/WpMcpServerTestFixtures.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport { injectable } from 'inversify';\nimport {\n ApiBadRequestError,\n ApiConnectionError,\n ApiEndUserError,\n ApiPath,\n ApiUnauthorizedError,\n Logger,\n LoggerFactory,\n LogLevel,\n ContextKey,\n ContextTuple,\n Endpoint,\n WpAuthJwt,\n WpAuthOidc,\n WpDto,\n WpDtoField,\n WpDtoFieldOptions,\n WpMcpAuthJwt,\n WpMcpHeader,\n WpMcpTool,\n WpResponseDto,\n} from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\nimport { AuthenticatedCaller, JwtHook, MintedJwt } from '@webpieces/http-routing';\nimport { McpAccessTokenAuthority, VerifiedMcpCredential } from '../McpAuth';\nimport { MCP_INVOCATION_CONTEXT } from '../McpInvocationContext';\n\nexport const USER_ID = ContextKey.trusted<string>(\n 'mcpSpecUserId',\n 'proven by a credential verifier',\n);\nexport const ENDPOINT_PATH = '/app-owned/mcp';\nexport const MODERN_VERSION = '2026-07-28';\n/** What every shipping MCP client opens with today — the top of the 2025 negotiation table. */\nexport const LEGACY_VERSION = '2025-11-25';\n/** An older 2025-era revision the negotiation table still carries. */\nexport const OLDER_LEGACY_VERSION = '2025-03-26';\n/** A revision NO era knows: the one case a server is still right to refuse. */\nexport const UNKNOWN_VERSION = '1999-01-01';\n\n@WpDto()\nexport class SearchRequest {\n @WpDtoField(\n new WpDtoFieldOptions(\n 'Search text',\n true,\n undefined,\n false,\n undefined,\n undefined,\n undefined,\n new WpMcpHeader('query'),\n ),\n )\n query!: string;\n}\n\n@WpDto()\nexport class SearchResponse {\n @WpDtoField(new WpDtoFieldOptions('Authenticated user', true))\n userId!: string;\n\n @WpDtoField(new WpDtoFieldOptions('Search result', true))\n result!: string;\n}\n\n@ApiPath('/mcp-spec')\nexport abstract class SearchApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthJwt({ allRolesAllowed: true })\n @Endpoint('/search', 'rpc')\n @WpResponseDto(() => SearchResponse)\n @WpMcpTool({\n name: 'account_search',\n description: 'Search records owned by the authenticated user.',\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n })\n search(_request: SearchRequest): Promise<SearchResponse> {\n throw new Error('contract only');\n }\n\n @WpMcpAuthJwt({ roles: ['admin'] })\n @WpAuthJwt({ roles: ['admin'] })\n @Endpoint('/admin', 'rpc')\n @WpResponseDto(() => SearchResponse)\n @WpMcpTool({\n name: 'admin_search',\n description: 'Administrative search.',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n })\n admin(_request: SearchRequest): Promise<SearchResponse> {\n throw new Error('contract only');\n }\n}\n\n@ApiPath('/remote-spec')\nexport abstract class RemoteSearchApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthOidc()\n @Endpoint('/search', 'rpc')\n @WpResponseDto(() => SearchResponse)\n @WpMcpTool({\n name: 'remote_search',\n description: 'Search a remote binding.',\n readOnlyHint: false,\n destructiveHint: false,\n idempotentHint: false,\n openWorldHint: false,\n })\n search(_request: SearchRequest): Promise<SearchResponse> {\n throw new Error('contract only');\n }\n}\n\n@injectable()\nexport class SearchController extends SearchApi {\n static waitStartedHook?: () => void;\n static waitAbortedHook?: () => void;\n adminInvocations = 0;\n\n override async search(request: SearchRequest): Promise<SearchResponse> {\n if (request.query === 'internal') throw new Error('database password appeared here');\n // A DOWNSTREAM call of ours failed. From the model's seat that is OUR bug, never a kind of\n // its own on the wire — see #959 and ApiErrorBoundary.encode.\n if (request.query === 'downstream') {\n throw new ApiConnectionError('ECONNREFUSED private-host:8443');\n }\n if (request.query === 'human') throw new ApiEndUserError('Safe human message', 'SAFE');\n if (request.query === 'malformed') {\n throw new ApiBadRequestError(\n 'operator-only: column tenant_7 rejected',\n '$.query',\n 'query is malformed',\n );\n }\n const invocation = RequestContext.getTrusted(MCP_INVOCATION_CONTEXT);\n if (request.query === 'progress') {\n await invocation?.reportProgress?.(1, 2, 'halfway');\n }\n if (request.query === 'wait' && invocation) {\n await invocation.reportProgress?.(0, 1, 'waiting');\n SearchController.waitStartedHook?.();\n await new Promise<void>((resolve: () => void) => {\n const finish = (): void => {\n SearchController.waitAbortedHook?.();\n resolve();\n };\n if (invocation.signal.aborted) finish();\n else invocation.signal.addEventListener('abort', finish, { once: true });\n });\n }\n return { userId: RequestContext.getTrusted(USER_ID) ?? 'missing', result: request.query };\n }\n\n override async admin(request: SearchRequest): Promise<SearchResponse> {\n this.adminInvocations += 1;\n return this.search(request);\n }\n}\n\nexport class RemoteSearchClient extends RemoteSearchApi {\n seenAuthorization?: string;\n\n override async search(request: SearchRequest): Promise<SearchResponse> {\n this.seenAuthorization = RequestContext.getRequest()?.getHeader('authorization');\n return { userId: RequestContext.getTrusted(USER_ID) ?? 'missing', result: request.query };\n }\n}\n\nexport class TestJwtHook extends JwtHook<string> {\n mintedTokens: string[] = [];\n lifetimeSeconds = 60;\n failWith?: Error;\n\n override async mint(subject: string): Promise<MintedJwt> {\n if (this.failWith) throw this.failWith;\n const token =\n subject === 'passthrough'\n ? 'mcp-passthrough'\n : `${subject}-endpoint-${this.mintedTokens.length + 1}`;\n this.mintedTokens.push(token);\n return new MintedJwt(token, Math.floor(Date.now() / 1000) + this.lifetimeSeconds);\n }\n\n override async parseJwt(token: string): Promise<AuthenticatedCaller> {\n if (token.startsWith('admin-endpoint-')) {\n return new AuthenticatedCaller(\n 'admin-7',\n ['admin'],\n [new ContextTuple(USER_ID, 'admin-7')],\n );\n }\n if (!token.startsWith('user-endpoint-')) throw new Error('invalid endpoint token');\n return new AuthenticatedCaller('user-7', [], [new ContextTuple(USER_ID, 'user-7')]);\n }\n}\n\nexport class TestTokenAuthority implements McpAccessTokenAuthority<string> {\n verificationCount = 0;\n roles: readonly string[] = [];\n\n async mintAccessToken(_grant: string): Promise<never> {\n throw new Error('not used');\n }\n\n async verifyAccessToken(token: string, resource: string): Promise<VerifiedMcpCredential> {\n this.verificationCount += 1;\n if (token === 'invalid') throw new ApiUnauthorizedError('invalid token');\n if (token === 'authority-bug') throw new Error('SECRET-internal-detail');\n const now = Math.floor(Date.now() / 1000);\n const actualResource =\n token === 'wrong-resource' ? 'https://attacker.example/mcp' : resource;\n const subject = token === 'mcp-passthrough' ? 'passthrough' : 'user';\n const issuer =\n token === 'wrong-issuer' ? 'https://attacker.example' : 'https://login.example.test';\n const expiresAt = token === 'expired' ? now - 1 : now + 60;\n const scopes = token === 'missing-scope' ? [] : ['tools'];\n const validatedAt = token === 'stale-account' ? now - 3_601 : now;\n const credential = new VerifiedMcpCredential(\n subject,\n issuer,\n actualResource,\n token === 'expired' ? now - 61 : now,\n expiresAt,\n scopes,\n validatedAt,\n this.roles,\n [new ContextTuple(USER_ID, `${subject}-delegated`)],\n );\n if (token === 'list-bug') {\n Object.defineProperty(credential, 'listingRoles', {\n get: (): readonly string[] => {\n throw new Error('SECRET-internal-detail');\n },\n });\n }\n return credential;\n }\n}\n\nexport class RecordedLogLine {\n constructor(\n public readonly logger: string,\n public readonly level: LogLevel,\n public readonly message: string,\n public readonly error?: Error,\n ) {}\n\n /** The rendered line an operator would see, including an attached error's message and stack. */\n rendered(): string {\n return `${this.message} ${this.error?.message ?? ''} ${this.error?.stack ?? ''}`;\n }\n}\n\nclass RecordingLogger implements Logger {\n constructor(\n private readonly name: string,\n private readonly lines: RecordedLogLine[],\n ) {}\n\n trace(message: string, err?: Error): void {\n this.lines.push(new RecordedLogLine(this.name, 'trace', message, err));\n }\n\n debug(message: string, err?: Error): void {\n this.lines.push(new RecordedLogLine(this.name, 'debug', message, err));\n }\n\n info(message: string, err?: Error): void {\n this.lines.push(new RecordedLogLine(this.name, 'info', message, err));\n }\n\n warn(message: string, err?: Error): void {\n this.lines.push(new RecordedLogLine(this.name, 'warn', message, err));\n }\n\n error(message: string, err?: Error): void {\n this.lines.push(new RecordedLogLine(this.name, 'error', message, err));\n }\n}\n\n/** Captures every log line in-process so tests can count and level-check operator output. */\nexport class RecordingLoggerFactory implements LoggerFactory {\n readonly lines: RecordedLogLine[] = [];\n\n getLogger(name: string): Logger {\n return new RecordingLogger(name, this.lines);\n }\n\n containing(text: string): RecordedLogLine[] {\n return this.lines.filter((line: RecordedLogLine) => line.rendered().includes(text));\n }\n}\n"]}