@webpieces/mcp-server 0.4.989 → 0.4.991
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 +199 -42
- package/package.json +7 -6
- package/src/McpApiDispatcher.d.ts +6 -3
- package/src/McpApiDispatcher.js +6 -2
- package/src/McpApiDispatcher.js.map +1 -1
- package/src/McpAuth.d.ts +115 -56
- package/src/McpAuth.js +180 -111
- package/src/McpAuth.js.map +1 -1
- package/src/McpAuthorityCompileAssertions.d.ts +12 -2
- package/src/McpAuthorityCompileAssertions.js +32 -4
- package/src/McpAuthorityCompileAssertions.js.map +1 -1
- package/src/McpBindOptions.d.ts +5 -20
- package/src/McpBindOptions.js +5 -15
- package/src/McpBindOptions.js.map +1 -1
- package/src/McpClientSet.d.ts +35 -0
- package/src/McpClientSet.js +86 -0
- package/src/McpClientSet.js.map +1 -0
- package/src/McpDoorAuth.d.ts +117 -0
- package/src/McpDoorAuth.js +455 -0
- package/src/McpDoorAuth.js.map +1 -0
- package/src/McpToolCallRendering.d.ts +5 -4
- package/src/McpToolCallRendering.js.map +1 -1
- package/src/McpToolCatalog.d.ts +1 -1
- package/src/McpToolCatalog.js +1 -1
- package/src/McpToolCatalog.js.map +1 -1
- package/src/McpToolRegistry.js +8 -2
- package/src/McpToolRegistry.js.map +1 -1
- package/src/McpToolset.d.ts +115 -0
- package/src/McpToolset.js +323 -0
- package/src/McpToolset.js.map +1 -0
- package/src/WpMcpErrorTranslator.d.ts +5 -7
- package/src/WpMcpErrorTranslator.js +7 -13
- package/src/WpMcpErrorTranslator.js.map +1 -1
- package/src/WpMcpInMemory.d.ts +30 -0
- package/src/WpMcpInMemory.js +97 -0
- package/src/WpMcpInMemory.js.map +1 -0
- package/src/WpMcpRemote.d.ts +23 -0
- package/src/WpMcpRemote.js +89 -0
- package/src/WpMcpRemote.js.map +1 -0
- package/src/WpMcpServer.d.ts +45 -76
- package/src/WpMcpServer.js +101 -327
- package/src/WpMcpServer.js.map +1 -1
- package/src/__tests__/McpTransportFixtures.d.ts +107 -0
- package/src/__tests__/McpTransportFixtures.js +266 -0
- package/src/__tests__/McpTransportFixtures.js.map +1 -0
- package/src/index.d.ts +9 -2
- package/src/index.js +17 -1
- package/src/index.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpToolCallRendering.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpToolCallRendering.ts"],"names":[],"mappings":"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","sourcesContent":["import { CallToolResult } from '@modelcontextprotocol/server';\nimport {\n ApiErrorBoundary,\n ApiErrorPayload,\n ApiMethodInfo,\n CallOutcome,\n ContextKey,\n DtoValue,\n EndpointOperation,\n WebpiecesCoreHeaders,\n ApiRegistry,\n} from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\nimport { McpProtocol } from './McpProtocol';\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 /**\n * The contract CLASS this request is classified under: the resolved tool's bound `@ApiPath`\n * contract, or {@link McpProtocol} while no tool is resolved (a protocol step, an unknown tool).\n */\n public readonly contract: Function,\n public readonly toolName?: string,\n public readonly operation?: EndpointOperation,\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, McpProtocol);\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 category: 'bug' | 'caller' | 'dependency' | 'temporary' | 'access',\n public readonly retry:\n | 'never'\n | 'after-correction'\n | 'after-delay'\n | 'safe'\n | 'unsafe-outcome-unknown',\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/** Explicit content-level envelope for clients that hide CallToolResult.isError. */\nexport class ModelVisibleToolErrorEnvelope {\n constructor(public readonly error: ModelVisibleToolError) {}\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 `ErrorTranslator` an app\n * registers on `ApiRegistry` for HTTP and the `IpcErrorTranslator` it registers on `IpcRegistry`.\n * Register one with {@link McpRegistry.setErrorTranslator}.\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 * # There is NO `fromWire`, and that is the protocol, not an oversight\n *\n * The HTTP and IPC translators have two halves because webpieces sits on BOTH ends of those wires: it\n * writes the response AND, in another process, reads one. **webpieces is never the MCP client.** The\n * client is Claude (or another model host); there is no return path for webpieces to translate, so\n * the pair is one-directional by nature. Do not \"restore the symmetry\" by adding a half with nothing\n * to call it.\n *\n * # The pre-SDK boundary does NOT share the HTTP `ErrorTranslator`, despite the identical type\n *\n * `WpMcpErrorTranslator.toBearerBoundaryResponse` returns an `HttpResponseDto`, exactly like\n * `ErrorTranslator.toWire` — and it must STILL not be routed through it. Two reasons, both hard:\n *\n * - its body must stay JSON-RPC shaped (`McpHttpErrorBody`) or an MCP client cannot parse the reply\n * at all, whereas an app's HTTP translator publishes the app's own REST envelope;\n * - it emits the `401 + WWW-Authenticate: Bearer resource_metadata=...` that MCP clients use for OAuth\n * discovery, so an app rewriting it breaks connector onboarding.\n *\n * Same TYPE, different OWNER. `tools/list` is framework-owned for the same reason.\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().toWire(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.\n */\nexport interface McpErrorTranslator {\n /** An error in, the whole `tools/call` reply out. Named for its HTTP and IPC twins. */\n toWire(error: Error): CallToolResult;\n}\n\n/**\n * webpieces' DEFAULT `tools/call` rendering, and what {@link McpRegistry} holds until an app installs\n * its own. Public so an app's own {@link McpErrorTranslator} can DECLINE an error by delegating to it\n * — the MCP twin of `WebpiecesDefaultErrorTranslator.toWire` 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 McpErrorTranslator {\n private readonly boundary = new ApiErrorBoundary();\n\n toWire(error: Error): CallToolResult {\n const requestId = McpCorrelation.requestId();\n const visible = new ModelVisibleToolErrorEnvelope(\n this.modelVisible(error, this.boundary.encode(error), requestId),\n );\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 /**\n * MCP has no retry table of its own: the advice is f(outcome, operation, retryAfterSeconds), and\n * the outcome is the ONE verdict `ApiRegistry.classify` gives every other reader (RetryStrategy,\n * LogApiCall). A TRANSIENT_FAILURE becomes operation-aware retry guidance; everything else is\n * rendered by kind, with `retry: 'never'` or `'after-correction'`.\n *\n * The one thing decided before the classifier is the published `implementation` kind: the\n * boundary has already ruled that this server is at fault — including a caller-local\n * `ApiConnectionError` (#959) — so it is a bug, and a model is never told a bug is worth retrying.\n */\n private modelVisible(\n error: Error,\n payload: ApiErrorPayload,\n requestId: string,\n ): ModelVisibleToolError {\n const support = `Give requestId ${requestId} to support so the internal failure can be located and fixed.`;\n if (payload.kind === 'implementation') {\n return this.bug(payload, requestId, support);\n }\n const correlation = McpCorrelation.current();\n const toolName = correlation.toolName ?? 'unknown';\n const methodInfo = new ApiMethodInfo('server', 'WpMcpServer', toolName, correlation.contract);\n if (ApiRegistry.classify(error, methodInfo) === CallOutcome.TRANSIENT_FAILURE) {\n return this.transient(payload, requestId, this.transientMessage(payload));\n }\n return this.settled(payload, requestId, support);\n }\n\n /** Rendering for an outcome that repeating will not change: EXPECTED or FAILURE. */\n private settled(\n payload: ApiErrorPayload,\n requestId: string,\n support: string,\n ): ModelVisibleToolError {\n switch (payload.kind) {\n case 'end-user':\n return this.visible(\n payload,\n requestId,\n 'caller',\n 'after-correction',\n payload.message,\n );\n case 'bad-request':\n return this.visible(\n payload,\n requestId,\n 'caller',\n 'after-correction',\n payload.callerMessage ?? payload.message,\n );\n case 'unauthorized':\n case 'forbidden':\n return this.visible(\n payload,\n requestId,\n 'access',\n 'after-correction',\n 'Access was denied. Retrying unchanged credentials and arguments will not help; correct the credentials or permissions first.',\n );\n case 'not-found':\n case 'endpoint-not-found':\n case 'conflict':\n case 'unprocessable':\n case 'precondition-failed':\n case 'unsupported-media-type':\n return this.visible(\n payload,\n requestId,\n 'caller',\n 'after-correction',\n `${payload.message}. Correct the request or current resource state before trying again.`,\n );\n default:\n return this.settledFailure(payload, requestId, support);\n }\n }\n\n /** A FAILURE the caller cannot correct: a missing operation, an exhausted or broken dependency. */\n private settledFailure(\n payload: ApiErrorPayload,\n requestId: string,\n support: string,\n ): ModelVisibleToolError {\n switch (payload.kind) {\n case 'not-implemented':\n return this.visible(\n payload,\n requestId,\n 'bug',\n 'never',\n `The tool does not implement this operation. Retrying or changing arguments will not help. ${support}`,\n );\n case 'retries-exhausted':\n return this.visible(\n payload,\n requestId,\n 'dependency',\n 'never',\n `A downstream dependency kept failing after this service already retried it. Retrying now will not help. If the failure persists, ${support}`,\n );\n case 'coded':\n return this.coded(payload, requestId, support);\n default:\n return this.visible(\n payload,\n requestId,\n 'dependency',\n 'never',\n `A downstream dependency failed. Retrying or changing arguments will not help. If the failure persists, ${support}`,\n );\n }\n }\n\n /** The situation text for a TRANSIENT_FAILURE; `transient` adds the operation-aware advice. */\n private transientMessage(payload: ApiErrorPayload): string {\n switch (payload.kind) {\n case 'request-timeout':\n case 'dependency-timeout':\n return 'The operation timed out; the previous operation may already have completed.';\n case 'bad-gateway':\n return 'A gateway returned an invalid upstream response; the previous operation may already have completed.';\n case 'rate-limited':\n case 'dependency-backoff':\n return payload.retryAfterSeconds === undefined\n ? 'The operation was throttled. Do not retry immediately.'\n : `The operation was throttled. Wait at least ${payload.retryAfterSeconds} seconds before retrying.`;\n case 'coded':\n return `The operation failed with HTTP ${payload.statusCode ?? 500}; the previous operation's outcome may be unknown.`;\n default:\n return 'A required service is temporarily unavailable; the previous operation may already have completed.';\n }\n }\n\n private coded(\n payload: ApiErrorPayload,\n requestId: string,\n support: string,\n ): ModelVisibleToolError {\n if ((payload.statusCode ?? 500) >= 500) {\n return this.visible(\n payload,\n requestId,\n 'dependency',\n 'never',\n `A downstream service failed with HTTP ${payload.statusCode ?? 500}. Retrying or changing arguments will not help. ${support}`,\n );\n }\n return this.visible(payload, requestId, 'caller', 'after-correction', payload.message);\n }\n\n private bug(\n payload: ApiErrorPayload,\n requestId: string,\n support: string,\n ): ModelVisibleToolError {\n return this.visible(\n payload,\n requestId,\n 'bug',\n 'never',\n `The tool ${McpCorrelation.current().toolName ?? 'unknown'} encountered an internal bug. ` +\n `Retrying or changing the arguments will not help. ${support}`,\n );\n }\n\n private transient(\n payload: ApiErrorPayload,\n requestId: string,\n message: string,\n ): ModelVisibleToolError {\n const operation = McpCorrelation.current().operation ?? 'write';\n if (operation === 'write') {\n return this.visible(\n payload,\n requestId,\n 'temporary',\n 'unsafe-outcome-unknown',\n `${message} This endpoint is a non-idempotent write, so retrying could duplicate it; verify the outcome before retrying.`,\n );\n }\n const retry = payload.retryAfterSeconds === undefined ? 'safe' : 'after-delay';\n return this.visible(\n payload,\n requestId,\n 'temporary',\n retry,\n `${message} This endpoint is ${operation === 'read' ? 'read-only' : 'idempotent'}, so retrying is safe${payload.retryAfterSeconds === undefined ? ' after a delay' : ` after ${payload.retryAfterSeconds} seconds`}.`,\n );\n }\n\n private visible(\n payload: ApiErrorPayload,\n requestId: string,\n category: ModelVisibleToolError['category'],\n retry: ModelVisibleToolError['retry'],\n message: string,\n ): ModelVisibleToolError {\n return new ModelVisibleToolError(\n payload.kind,\n category,\n retry,\n message,\n requestId,\n payload.field,\n payload.callerMessage,\n payload.errorCode,\n payload.retryAfterSeconds,\n payload.statusCode,\n );\n }\n}\n\n"]}
|
|
1
|
+
{"version":3,"file":"McpToolCallRendering.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpToolCallRendering.ts"],"names":[],"mappings":"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","sourcesContent":["import { CallToolResult } from '@modelcontextprotocol/server';\nimport {\n ApiErrorBoundary,\n ApiErrorPayload,\n ApiMethodInfo,\n CallOutcome,\n ContextKey,\n DtoValue,\n EndpointOperation,\n WebpiecesCoreHeaders,\n ApiRegistry,\n} from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\nimport { McpProtocol } from './McpProtocol';\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 /**\n * The contract CLASS this request is classified under: the resolved tool's bound `@ApiPath`\n * contract, or {@link McpProtocol} while no tool is resolved (a protocol step, an unknown tool).\n */\n public readonly contract: Function,\n public readonly toolName?: string,\n public readonly operation?: EndpointOperation,\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, McpProtocol);\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 category: 'bug' | 'caller' | 'dependency' | 'temporary' | 'access',\n public readonly retry:\n | 'never'\n | 'after-correction'\n | 'after-delay'\n | 'safe'\n | 'unsafe-outcome-unknown',\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/** Explicit content-level envelope for clients that hide CallToolResult.isError. */\nexport class ModelVisibleToolErrorEnvelope {\n constructor(public readonly error: ModelVisibleToolError) {}\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 `ErrorTranslator` an app\n * registers on `ApiRegistry` for HTTP and the `IpcErrorTranslator` it registers on `IpcRegistry`.\n * Register one with {@link McpRegistry.setErrorTranslator}.\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 * # There is NO `fromWire`, and that is the protocol, not an oversight\n *\n * The HTTP and IPC translators have two halves because webpieces sits on BOTH ends of those wires: it\n * writes the response AND, in another process, reads one. An MCP tool result is read by a MODEL, not\n * decoded back into an exception: the model host (Claude, or an agent holding `WpMcpInMemory` /\n * `WpMcpRemote`, which are the SDK's own `Client`) hands the `CallToolResult` to the model as-is. There\n * is no return path for webpieces to translate, so the pair is one-directional by nature. Do not\n * \"restore the symmetry\" by adding a half with nothing to call it.\n *\n * # The pre-SDK boundary does NOT share the HTTP `ErrorTranslator`, despite the identical type\n *\n * `WpMcpErrorTranslator.toBearerBoundaryResponse` returns an `HttpResponseDto`, exactly like\n * `ErrorTranslator.toWire` — and it must STILL not be routed through it. Two reasons, both hard:\n *\n * - its body must stay JSON-RPC shaped (`McpHttpErrorBody`) or an MCP client cannot parse the reply\n * at all, whereas an app's HTTP translator publishes the app's own REST envelope;\n * - it emits the `401 + WWW-Authenticate: Bearer resource_metadata=...` that MCP clients use for OAuth\n * discovery, so an app rewriting it breaks connector onboarding.\n *\n * Same TYPE, different OWNER. `tools/list` is framework-owned for the same reason.\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().toWire(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.\n */\nexport interface McpErrorTranslator {\n /** An error in, the whole `tools/call` reply out. Named for its HTTP and IPC twins. */\n toWire(error: Error): CallToolResult;\n}\n\n/**\n * webpieces' DEFAULT `tools/call` rendering, and what {@link McpRegistry} holds until an app installs\n * its own. Public so an app's own {@link McpErrorTranslator} can DECLINE an error by delegating to it\n * — the MCP twin of `WebpiecesDefaultErrorTranslator.toWire` 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 McpErrorTranslator {\n private readonly boundary = new ApiErrorBoundary();\n\n toWire(error: Error): CallToolResult {\n const requestId = McpCorrelation.requestId();\n const visible = new ModelVisibleToolErrorEnvelope(\n this.modelVisible(error, this.boundary.encode(error), requestId),\n );\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 /**\n * MCP has no retry table of its own: the advice is f(outcome, operation, retryAfterSeconds), and\n * the outcome is the ONE verdict `ApiRegistry.classify` gives every other reader (RetryStrategy,\n * LogApiCall). A TRANSIENT_FAILURE becomes operation-aware retry guidance; everything else is\n * rendered by kind, with `retry: 'never'` or `'after-correction'`.\n *\n * The one thing decided before the classifier is the published `implementation` kind: the\n * boundary has already ruled that this server is at fault — including a caller-local\n * `ApiConnectionError` (#959) — so it is a bug, and a model is never told a bug is worth retrying.\n */\n private modelVisible(\n error: Error,\n payload: ApiErrorPayload,\n requestId: string,\n ): ModelVisibleToolError {\n const support = `Give requestId ${requestId} to support so the internal failure can be located and fixed.`;\n if (payload.kind === 'implementation') {\n return this.bug(payload, requestId, support);\n }\n const correlation = McpCorrelation.current();\n const toolName = correlation.toolName ?? 'unknown';\n const methodInfo = new ApiMethodInfo('server', 'WpMcpServer', toolName, correlation.contract);\n if (ApiRegistry.classify(error, methodInfo) === CallOutcome.TRANSIENT_FAILURE) {\n return this.transient(payload, requestId, this.transientMessage(payload));\n }\n return this.settled(payload, requestId, support);\n }\n\n /** Rendering for an outcome that repeating will not change: EXPECTED or FAILURE. */\n private settled(\n payload: ApiErrorPayload,\n requestId: string,\n support: string,\n ): ModelVisibleToolError {\n switch (payload.kind) {\n case 'end-user':\n return this.visible(\n payload,\n requestId,\n 'caller',\n 'after-correction',\n payload.message,\n );\n case 'bad-request':\n return this.visible(\n payload,\n requestId,\n 'caller',\n 'after-correction',\n payload.callerMessage ?? payload.message,\n );\n case 'unauthorized':\n case 'forbidden':\n return this.visible(\n payload,\n requestId,\n 'access',\n 'after-correction',\n 'Access was denied. Retrying unchanged credentials and arguments will not help; correct the credentials or permissions first.',\n );\n case 'not-found':\n case 'endpoint-not-found':\n case 'conflict':\n case 'unprocessable':\n case 'precondition-failed':\n case 'unsupported-media-type':\n return this.visible(\n payload,\n requestId,\n 'caller',\n 'after-correction',\n `${payload.message}. Correct the request or current resource state before trying again.`,\n );\n default:\n return this.settledFailure(payload, requestId, support);\n }\n }\n\n /** A FAILURE the caller cannot correct: a missing operation, an exhausted or broken dependency. */\n private settledFailure(\n payload: ApiErrorPayload,\n requestId: string,\n support: string,\n ): ModelVisibleToolError {\n switch (payload.kind) {\n case 'not-implemented':\n return this.visible(\n payload,\n requestId,\n 'bug',\n 'never',\n `The tool does not implement this operation. Retrying or changing arguments will not help. ${support}`,\n );\n case 'retries-exhausted':\n return this.visible(\n payload,\n requestId,\n 'dependency',\n 'never',\n `A downstream dependency kept failing after this service already retried it. Retrying now will not help. If the failure persists, ${support}`,\n );\n case 'coded':\n return this.coded(payload, requestId, support);\n default:\n return this.visible(\n payload,\n requestId,\n 'dependency',\n 'never',\n `A downstream dependency failed. Retrying or changing arguments will not help. If the failure persists, ${support}`,\n );\n }\n }\n\n /** The situation text for a TRANSIENT_FAILURE; `transient` adds the operation-aware advice. */\n private transientMessage(payload: ApiErrorPayload): string {\n switch (payload.kind) {\n case 'request-timeout':\n case 'dependency-timeout':\n return 'The operation timed out; the previous operation may already have completed.';\n case 'bad-gateway':\n return 'A gateway returned an invalid upstream response; the previous operation may already have completed.';\n case 'rate-limited':\n case 'dependency-backoff':\n return payload.retryAfterSeconds === undefined\n ? 'The operation was throttled. Do not retry immediately.'\n : `The operation was throttled. Wait at least ${payload.retryAfterSeconds} seconds before retrying.`;\n case 'coded':\n return `The operation failed with HTTP ${payload.statusCode ?? 500}; the previous operation's outcome may be unknown.`;\n default:\n return 'A required service is temporarily unavailable; the previous operation may already have completed.';\n }\n }\n\n private coded(\n payload: ApiErrorPayload,\n requestId: string,\n support: string,\n ): ModelVisibleToolError {\n if ((payload.statusCode ?? 500) >= 500) {\n return this.visible(\n payload,\n requestId,\n 'dependency',\n 'never',\n `A downstream service failed with HTTP ${payload.statusCode ?? 500}. Retrying or changing arguments will not help. ${support}`,\n );\n }\n return this.visible(payload, requestId, 'caller', 'after-correction', payload.message);\n }\n\n private bug(\n payload: ApiErrorPayload,\n requestId: string,\n support: string,\n ): ModelVisibleToolError {\n return this.visible(\n payload,\n requestId,\n 'bug',\n 'never',\n `The tool ${McpCorrelation.current().toolName ?? 'unknown'} encountered an internal bug. ` +\n `Retrying or changing the arguments will not help. ${support}`,\n );\n }\n\n private transient(\n payload: ApiErrorPayload,\n requestId: string,\n message: string,\n ): ModelVisibleToolError {\n const operation = McpCorrelation.current().operation ?? 'write';\n if (operation === 'write') {\n return this.visible(\n payload,\n requestId,\n 'temporary',\n 'unsafe-outcome-unknown',\n `${message} This endpoint is a non-idempotent write, so retrying could duplicate it; verify the outcome before retrying.`,\n );\n }\n const retry = payload.retryAfterSeconds === undefined ? 'safe' : 'after-delay';\n return this.visible(\n payload,\n requestId,\n 'temporary',\n retry,\n `${message} This endpoint is ${operation === 'read' ? 'read-only' : 'idempotent'}, so retrying is safe${payload.retryAfterSeconds === undefined ? ' after a delay' : ` after ${payload.retryAfterSeconds} seconds`}.`,\n );\n }\n\n private visible(\n payload: ApiErrorPayload,\n requestId: string,\n category: ModelVisibleToolError['category'],\n retry: ModelVisibleToolError['retry'],\n message: string,\n ): ModelVisibleToolError {\n return new ModelVisibleToolError(\n payload.kind,\n category,\n retry,\n message,\n requestId,\n payload.field,\n payload.callerMessage,\n payload.errorCode,\n payload.retryAfterSeconds,\n payload.statusCode,\n );\n }\n}\n\n"]}
|
package/src/McpToolCatalog.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { McpToolCatalogFile, McpToolDefinition } from '@webpieces/core-util';
|
|
|
3
3
|
* ONE contract's MCP tools as a server boots them: the generated `mcp-<ContractClass>-tools.json`, and
|
|
4
4
|
* the directory it was read from, so a boot refusal can name every directory it searched.
|
|
5
5
|
*
|
|
6
|
-
* `
|
|
6
|
+
* `McpToolset` is handed a LIST of these (its `catalogs` constructor argument), and `McpToolRegistry` checks
|
|
7
7
|
* each `McpApiBinding` against the catalog of exactly its own contract (#1021).
|
|
8
8
|
*
|
|
9
9
|
* Build them with {@link McpToolCatalog.fromPackages}, the one call that works in every environment a
|
package/src/McpToolCatalog.js
CHANGED
|
@@ -13,7 +13,7 @@ const BUILD_IT = 'Generate the api library\'s documents (`nx run <api-lib>:opena
|
|
|
13
13
|
* ONE contract's MCP tools as a server boots them: the generated `mcp-<ContractClass>-tools.json`, and
|
|
14
14
|
* the directory it was read from, so a boot refusal can name every directory it searched.
|
|
15
15
|
*
|
|
16
|
-
* `
|
|
16
|
+
* `McpToolset` is handed a LIST of these (its `catalogs` constructor argument), and `McpToolRegistry` checks
|
|
17
17
|
* each `McpApiBinding` against the catalog of exactly its own contract (#1021).
|
|
18
18
|
*
|
|
19
19
|
* Build them with {@link McpToolCatalog.fromPackages}, the one call that works in every environment a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpToolCatalog.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpToolCatalog.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport {\n GeneratedApiDocsLayout,\n LayoutTargets,\n McpToolCatalogError,\n McpToolCatalogFile,\n McpToolDefinition,\n} from '@webpieces/core-util';\n\n/** How every catalog failure is fixed: build the api library, whose `openapi-generate` writes them. */\nconst BUILD_IT =\n 'Generate the api library\\'s documents (`nx run <api-lib>:openapi-generate`, which builds it first) — ' +\n 'or, for a spec that boots the MCP server, give nx.json\\'s targetDefaults for `test` ' +\n '`\"dependsOn\": [\"^openapi-generate\"]` so every dependent generates it first.';\n\n/** The two fields of an nx `project.json` this loader reads. */\ntype ProjectJson = { readonly name?: string; readonly targets?: LayoutTargets };\n\n/**\n * ONE contract's MCP tools as a server boots them: the generated `mcp-<ContractClass>-tools.json`, and\n * the directory it was read from, so a boot refusal can name every directory it searched.\n *\n * `WpMcpServer` is handed a LIST of these (`McpBindOptions.toolCatalogs`), and `McpToolRegistry` checks\n * each `McpApiBinding` against the catalog of exactly its own contract (#1021).\n *\n * Build them with {@link McpToolCatalog.fromPackages}, the one call that works in every environment a\n * server runs in. The constructor is for a spec that hands the server catalogs it built in memory.\n */\nexport class McpToolCatalog {\n constructor(\n readonly file: McpToolCatalogFile,\n /** Where it was read from — an absolute directory, or a label for a catalog built in memory. */\n readonly directory: string,\n ) {}\n\n get contractName(): string {\n return this.file.contractName;\n }\n\n find(name: string): McpToolDefinition | undefined {\n return this.file.find(name);\n }\n\n names(): readonly string[] {\n return this.file.names();\n }\n\n /**\n * Every MCP tool catalog the named api-library packages carry — ONE call, in every environment:\n *\n * ```typescript\n * McpToolCatalog.fromPackages(['@myorg/lang-website-apis'], __dirname)\n * ```\n *\n * Each package is found the way node's own resolution finds it: `node_modules/<pkg>` in\n * `resolveFrom` and every directory above it, symlinks followed. What that directory holds decides\n * where the files are:\n *\n * - `mcp-*-tools.json` beside its `package.json` → a BUILT or PUBLISHED package (a Docker image that\n * relinks `node_modules/@myorg/*` onto `dist/libraries/**`, or an npm consumer) → read them there;\n * - a `project.json` → a workspace SOURCE directory (pnpm links `node_modules/@myorg/x` onto\n * `libraries/.../x` for local dev and vitest) → read the `outputPath` of the target its\n * `openapi-generate` dependsOn, resolved against the nx workspace root (found by walking up to\n * `nx.json`), and read the files there. That is the SAME lookup the executor used to decide where\n * to write them (`GeneratedApiDocsLayout`).\n *\n * The catalogs are SELF-CONTAINED: every schema a tool reaches is inlined, whichever package\n * declares it, so a catalog never needs another document to be read with it. That stays true under\n * chained OpenAPI generation (#1058), where a contract library's OpenAPI documents `$ref` its DTO\n * libraries' `components.openapi.json` instead of copying their schemas: the generator locates\n * those documents with this SAME lookup — node resolution, then a built directory or a workspace\n * source directory's `outputPath` (there, the target `openapi-components-generate` dependsOn) — and\n * a catalog renders a shared DTO byte-identically to every other catalog that inlines it.\n *\n * @param resolveFrom where node resolution starts — pass `__dirname` of the calling file. It is\n * required because the right answer is the CALLER's `node_modules`: in a pnpm workspace the api\n * library is linked into the server project's own `node_modules`, which neither the process's cwd\n * nor this package's install location can see.\n */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromPackages(packages: readonly string[], resolveFrom: string): McpToolCatalog[] {\n if (packages.length === 0) {\n throw new McpToolCatalogError(\n 'McpToolCatalog.fromPackages was given no packages.',\n \"Name every api library whose contracts this server binds, e.g. fromPackages(['@myorg/my-apis'], __dirname).\",\n );\n }\n const loader = new McpPackageCatalogs(resolveFrom);\n return packages.flatMap((packageName: string) => loader.load(packageName));\n }\n}\n\n/** The filesystem half of {@link McpToolCatalog.fromPackages}: resolve, classify, read. */\nclass McpPackageCatalogs {\n constructor(private readonly resolveFrom: string) {}\n\n load(packageName: string): McpToolCatalog[] {\n const packageDir = this.resolve(packageName);\n if (this.catalogFiles(packageDir).length > 0) {\n return this.read(packageName, packageDir, packageDir);\n }\n if (fs.existsSync(path.join(packageDir, 'project.json'))) {\n return this.read(packageName, packageDir, this.outputDirOfSource(packageName, packageDir));\n }\n throw new McpToolCatalogError(\n `${packageName} resolved to ${packageDir}, which holds neither an mcp-<ContractClass>-tools.json ` +\n '(a built package) nor a project.json (a workspace source directory).',\n BUILD_IT,\n );\n }\n\n /** `<dir>/node_modules/<pkg>/package.json` in `resolveFrom` and every parent — node's own walk. */\n private resolve(packageName: string): string {\n const searched: string[] = [];\n let dir = path.resolve(this.resolveFrom);\n for (;;) {\n const candidate = path.join(dir, 'node_modules', ...packageName.split('/'));\n searched.push(candidate);\n if (fs.existsSync(path.join(candidate, 'package.json'))) return fs.realpathSync(candidate);\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new McpToolCatalogError(\n `${packageName} is not installed where the MCP server can see it. Searched:\\n` +\n searched.map((each: string) => ` ${each}`).join('\\n'),\n `Add ${packageName} as a dependency of the server project, and pass the calling file's ` +\n '__dirname as fromPackages(...)\\'s resolveFrom.',\n );\n }\n\n /**\n * A workspace SOURCE directory: the outputPath of the target its `openapi-generate` dependsOn,\n * against the workspace root — never an assumed `dist/`.\n */\n private outputDirOfSource(packageName: string, packageDir: string): string {\n const workspaceRoot = this.workspaceRootAbove(packageName, packageDir);\n const project = JSON.parse(fs.readFileSync(path.join(packageDir, 'project.json'), 'utf8')) as ProjectJson;\n const projectRoot = path.relative(workspaceRoot, packageDir).split(path.sep).join('/');\n const lookup = new GeneratedApiDocsLayout(projectRoot, project.name ?? projectRoot, project.targets ?? {})\n .outputTarget(GeneratedApiDocsLayout.OPENAPI_TARGET);\n if (lookup.found === undefined) {\n throw new McpToolCatalogError(\n `${packageName} is the workspace source directory ${packageDir}, and its generated MCP tool ` +\n `catalogs cannot be located: ${lookup.problem!.problem}`,\n lookup.problem!.cure,\n );\n }\n return path.resolve(workspaceRoot, lookup.found.outputPath);\n }\n\n private workspaceRootAbove(packageName: string, packageDir: string): string {\n let dir = packageDir;\n for (;;) {\n if (fs.existsSync(path.join(dir, 'nx.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new McpToolCatalogError(\n `${packageName} resolved to ${packageDir}, which has a project.json but no nx.json above it, ` +\n 'so there is no nx workspace to resolve its build outputPath against.',\n 'Link the BUILT package instead (its directory holds the mcp-<ContractClass>-tools.json files).',\n );\n }\n\n private read(packageName: string, packageDir: string, dir: string): McpToolCatalog[] {\n const files = this.catalogFiles(dir);\n if (files.length === 0) {\n throw new McpToolCatalogError(\n `${packageName} (${packageDir}) has no mcp-<ContractClass>-tools.json in ${dir}` +\n (fs.existsSync(dir) ? '.' : ' — the directory does not exist, so the library was never built.'),\n BUILD_IT,\n );\n }\n return files.map(\n (file: string) =>\n new McpToolCatalog(\n McpToolCatalogFile.fromJsonText(file, fs.readFileSync(path.join(dir, file), 'utf8')),\n dir,\n ),\n );\n }\n\n private catalogFiles(dir: string): string[] {\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((file: string) => McpToolCatalogFile.contractNameOf(file) !== undefined)\n .sort();\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"McpToolCatalog.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpToolCatalog.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\nimport * as path from 'path';\nimport {\n GeneratedApiDocsLayout,\n LayoutTargets,\n McpToolCatalogError,\n McpToolCatalogFile,\n McpToolDefinition,\n} from '@webpieces/core-util';\n\n/** How every catalog failure is fixed: build the api library, whose `openapi-generate` writes them. */\nconst BUILD_IT =\n 'Generate the api library\\'s documents (`nx run <api-lib>:openapi-generate`, which builds it first) — ' +\n 'or, for a spec that boots the MCP server, give nx.json\\'s targetDefaults for `test` ' +\n '`\"dependsOn\": [\"^openapi-generate\"]` so every dependent generates it first.';\n\n/** The two fields of an nx `project.json` this loader reads. */\ntype ProjectJson = { readonly name?: string; readonly targets?: LayoutTargets };\n\n/**\n * ONE contract's MCP tools as a server boots them: the generated `mcp-<ContractClass>-tools.json`, and\n * the directory it was read from, so a boot refusal can name every directory it searched.\n *\n * `McpToolset` is handed a LIST of these (its `catalogs` constructor argument), and `McpToolRegistry` checks\n * each `McpApiBinding` against the catalog of exactly its own contract (#1021).\n *\n * Build them with {@link McpToolCatalog.fromPackages}, the one call that works in every environment a\n * server runs in. The constructor is for a spec that hands the server catalogs it built in memory.\n */\nexport class McpToolCatalog {\n constructor(\n readonly file: McpToolCatalogFile,\n /** Where it was read from — an absolute directory, or a label for a catalog built in memory. */\n readonly directory: string,\n ) {}\n\n get contractName(): string {\n return this.file.contractName;\n }\n\n find(name: string): McpToolDefinition | undefined {\n return this.file.find(name);\n }\n\n names(): readonly string[] {\n return this.file.names();\n }\n\n /**\n * Every MCP tool catalog the named api-library packages carry — ONE call, in every environment:\n *\n * ```typescript\n * McpToolCatalog.fromPackages(['@myorg/lang-website-apis'], __dirname)\n * ```\n *\n * Each package is found the way node's own resolution finds it: `node_modules/<pkg>` in\n * `resolveFrom` and every directory above it, symlinks followed. What that directory holds decides\n * where the files are:\n *\n * - `mcp-*-tools.json` beside its `package.json` → a BUILT or PUBLISHED package (a Docker image that\n * relinks `node_modules/@myorg/*` onto `dist/libraries/**`, or an npm consumer) → read them there;\n * - a `project.json` → a workspace SOURCE directory (pnpm links `node_modules/@myorg/x` onto\n * `libraries/.../x` for local dev and vitest) → read the `outputPath` of the target its\n * `openapi-generate` dependsOn, resolved against the nx workspace root (found by walking up to\n * `nx.json`), and read the files there. That is the SAME lookup the executor used to decide where\n * to write them (`GeneratedApiDocsLayout`).\n *\n * The catalogs are SELF-CONTAINED: every schema a tool reaches is inlined, whichever package\n * declares it, so a catalog never needs another document to be read with it. That stays true under\n * chained OpenAPI generation (#1058), where a contract library's OpenAPI documents `$ref` its DTO\n * libraries' `components.openapi.json` instead of copying their schemas: the generator locates\n * those documents with this SAME lookup — node resolution, then a built directory or a workspace\n * source directory's `outputPath` (there, the target `openapi-components-generate` dependsOn) — and\n * a catalog renders a shared DTO byte-identically to every other catalog that inlines it.\n *\n * @param resolveFrom where node resolution starts — pass `__dirname` of the calling file. It is\n * required because the right answer is the CALLER's `node_modules`: in a pnpm workspace the api\n * library is linked into the server project's own `node_modules`, which neither the process's cwd\n * nor this package's install location can see.\n */\n // webpieces-disable no-function-outside-class -- static factory of this class\n static fromPackages(packages: readonly string[], resolveFrom: string): McpToolCatalog[] {\n if (packages.length === 0) {\n throw new McpToolCatalogError(\n 'McpToolCatalog.fromPackages was given no packages.',\n \"Name every api library whose contracts this server binds, e.g. fromPackages(['@myorg/my-apis'], __dirname).\",\n );\n }\n const loader = new McpPackageCatalogs(resolveFrom);\n return packages.flatMap((packageName: string) => loader.load(packageName));\n }\n}\n\n/** The filesystem half of {@link McpToolCatalog.fromPackages}: resolve, classify, read. */\nclass McpPackageCatalogs {\n constructor(private readonly resolveFrom: string) {}\n\n load(packageName: string): McpToolCatalog[] {\n const packageDir = this.resolve(packageName);\n if (this.catalogFiles(packageDir).length > 0) {\n return this.read(packageName, packageDir, packageDir);\n }\n if (fs.existsSync(path.join(packageDir, 'project.json'))) {\n return this.read(packageName, packageDir, this.outputDirOfSource(packageName, packageDir));\n }\n throw new McpToolCatalogError(\n `${packageName} resolved to ${packageDir}, which holds neither an mcp-<ContractClass>-tools.json ` +\n '(a built package) nor a project.json (a workspace source directory).',\n BUILD_IT,\n );\n }\n\n /** `<dir>/node_modules/<pkg>/package.json` in `resolveFrom` and every parent — node's own walk. */\n private resolve(packageName: string): string {\n const searched: string[] = [];\n let dir = path.resolve(this.resolveFrom);\n for (;;) {\n const candidate = path.join(dir, 'node_modules', ...packageName.split('/'));\n searched.push(candidate);\n if (fs.existsSync(path.join(candidate, 'package.json'))) return fs.realpathSync(candidate);\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new McpToolCatalogError(\n `${packageName} is not installed where the MCP server can see it. Searched:\\n` +\n searched.map((each: string) => ` ${each}`).join('\\n'),\n `Add ${packageName} as a dependency of the server project, and pass the calling file's ` +\n '__dirname as fromPackages(...)\\'s resolveFrom.',\n );\n }\n\n /**\n * A workspace SOURCE directory: the outputPath of the target its `openapi-generate` dependsOn,\n * against the workspace root — never an assumed `dist/`.\n */\n private outputDirOfSource(packageName: string, packageDir: string): string {\n const workspaceRoot = this.workspaceRootAbove(packageName, packageDir);\n const project = JSON.parse(fs.readFileSync(path.join(packageDir, 'project.json'), 'utf8')) as ProjectJson;\n const projectRoot = path.relative(workspaceRoot, packageDir).split(path.sep).join('/');\n const lookup = new GeneratedApiDocsLayout(projectRoot, project.name ?? projectRoot, project.targets ?? {})\n .outputTarget(GeneratedApiDocsLayout.OPENAPI_TARGET);\n if (lookup.found === undefined) {\n throw new McpToolCatalogError(\n `${packageName} is the workspace source directory ${packageDir}, and its generated MCP tool ` +\n `catalogs cannot be located: ${lookup.problem!.problem}`,\n lookup.problem!.cure,\n );\n }\n return path.resolve(workspaceRoot, lookup.found.outputPath);\n }\n\n private workspaceRootAbove(packageName: string, packageDir: string): string {\n let dir = packageDir;\n for (;;) {\n if (fs.existsSync(path.join(dir, 'nx.json'))) return dir;\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new McpToolCatalogError(\n `${packageName} resolved to ${packageDir}, which has a project.json but no nx.json above it, ` +\n 'so there is no nx workspace to resolve its build outputPath against.',\n 'Link the BUILT package instead (its directory holds the mcp-<ContractClass>-tools.json files).',\n );\n }\n\n private read(packageName: string, packageDir: string, dir: string): McpToolCatalog[] {\n const files = this.catalogFiles(dir);\n if (files.length === 0) {\n throw new McpToolCatalogError(\n `${packageName} (${packageDir}) has no mcp-<ContractClass>-tools.json in ${dir}` +\n (fs.existsSync(dir) ? '.' : ' — the directory does not exist, so the library was never built.'),\n BUILD_IT,\n );\n }\n return files.map(\n (file: string) =>\n new McpToolCatalog(\n McpToolCatalogFile.fromJsonText(file, fs.readFileSync(path.join(dir, file), 'utf8')),\n dir,\n ),\n );\n }\n\n private catalogFiles(dir: string): string[] {\n if (!fs.existsSync(dir)) return [];\n return fs\n .readdirSync(dir)\n .filter((file: string) => McpToolCatalogFile.contractNameOf(file) !== undefined)\n .sort();\n }\n}\n"]}
|
package/src/McpToolRegistry.js
CHANGED
|
@@ -135,6 +135,12 @@ class McpToolRegistry {
|
|
|
135
135
|
if (!authorization) {
|
|
136
136
|
throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} must declare @WpAuthorization(...).`);
|
|
137
137
|
}
|
|
138
|
+
if (authorization.authType === core_util_1.AuthorizationType.SERVICE_ONLY) {
|
|
139
|
+
throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} declares @WpAuthorization SERVICE_ONLY. An MCP ` +
|
|
140
|
+
'tool is always called on behalf of a model, so a policy that admits a bare service hop would ' +
|
|
141
|
+
'admit every model behind it (and deny it everywhere else): SERVICE_ONLY can never be an MCP ' +
|
|
142
|
+
'tool. Use USERS_OR_SERVICES, ALL_USERS or ROLES, or remove @WpMcpTool.');
|
|
143
|
+
}
|
|
138
144
|
this.authorizationService.validate(authorization);
|
|
139
145
|
binding.validateMethod(metadata.methodName, this.authorizationService);
|
|
140
146
|
if (catalog === undefined)
|
|
@@ -210,7 +216,7 @@ class McpCatalogPairing {
|
|
|
210
216
|
continue;
|
|
211
217
|
this.problem(`${catalog.file.fileName} (${catalog.directory}) is the catalog of ${catalog.contractName}, ` +
|
|
212
218
|
`which the server does not bind. Bind it with McpApiBinding.local(${catalog.contractName}, ...) ` +
|
|
213
|
-
'or .remote(...), or leave it out of
|
|
219
|
+
'or .remote(...), or leave it out of the catalogs passed to new McpToolset(...), e.g. ' +
|
|
214
220
|
`catalogs.filter((catalog: McpToolCatalog) => catalog.contractName !== '${catalog.contractName}').`);
|
|
215
221
|
}
|
|
216
222
|
if (this.problems.length === 0)
|
|
@@ -222,7 +228,7 @@ class McpCatalogPairing {
|
|
|
222
228
|
this.problems.map((text) => ` - ${text}`).join('\n') +
|
|
223
229
|
'\nDirectories searched for mcp-<ContractClass>-tools.json:\n' +
|
|
224
230
|
(directories.length === 0
|
|
225
|
-
? ' (none —
|
|
231
|
+
? ' (none — the catalogs passed to new McpToolset(...) are empty)'
|
|
226
232
|
: directories.map((dir) => ` ${dir}`).join('\n')) +
|
|
227
233
|
'\n', 'Each bound contract needs exactly the one catalog its api library build writes, and nothing else.');
|
|
228
234
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpToolRegistry.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpToolRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { McpToolPolicy } from './McpToolPolicy';\nimport {\n ApiJsonSchema,\n AuthMeta,\n assertApiTypeMatchesMcpTools,\n getAuthMeta,\n getEndpointKind,\n getHttpParameterDeclarations,\n getEndpointOperation,\n getEndpoints,\n getWpMcpTools,\n getAuthorization,\n AuthorizationRequirement,\n EndpointOperation,\n McpToolCatalogError,\n McpToolCatalogFile,\n McpToolDefinition,\n WpMcpToolHints,\n WpMcpToolMetadata,\n} from '@webpieces/core-util';\nimport { ClassType, AuthorizationService } from '@webpieces/http-routing';\nimport { McpApiBinding } from './McpApiBinding';\nimport { McpToolCatalog } from './McpToolCatalog';\n\n/** Fully resolved contract metadata for one MCP tool. */\nexport class RegisteredMcpTool {\n constructor(\n public readonly apiClass: ClassType,\n public readonly methodName: string,\n public readonly name: string,\n /**\n * The human-readable display name, read from the CATALOG — which the generator wrote from the\n * method's `@summary` JSDoc tag (#1296). Published as BOTH `Tool.title` and `annotations.title`.\n * The contract carries no title at runtime, so there is nothing to compare it with: one source.\n */\n public readonly title: string,\n public readonly description: string,\n public readonly annotations: WpMcpToolHints,\n public readonly operation: EndpointOperation,\n public readonly authMeta: AuthMeta,\n // webpieces-disable no-any-unknown -- custom policy is validated by AuthorizationService\n public readonly authorization: AuthorizationRequirement<unknown>,\n public readonly binding: McpApiBinding,\n public readonly inputSchema: ApiJsonSchema,\n public readonly outputSchema: ApiJsonSchema,\n public readonly profiles: readonly string[],\n ) {}\n}\n\n/**\n * Fail-fast registry: the CONTRACT says which methods are tools and how they are authorized, and the\n * build's generated catalog OF THAT CONTRACT says what each tool's title, documentation and schemas\n * are. The title and the description are JSDoc (`@summary` and the body), which only the compiler can\n * read, so the catalog is their one runtime source (#1296).\n *\n * ## Why the schemas come from the catalog and not from here\n *\n * They used to be built at boot by `DtoSchemaBuilder` walking reflect-metadata. That made the\n * document a partner reads and the schema a server accepts two independent derivations of one\n * contract, free to disagree; and it could not see a field's declared TYPE at all, so every fact\n * about a field had to be restated in a `@WpDtoField` argument. #983 measured the compiler against\n * it and found the compiler reproduces all of it, so #984 deleted the decorator and this registry\n * now READS the same catalogs the documents were rendered beside.\n *\n * ## One catalog per bound contract, exactly (#1021)\n *\n * `wp-openapi` writes one `mcp-<ContractClass>-tools.json` per contract, and this registry pairs every\n * `McpApiBinding` with the file of its own contract. Three mismatches refuse the boot, all reported at\n * once, each naming every directory the catalogs were read from:\n *\n * - a bound contract with no catalog — its tools were never given a schema by the build;\n * - a catalog whose contract is not bound — the server was handed tools it does not serve;\n * - a tool name declared twice — the protocol namespace is flat.\n *\n * A registered `@WpMcpTool` the catalog does not name is likewise a HARD FAILURE, not a fallback: a\n * tool the build never saw is a tool whose schema nobody checked.\n */\nexport class McpToolRegistry {\n readonly tools: readonly RegisteredMcpTool[];\n\n constructor(\n bindings: readonly McpApiBinding[],\n catalogs: readonly McpToolCatalog[],\n private readonly authorizationService: AuthorizationService,\n ) {\n const pairing = new McpCatalogPairing(catalogs);\n const registered: RegisteredMcpTool[] = [];\n const names = new Set<string>();\n for (const binding of bindings) {\n const apiClass = binding.api;\n // MCP membership is declared twice — on @ApiType and on the methods — and this is the\n // one call that makes the two disagree impossible. It existed before #984 and nothing\n // reached it, so a contract could carry tools nobody published to agents.\n assertApiTypeMatchesMcpTools(apiClass);\n const declared = getWpMcpTools(apiClass);\n const catalog = pairing.claim(apiClass, declared.length);\n for (const entry of catalog?.file.tools ?? []) {\n if (!declared.some((metadata: WpMcpToolMetadata) => metadata.name === entry.name)) {\n pairing.problem(\n `Stale tool '${entry.name}' in ${catalog!.file.fileName}; rebuild the api library.`,\n );\n }\n }\n for (const metadata of declared) {\n if (names.has(metadata.name)) {\n pairing.problem(\n `Duplicate @WpMcpTool name '${metadata.name}' (again on ${apiClass.name}). Tool ` +\n 'names are the protocol identity and must be globally unique — rename one.',\n );\n continue;\n }\n names.add(metadata.name);\n const tool = this.resolve(apiClass, metadata, binding, catalog, pairing);\n if (tool !== undefined) registered.push(tool);\n }\n }\n pairing.assertComplete();\n this.tools = registered;\n }\n\n find(name: string): RegisteredMcpTool | undefined {\n return this.tools.find((tool: RegisteredMcpTool) => tool.name === name);\n }\n\n /**\n * The contract checks THROW at once — they are about the contract source, not the build. A tool\n * missing from its catalog is RECORDED on `pairing`, so one boot names every catalog mismatch.\n */\n private resolve(\n apiClass: ClassType,\n metadata: WpMcpToolMetadata,\n binding: McpApiBinding,\n catalog: McpToolCatalog | undefined,\n pairing: McpCatalogPairing,\n ): RegisteredMcpTool | undefined {\n const endpoints = getEndpoints(apiClass) ?? {};\n if (!endpoints[metadata.methodName]) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must also be an @Endpoint.`,\n );\n }\n if (getEndpointKind(apiClass, metadata.methodName) !== 'rpc') {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must be an RPC endpoint.`,\n );\n }\n if (getHttpParameterDeclarations(apiClass, metadata.methodName).length > 0) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} binds arguments to the URL with ` +\n '@PathParam/@QueryParam. An MCP tool receives exactly ONE argument (its input object), so ' +\n 'an endpoint with path or query bindings is not an MCP tool: remove @WpMcpTool, or move ' +\n 'those values into the request body.',\n );\n }\n const authMeta = getAuthMeta(apiClass, metadata.methodName);\n if (!authMeta)\n throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} has no HTTP auth.`);\n const authorization = getAuthorization(apiClass, metadata.methodName);\n if (!authorization) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must declare @WpAuthorization(...).`,\n );\n }\n this.authorizationService.validate(authorization);\n binding.validateMethod(metadata.methodName, this.authorizationService);\n if (catalog === undefined) return undefined;\n const published = this.published(apiClass, metadata, catalog, pairing);\n if (published === undefined) return undefined;\n return new RegisteredMcpTool(\n apiClass,\n metadata.methodName,\n metadata.name,\n published.title,\n published.description,\n published.hints,\n getEndpointOperation(apiClass, metadata.methodName),\n authMeta,\n authorization,\n binding,\n published.inputSchema,\n published.outputSchema,\n published.profiles,\n );\n }\n\n /** The generated entry for this tool, or a recorded refusal naming what the build DID publish. */\n private published(\n apiClass: ClassType,\n metadata: WpMcpToolMetadata,\n catalog: McpToolCatalog,\n pairing: McpCatalogPairing,\n ): McpToolDefinition | undefined {\n const published = catalog.find(metadata.name);\n if (published === undefined) {\n pairing.problem(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} publishes '${metadata.name}', which ` +\n `${catalog.file.fileName} (${catalog.directory}) does not contain. It has: ` +\n `${catalog.names().join(', ') || '(nothing)'}. The catalog is older than the contract — ` +\n 'rebuild the api library; a tool the build never saw is a tool whose schema nobody checked.',\n );\n return undefined;\n }\n if (\n published.methodName !== metadata.methodName ||\n published.profiles.join(',') !== metadata.profiles.join(',')\n ) {\n pairing.problem(\n `MCP tool '${metadata.name}' has stale method/profile metadata in ${catalog.file.fileName}; rebuild the api library.`,\n );\n return undefined;\n }\n return published;\n }\n}\n\n/**\n * Pairs each bound contract with the catalog of exactly that contract, and collects every mismatch so\n * the boot refusal lists them all at once instead of one per restart.\n */\nclass McpCatalogPairing {\n private readonly byContract = new Map<string, McpToolCatalog>();\n private readonly claimed = new Set<string>();\n private readonly problems: string[] = [];\n\n constructor(private readonly catalogs: readonly McpToolCatalog[]) {\n for (const catalog of catalogs) {\n const earlier = this.byContract.get(catalog.contractName);\n if (earlier !== undefined) {\n this.problem(\n `Two MCP tool catalogs for ${catalog.contractName}: ${earlier.directory} and ` +\n `${catalog.directory}. Name each api library in fromPackages(...) once, and bind ` +\n 'each contract from exactly one of them.',\n );\n continue;\n }\n this.byContract.set(catalog.contractName, catalog);\n }\n }\n\n /** The catalog of `apiClass`, recording a problem when there is none. */\n claim(apiClass: ClassType, declaredTools: number): McpToolCatalog | undefined {\n const catalog = this.byContract.get(apiClass.name);\n this.claimed.add(apiClass.name);\n if (catalog !== undefined) return catalog;\n this.problem(\n declaredTools === 0\n ? `The server binds ${apiClass.name}, which declares no @WpMcpTool method, so the build ` +\n `wrote no ${McpToolCatalogFile.fileNameFor(apiClass.name)} for it. Delete its ` +\n 'McpApiBinding — it publishes nothing.'\n : `The server binds ${apiClass.name}, and no ${McpToolCatalogFile.fileNameFor(apiClass.name)} ` +\n 'was handed to it. Add the api library that declares it to McpToolCatalog.fromPackages([...]) ' +\n 'and rebuild that library (its openapi-generate target writes the file).',\n );\n return undefined;\n }\n\n problem(text: string): void {\n this.problems.push(text);\n }\n\n /** Refuse the boot on any mismatch, naming every problem and every directory searched. */\n assertComplete(): void {\n for (const catalog of this.byContract.values()) {\n if (this.claimed.has(catalog.contractName)) continue;\n this.problem(\n `${catalog.file.fileName} (${catalog.directory}) is the catalog of ${catalog.contractName}, ` +\n `which the server does not bind. Bind it with McpApiBinding.local(${catalog.contractName}, ...) ` +\n 'or .remote(...), or leave it out of toolCatalogs, e.g. ' +\n `catalogs.filter((catalog: McpToolCatalog) => catalog.contractName !== '${catalog.contractName}').`,\n );\n }\n if (this.problems.length === 0) return;\n const directories = [\n ...new Set(this.catalogs.map((catalog: McpToolCatalog) => catalog.directory)),\n ];\n throw new McpToolCatalogError(\n 'The MCP tool catalogs do not match the bound contracts:\\n' +\n this.problems.map((text: string) => ` - ${text}`).join('\\n') +\n '\\nDirectories searched for mcp-<ContractClass>-tools.json:\\n' +\n (directories.length === 0\n ? ' (none — toolCatalogs is empty)'\n : directories.map((dir: string) => ` ${dir}`).join('\\n')) +\n '\\n',\n 'Each bound contract needs exactly the one catalog its api library build writes, and nothing else.',\n );\n }\n}\n\n/** Request-specific authorized projection, shared by documentation, schema lookup and name lookup. */\nexport class AuthorizedMcpTools {\n constructor(public readonly tools: readonly RegisteredMcpTool[]) {}\n\n find(name: string): RegisteredMcpTool | undefined {\n return this.tools.find((tool: RegisteredMcpTool) => tool.name === name);\n }\n\n // webpieces-disable no-function-outside-class -- asynchronous projection factory\n static async create(\n registry: McpToolRegistry,\n policy: AuthorizationService,\n profiles: readonly string[],\n ): Promise<AuthorizedMcpTools> {\n const tools: RegisteredMcpTool[] = [];\n const authorization = new McpToolPolicy(policy);\n for (const tool of [...registry.tools].sort((a: RegisteredMcpTool, b: RegisteredMcpTool) =>\n a.name.localeCompare(b.name),\n )) {\n if (!tool.profiles.some((profile: string) => profiles.includes(profile))) continue;\n if (await authorization.permits(tool.authorization)) tools.push(tool);\n }\n return new AuthorizedMcpTools(tools);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"McpToolRegistry.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpToolRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import { McpToolPolicy } from './McpToolPolicy';\nimport {\n ApiJsonSchema,\n AuthMeta,\n assertApiTypeMatchesMcpTools,\n getAuthMeta,\n getEndpointKind,\n getHttpParameterDeclarations,\n getEndpointOperation,\n getEndpoints,\n getWpMcpTools,\n getAuthorization,\n AuthorizationRequirement,\n AuthorizationType,\n EndpointOperation,\n McpToolCatalogError,\n McpToolCatalogFile,\n McpToolDefinition,\n WpMcpToolHints,\n WpMcpToolMetadata,\n} from '@webpieces/core-util';\nimport { ClassType, AuthorizationService } from '@webpieces/http-routing';\nimport { McpApiBinding } from './McpApiBinding';\nimport { McpToolCatalog } from './McpToolCatalog';\n\n/** Fully resolved contract metadata for one MCP tool. */\nexport class RegisteredMcpTool {\n constructor(\n public readonly apiClass: ClassType,\n public readonly methodName: string,\n public readonly name: string,\n /**\n * The human-readable display name, read from the CATALOG — which the generator wrote from the\n * method's `@summary` JSDoc tag (#1296). Published as BOTH `Tool.title` and `annotations.title`.\n * The contract carries no title at runtime, so there is nothing to compare it with: one source.\n */\n public readonly title: string,\n public readonly description: string,\n public readonly annotations: WpMcpToolHints,\n public readonly operation: EndpointOperation,\n public readonly authMeta: AuthMeta,\n // webpieces-disable no-any-unknown -- custom policy is validated by AuthorizationService\n public readonly authorization: AuthorizationRequirement<unknown>,\n public readonly binding: McpApiBinding,\n public readonly inputSchema: ApiJsonSchema,\n public readonly outputSchema: ApiJsonSchema,\n public readonly profiles: readonly string[],\n ) {}\n}\n\n/**\n * Fail-fast registry: the CONTRACT says which methods are tools and how they are authorized, and the\n * build's generated catalog OF THAT CONTRACT says what each tool's title, documentation and schemas\n * are. The title and the description are JSDoc (`@summary` and the body), which only the compiler can\n * read, so the catalog is their one runtime source (#1296).\n *\n * ## Why the schemas come from the catalog and not from here\n *\n * They used to be built at boot by `DtoSchemaBuilder` walking reflect-metadata. That made the\n * document a partner reads and the schema a server accepts two independent derivations of one\n * contract, free to disagree; and it could not see a field's declared TYPE at all, so every fact\n * about a field had to be restated in a `@WpDtoField` argument. #983 measured the compiler against\n * it and found the compiler reproduces all of it, so #984 deleted the decorator and this registry\n * now READS the same catalogs the documents were rendered beside.\n *\n * ## One catalog per bound contract, exactly (#1021)\n *\n * `wp-openapi` writes one `mcp-<ContractClass>-tools.json` per contract, and this registry pairs every\n * `McpApiBinding` with the file of its own contract. Three mismatches refuse the boot, all reported at\n * once, each naming every directory the catalogs were read from:\n *\n * - a bound contract with no catalog — its tools were never given a schema by the build;\n * - a catalog whose contract is not bound — the server was handed tools it does not serve;\n * - a tool name declared twice — the protocol namespace is flat.\n *\n * A registered `@WpMcpTool` the catalog does not name is likewise a HARD FAILURE, not a fallback: a\n * tool the build never saw is a tool whose schema nobody checked.\n */\nexport class McpToolRegistry {\n readonly tools: readonly RegisteredMcpTool[];\n\n constructor(\n bindings: readonly McpApiBinding[],\n catalogs: readonly McpToolCatalog[],\n private readonly authorizationService: AuthorizationService,\n ) {\n const pairing = new McpCatalogPairing(catalogs);\n const registered: RegisteredMcpTool[] = [];\n const names = new Set<string>();\n for (const binding of bindings) {\n const apiClass = binding.api;\n // MCP membership is declared twice — on @ApiType and on the methods — and this is the\n // one call that makes the two disagree impossible. It existed before #984 and nothing\n // reached it, so a contract could carry tools nobody published to agents.\n assertApiTypeMatchesMcpTools(apiClass);\n const declared = getWpMcpTools(apiClass);\n const catalog = pairing.claim(apiClass, declared.length);\n for (const entry of catalog?.file.tools ?? []) {\n if (!declared.some((metadata: WpMcpToolMetadata) => metadata.name === entry.name)) {\n pairing.problem(\n `Stale tool '${entry.name}' in ${catalog!.file.fileName}; rebuild the api library.`,\n );\n }\n }\n for (const metadata of declared) {\n if (names.has(metadata.name)) {\n pairing.problem(\n `Duplicate @WpMcpTool name '${metadata.name}' (again on ${apiClass.name}). Tool ` +\n 'names are the protocol identity and must be globally unique — rename one.',\n );\n continue;\n }\n names.add(metadata.name);\n const tool = this.resolve(apiClass, metadata, binding, catalog, pairing);\n if (tool !== undefined) registered.push(tool);\n }\n }\n pairing.assertComplete();\n this.tools = registered;\n }\n\n find(name: string): RegisteredMcpTool | undefined {\n return this.tools.find((tool: RegisteredMcpTool) => tool.name === name);\n }\n\n /**\n * The contract checks THROW at once — they are about the contract source, not the build. A tool\n * missing from its catalog is RECORDED on `pairing`, so one boot names every catalog mismatch.\n */\n private resolve(\n apiClass: ClassType,\n metadata: WpMcpToolMetadata,\n binding: McpApiBinding,\n catalog: McpToolCatalog | undefined,\n pairing: McpCatalogPairing,\n ): RegisteredMcpTool | undefined {\n const endpoints = getEndpoints(apiClass) ?? {};\n if (!endpoints[metadata.methodName]) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must also be an @Endpoint.`,\n );\n }\n if (getEndpointKind(apiClass, metadata.methodName) !== 'rpc') {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must be an RPC endpoint.`,\n );\n }\n if (getHttpParameterDeclarations(apiClass, metadata.methodName).length > 0) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} binds arguments to the URL with ` +\n '@PathParam/@QueryParam. An MCP tool receives exactly ONE argument (its input object), so ' +\n 'an endpoint with path or query bindings is not an MCP tool: remove @WpMcpTool, or move ' +\n 'those values into the request body.',\n );\n }\n const authMeta = getAuthMeta(apiClass, metadata.methodName);\n if (!authMeta)\n throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} has no HTTP auth.`);\n const authorization = getAuthorization(apiClass, metadata.methodName);\n if (!authorization) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must declare @WpAuthorization(...).`,\n );\n }\n if (authorization.authType === AuthorizationType.SERVICE_ONLY) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} declares @WpAuthorization SERVICE_ONLY. An MCP ` +\n 'tool is always called on behalf of a model, so a policy that admits a bare service hop would ' +\n 'admit every model behind it (and deny it everywhere else): SERVICE_ONLY can never be an MCP ' +\n 'tool. Use USERS_OR_SERVICES, ALL_USERS or ROLES, or remove @WpMcpTool.',\n );\n }\n this.authorizationService.validate(authorization);\n binding.validateMethod(metadata.methodName, this.authorizationService);\n if (catalog === undefined) return undefined;\n const published = this.published(apiClass, metadata, catalog, pairing);\n if (published === undefined) return undefined;\n return new RegisteredMcpTool(\n apiClass,\n metadata.methodName,\n metadata.name,\n published.title,\n published.description,\n published.hints,\n getEndpointOperation(apiClass, metadata.methodName),\n authMeta,\n authorization,\n binding,\n published.inputSchema,\n published.outputSchema,\n published.profiles,\n );\n }\n\n /** The generated entry for this tool, or a recorded refusal naming what the build DID publish. */\n private published(\n apiClass: ClassType,\n metadata: WpMcpToolMetadata,\n catalog: McpToolCatalog,\n pairing: McpCatalogPairing,\n ): McpToolDefinition | undefined {\n const published = catalog.find(metadata.name);\n if (published === undefined) {\n pairing.problem(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} publishes '${metadata.name}', which ` +\n `${catalog.file.fileName} (${catalog.directory}) does not contain. It has: ` +\n `${catalog.names().join(', ') || '(nothing)'}. The catalog is older than the contract — ` +\n 'rebuild the api library; a tool the build never saw is a tool whose schema nobody checked.',\n );\n return undefined;\n }\n if (\n published.methodName !== metadata.methodName ||\n published.profiles.join(',') !== metadata.profiles.join(',')\n ) {\n pairing.problem(\n `MCP tool '${metadata.name}' has stale method/profile metadata in ${catalog.file.fileName}; rebuild the api library.`,\n );\n return undefined;\n }\n return published;\n }\n}\n\n/**\n * Pairs each bound contract with the catalog of exactly that contract, and collects every mismatch so\n * the boot refusal lists them all at once instead of one per restart.\n */\nclass McpCatalogPairing {\n private readonly byContract = new Map<string, McpToolCatalog>();\n private readonly claimed = new Set<string>();\n private readonly problems: string[] = [];\n\n constructor(private readonly catalogs: readonly McpToolCatalog[]) {\n for (const catalog of catalogs) {\n const earlier = this.byContract.get(catalog.contractName);\n if (earlier !== undefined) {\n this.problem(\n `Two MCP tool catalogs for ${catalog.contractName}: ${earlier.directory} and ` +\n `${catalog.directory}. Name each api library in fromPackages(...) once, and bind ` +\n 'each contract from exactly one of them.',\n );\n continue;\n }\n this.byContract.set(catalog.contractName, catalog);\n }\n }\n\n /** The catalog of `apiClass`, recording a problem when there is none. */\n claim(apiClass: ClassType, declaredTools: number): McpToolCatalog | undefined {\n const catalog = this.byContract.get(apiClass.name);\n this.claimed.add(apiClass.name);\n if (catalog !== undefined) return catalog;\n this.problem(\n declaredTools === 0\n ? `The server binds ${apiClass.name}, which declares no @WpMcpTool method, so the build ` +\n `wrote no ${McpToolCatalogFile.fileNameFor(apiClass.name)} for it. Delete its ` +\n 'McpApiBinding — it publishes nothing.'\n : `The server binds ${apiClass.name}, and no ${McpToolCatalogFile.fileNameFor(apiClass.name)} ` +\n 'was handed to it. Add the api library that declares it to McpToolCatalog.fromPackages([...]) ' +\n 'and rebuild that library (its openapi-generate target writes the file).',\n );\n return undefined;\n }\n\n problem(text: string): void {\n this.problems.push(text);\n }\n\n /** Refuse the boot on any mismatch, naming every problem and every directory searched. */\n assertComplete(): void {\n for (const catalog of this.byContract.values()) {\n if (this.claimed.has(catalog.contractName)) continue;\n this.problem(\n `${catalog.file.fileName} (${catalog.directory}) is the catalog of ${catalog.contractName}, ` +\n `which the server does not bind. Bind it with McpApiBinding.local(${catalog.contractName}, ...) ` +\n 'or .remote(...), or leave it out of the catalogs passed to new McpToolset(...), e.g. ' +\n `catalogs.filter((catalog: McpToolCatalog) => catalog.contractName !== '${catalog.contractName}').`,\n );\n }\n if (this.problems.length === 0) return;\n const directories = [\n ...new Set(this.catalogs.map((catalog: McpToolCatalog) => catalog.directory)),\n ];\n throw new McpToolCatalogError(\n 'The MCP tool catalogs do not match the bound contracts:\\n' +\n this.problems.map((text: string) => ` - ${text}`).join('\\n') +\n '\\nDirectories searched for mcp-<ContractClass>-tools.json:\\n' +\n (directories.length === 0\n ? ' (none — the catalogs passed to new McpToolset(...) are empty)'\n : directories.map((dir: string) => ` ${dir}`).join('\\n')) +\n '\\n',\n 'Each bound contract needs exactly the one catalog its api library build writes, and nothing else.',\n );\n }\n}\n\n/** Request-specific authorized projection, shared by documentation, schema lookup and name lookup. */\nexport class AuthorizedMcpTools {\n constructor(public readonly tools: readonly RegisteredMcpTool[]) {}\n\n find(name: string): RegisteredMcpTool | undefined {\n return this.tools.find((tool: RegisteredMcpTool) => tool.name === name);\n }\n\n // webpieces-disable no-function-outside-class -- asynchronous projection factory\n static async create(\n registry: McpToolRegistry,\n policy: AuthorizationService,\n profiles: readonly string[],\n ): Promise<AuthorizedMcpTools> {\n const tools: RegisteredMcpTool[] = [];\n const authorization = new McpToolPolicy(policy);\n for (const tool of [...registry.tools].sort((a: RegisteredMcpTool, b: RegisteredMcpTool) =>\n a.name.localeCompare(b.name),\n )) {\n if (!tool.profiles.some((profile: string) => profiles.includes(profile))) continue;\n if (await authorization.permits(tool.authorization)) tools.push(tool);\n }\n return new AuthorizedMcpTools(tools);\n }\n}\n"]}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Implementation, McpServer, ServerOptions } from '@modelcontextprotocol/server';
|
|
2
|
+
import { McpToolProfiles } from '@webpieces/core-util';
|
|
3
|
+
import { AuthorizationService } from '@webpieces/http-routing';
|
|
4
|
+
import { McpApiBinding } from './McpApiBinding';
|
|
5
|
+
import { McpToolCatalog } from './McpToolCatalog';
|
|
6
|
+
import { AuthorizedMcpTools, McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';
|
|
7
|
+
/**
|
|
8
|
+
* What ONE MCP request may see and how it is cancelled, established by the transport before the SDK
|
|
9
|
+
* handler runs: the caller's authorized projection, and the signal that fires when the caller goes away.
|
|
10
|
+
*/
|
|
11
|
+
export declare class McpToolRequest {
|
|
12
|
+
readonly tools: AuthorizedMcpTools;
|
|
13
|
+
readonly disconnectSignal: AbortSignal;
|
|
14
|
+
constructor(tools: AuthorizedMcpTools, disconnectSignal: AbortSignal);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* McpToolset — the shared core of every MCP transport: WHICH tools are served, and everything that
|
|
18
|
+
* happens to a request once its caller is established.
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const toolset = new McpToolset(
|
|
22
|
+
* bindings, // McpApiBinding.local / .remote
|
|
23
|
+
* McpToolCatalog.fromPackages(['@myorg/my-apis'], __dirname),
|
|
24
|
+
* router.authorizationService(),
|
|
25
|
+
* ['user'], // #1187 profiles: WHICH tools this toolset serves
|
|
26
|
+
* );
|
|
27
|
+
* const client = await WpMcpInMemory.connect(toolset); // in-process agent
|
|
28
|
+
* new WpMcpServer(config, toolset).bind(app, options); // HTTP
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* It owns the {@link McpToolRegistry}, the per-caller {@link AuthorizedMcpTools} projection, input and
|
|
32
|
+
* output schema validation, dispatch through `binding.invoke`, and the error translation. Its ONLY input
|
|
33
|
+
* about the caller is the CURRENT `RequestContext` — the trusted USER_ID / USER_ROLES / ORG_ID and a
|
|
34
|
+
* `VerifiedMachineCaller`, exactly what `AuthorizationService.authorize` reads. A transport's only job
|
|
35
|
+
* is to establish that context; `@WpAuthorization` on the contract is the single policy everywhere.
|
|
36
|
+
*
|
|
37
|
+
* Profiles are fixed per toolset: `/mcp/user` and `/mcp/admin` are two toolsets (two servers), so paths
|
|
38
|
+
* split by WHICH TOOLS, never by HOW THE CALLER AUTHENTICATES.
|
|
39
|
+
*
|
|
40
|
+
* Before tools/list and tools/call hand an IMPLEMENTATION-kind failure to the translator, they
|
|
41
|
+
* `log.error` it with its stack and the requestId ({@link logImplementationFailure}, #1055). The
|
|
42
|
+
* translator reduces such a failure to "Internal Error" for the caller, so that line is the only place
|
|
43
|
+
* its cause exists.
|
|
44
|
+
*/
|
|
45
|
+
export declare class McpToolset {
|
|
46
|
+
readonly authorizationService: AuthorizationService;
|
|
47
|
+
readonly registry: McpToolRegistry;
|
|
48
|
+
readonly profiles: readonly string[];
|
|
49
|
+
/** A short hash of the served tool surface, appended to every server version and tool `_meta`. */
|
|
50
|
+
readonly revision: string;
|
|
51
|
+
private readonly dispatcher;
|
|
52
|
+
/** Classifies a failure exactly as the translator will render it, so the two cannot disagree. */
|
|
53
|
+
private readonly boundary;
|
|
54
|
+
private readonly outputSchemas;
|
|
55
|
+
/**
|
|
56
|
+
* tools/list has NO filter chain above it — `LogApiFilter` never sees a list the SDK renders. Wrapping
|
|
57
|
+
* it in `LogApiCall` is this repo's one line on logging.
|
|
58
|
+
*/
|
|
59
|
+
private readonly logApiCall;
|
|
60
|
+
/** tools/call reads `McpRegistry`; tools/list replies stay framework-owned. See McpErrorTranslator. */
|
|
61
|
+
private readonly translator;
|
|
62
|
+
constructor(bindings: readonly McpApiBinding[],
|
|
63
|
+
/**
|
|
64
|
+
* The tools the build published — ONE catalog per bound contract, each the
|
|
65
|
+
* `mcp-<ContractClass>-tools.json` `wp-openapi` wrote beside its library's OpenAPI documents,
|
|
66
|
+
* read with `McpToolCatalog.fromPackages([...], __dirname)`. The only source of a tool's
|
|
67
|
+
* documentation and schemas; `McpToolRegistry` refuses to build unless it holds exactly one
|
|
68
|
+
* catalog per bound contract.
|
|
69
|
+
*/
|
|
70
|
+
catalogs: readonly McpToolCatalog[], authorizationService: AuthorizationService,
|
|
71
|
+
/** The union of `@WpMcpTool` profiles this toolset serves, at least one (`[Mcp.DEFAULT]` for untagged tools). */
|
|
72
|
+
profiles: McpToolProfiles);
|
|
73
|
+
/** The tools the caller in the CURRENT RequestContext may see, sorted by name. */
|
|
74
|
+
authorizedTools(): Promise<AuthorizedMcpTools>;
|
|
75
|
+
/**
|
|
76
|
+
* A fresh SDK server whose tools/list and tools/call are this toolset's. `request` establishes, per
|
|
77
|
+
* MCP request, what the caller may see; `schemaView` answers the SDK's input-schema lookup (the HTTP
|
|
78
|
+
* transport passes the request's projection, the in-memory one the full registry).
|
|
79
|
+
*/
|
|
80
|
+
createSdkServer(info: Implementation, options: ServerOptions, request: () => Promise<McpToolRequest>, schemaView: (name: string) => RegisteredMcpTool | undefined): McpServer;
|
|
81
|
+
/** Run one un-filtered edge step under `LogApiCall`, so its failure gets exactly one log line. */
|
|
82
|
+
logEdge<R>(step: string, work: () => Promise<R>): Promise<R>;
|
|
83
|
+
/** ONE SDK callback catch implementation; the SDK intercepts foreign throws itself. */
|
|
84
|
+
private sdkBoundary;
|
|
85
|
+
private listTools;
|
|
86
|
+
private toolDefinition;
|
|
87
|
+
/**
|
|
88
|
+
* SDK tool callback: an unknown or hidden tool is a JSON-RPC -32602; every failure after the tool
|
|
89
|
+
* is found is an `isError: true` result, identically for local and remote bindings.
|
|
90
|
+
*/
|
|
91
|
+
private handleCallTool;
|
|
92
|
+
/**
|
|
93
|
+
* ONE `log.error` line, with the stack and the requestId, for a failure that is THIS server's bug
|
|
94
|
+
* — the `implementation` kind, which the translator reduces to "Internal Error" and a requestId
|
|
95
|
+
* for the caller (#1055).
|
|
96
|
+
*
|
|
97
|
+
* `LogApiCall` does not cover this. An MCP output-schema violation is raised AFTER the controller
|
|
98
|
+
* returned, so the only `LogApiCall` line for that call reads `resp-SUCCESS`; before this line
|
|
99
|
+
* existed, the requestId support was handed led to a log with no error in it. Where a
|
|
100
|
+
* `[API-*-resp-FAIL]` line does exist it carries the error's message, not its stack. So this line
|
|
101
|
+
* is not the bare duplicate `ApiErrorBoundary` stopped writing: it is the one that says WHERE.
|
|
102
|
+
*
|
|
103
|
+
* Every other kind (bad-request, end-user, dependency, ...) is a caller's or a downstream's
|
|
104
|
+
* failure, published to the caller with its own text, and `LogApiCall` already records it.
|
|
105
|
+
*/
|
|
106
|
+
private logImplementationFailure;
|
|
107
|
+
/** Everything a tool call does. No catch in here: failures propagate to handleCallTool. */
|
|
108
|
+
private callTool;
|
|
109
|
+
/** The trusted caller of the CURRENT RequestContext: a verified user, else a verified machine. */
|
|
110
|
+
private currentCaller;
|
|
111
|
+
private progressReporter;
|
|
112
|
+
private progressParams;
|
|
113
|
+
private calculateRevision;
|
|
114
|
+
private record;
|
|
115
|
+
}
|