@webpieces/mcp-server 0.4.790 → 0.4.792
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 +23 -13
- package/package.json +6 -6
- package/src/McpApiDispatcher.js +6 -0
- package/src/McpApiDispatcher.js.map +1 -1
- package/src/McpAuth.d.ts +4 -12
- package/src/McpAuth.js +4 -20
- package/src/McpAuth.js.map +1 -1
- package/src/McpRegistry.d.ts +40 -0
- package/src/McpRegistry.js +54 -0
- package/src/McpRegistry.js.map +1 -0
- package/src/McpToolCallRendering.d.ts +111 -0
- package/src/McpToolCallRendering.js +135 -0
- package/src/McpToolCallRendering.js.map +1 -0
- package/src/WpMcpErrorTranslator.d.ts +11 -99
- package/src/WpMcpErrorTranslator.js +27 -157
- package/src/WpMcpErrorTranslator.js.map +1 -1
- package/src/WpMcpServer.d.ts +1 -1
- package/src/WpMcpServer.js +8 -7
- package/src/WpMcpServer.js.map +1 -1
- package/src/__tests__/McpRemoteFixtures.d.ts +4 -3
- package/src/__tests__/McpRemoteFixtures.js +11 -11
- package/src/__tests__/McpRemoteFixtures.js.map +1 -1
- package/src/index.d.ts +4 -2
- package/src/index.js +12 -8
- package/src/index.js.map +1 -1
package/README.md
CHANGED
|
@@ -91,8 +91,10 @@ const config = new WpMcpServerConfig<MyGrant, MyMintRequest>()
|
|
|
91
91
|
.setEndpointMintRequest((credential) => new MyMintRequest(credential.subject))
|
|
92
92
|
.setAuthorizationServers(['https://login.example.com'])
|
|
93
93
|
.setRequiredScopes(['tools'])
|
|
94
|
-
.setErrorTranslator(new MyMcpErrorTranslators())
|
|
95
94
|
.setMaxAccountValidationAgeSeconds(15 * 60);
|
|
95
|
+
|
|
96
|
+
// The tools/call error translator is a PROCESS-GLOBAL, not a member of this config:
|
|
97
|
+
McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());
|
|
96
98
|
```
|
|
97
99
|
|
|
98
100
|
Each setter validates its own value immediately and names itself in the failure: `setResource(...)`
|
|
@@ -179,11 +181,11 @@ hour and MCP access tokens at 30 days.
|
|
|
179
181
|
## Error boundary
|
|
180
182
|
|
|
181
183
|
`WpMcpErrorTranslator` is the one place a failure becomes an MCP reply, mirroring
|
|
182
|
-
`
|
|
184
|
+
`WebpiecesDefaultErrorTranslator`: an error in, the exact wire shape out. Each entry point has exactly one catch
|
|
183
185
|
that only delegates to it; the dispatcher and the local/remote invokers have none. Classification is
|
|
184
186
|
the shared `ApiErrorBoundary.encode` rule (a non-`ApiError`, or a caller-local `ApiConnectionError`,
|
|
185
187
|
publishes as kind `implementation`). The boundary never substitutes an object for the thrown error,
|
|
186
|
-
so an app's `
|
|
188
|
+
so an app's `McpErrorTranslator` can `instanceof` its own error classes.
|
|
187
189
|
|
|
188
190
|
There is ONE method per BOUNDARY, named for the boundary it guards. They cannot be one shape — the
|
|
189
191
|
MCP spec fixes each — so the three-ness is made obvious rather than accidental:
|
|
@@ -192,7 +194,7 @@ MCP spec fixes each — so the three-ness is made obvious rather than accidental
|
|
|
192
194
|
|---|---|---|
|
|
193
195
|
| before the SDK: bearer, `Origin`, malformed body | `toBearerBoundaryResponse(error): HttpResponseDto<McpHttpErrorBody>` | HTTP 401 + `WWW-Authenticate` / 403 / 400, anything else 500 |
|
|
194
196
|
| `tools/list` | `toListError(error): never` | throws JSON-RPC `-32602` (bad request) or `-32603` `Internal Error` |
|
|
195
|
-
| `tools/call`, tool found: bad arguments, `@WpMcpAuthJwt` denial, JWT/OIDC mint failure, any local or remote failure, output-schema or serialization failure | `toToolCallResult(error): CallToolResult` | `isError: true` result |
|
|
197
|
+
| `tools/call`, tool found: bad arguments, `@WpMcpAuthJwt` denial, JWT/OIDC mint failure, any local or remote failure, output-schema or serialization failure | `toToolCallResult(error): CallToolResult` (delegates to `McpRegistry.getErrorTranslator().toWire`) | `isError: true` result |
|
|
196
198
|
| `tools/call`, unknown tool name | `unknownTool(name)` | JSON-RPC `-32602` `Unknown tool: <name>` |
|
|
197
199
|
|
|
198
200
|
The pre-SDK boundary produces a VALUE like every other API in the framework and `WpMcpServer` writes
|
|
@@ -234,16 +236,18 @@ Every reply carries the requestId so a user can quote it: `_meta["webpieces/requ
|
|
|
234
236
|
An app owns its error taxonomy and owns how those errors should be explained to a model, so a
|
|
235
237
|
registered translator REPLACES the webpieces default for every `tools/call` failure — the same
|
|
236
238
|
convention `ExpressWrapper.handleError` applies on the HTTP path. Register one with
|
|
237
|
-
`
|
|
238
|
-
|
|
239
|
+
`McpRegistry.setErrorTranslator(...)`: ONE process-global per protocol, mirroring `ClientRegistry`
|
|
240
|
+
for HTTP and `IpcRegistry` for IPC, so every call site is one unconditional line. (Two MCP servers in
|
|
241
|
+
one process therefore share one translator, and registration order matters rather than being fixed at
|
|
242
|
+
construction — the trade taken deliberately in issue #968 to make all three protocols read alike.)
|
|
239
243
|
|
|
240
244
|
```ts
|
|
241
|
-
class
|
|
245
|
+
class LangMcpErrorTranslator implements McpErrorTranslator {
|
|
242
246
|
private readonly fallback = new McpDefaultToolCallRenderer();
|
|
243
247
|
|
|
244
|
-
|
|
248
|
+
toWire(error: Error): CallToolResult {
|
|
245
249
|
if (!(error instanceof LangPassageLockedError)) {
|
|
246
|
-
return this.fallback.
|
|
250
|
+
return this.fallback.toWire(error); // not mine -> webpieces default
|
|
247
251
|
}
|
|
248
252
|
return {
|
|
249
253
|
content: [{ type: 'text', text: `Passage ${error.passageId} is locked.` }],
|
|
@@ -270,10 +274,16 @@ class LangMcpErrorTranslators implements McpErrorTranslators {
|
|
|
270
274
|
extremely hard to debug.
|
|
271
275
|
|
|
272
276
|
A remote binding's generated Node client turns a dependency's 4xx into the gateway's own
|
|
273
|
-
`ApiImplementationError
|
|
274
|
-
to see the peer's typed
|
|
275
|
-
`ClientRegistry.
|
|
276
|
-
local and remote bindings produce identical tool results.
|
|
277
|
+
`ApiImplementationError`, and a 5xx into an `ApiDependencyError` (the uniform rule in core-util's
|
|
278
|
+
`ReceivedApiErrorRule`). A gateway that wants the model to see the peer's typed failure instead
|
|
279
|
+
registers an `ErrorTranslator` via `ClientRegistry.setErrorTranslator(...)` whose `fromWire` THROWS
|
|
280
|
+
the decoded Webpieces payload; with it, local and remote bindings produce identical tool results.
|
|
281
|
+
|
|
282
|
+
### Why there is no `fromWire` on `McpErrorTranslator`
|
|
283
|
+
|
|
284
|
+
The HTTP and IPC translators have two halves because webpieces sits on both ends of those wires.
|
|
285
|
+
**webpieces is never the MCP client** — Claude is — so there is no return path for webpieces to
|
|
286
|
+
translate. The pair is one-directional by nature, not by omission.
|
|
277
287
|
|
|
278
288
|
The verifier's `accountValidatedAtEpochSeconds` must represent an authoritative enabled/revoked and
|
|
279
289
|
role/scope read. The bridge enforces a maximum one-hour decision age; per-request reads are preferred so
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.792",
|
|
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.
|
|
28
|
-
"@webpieces/core-util": "0.4.
|
|
29
|
-
"@webpieces/http-routing": "0.4.
|
|
30
|
-
"@webpieces/http-server": "0.4.
|
|
27
|
+
"@webpieces/core-context": "0.4.792",
|
|
28
|
+
"@webpieces/core-util": "0.4.792",
|
|
29
|
+
"@webpieces/http-routing": "0.4.792",
|
|
30
|
+
"@webpieces/http-server": "0.4.792",
|
|
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.
|
|
36
|
+
"@webpieces/http-client-node": "0.4.792"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/McpApiDispatcher.js
CHANGED
|
@@ -37,6 +37,12 @@ class McpApiDispatcher {
|
|
|
37
37
|
const request = new core_context_1.HttpRequest('POST', `/__webpieces/mcp/${tool.name}`, headers);
|
|
38
38
|
new core_context_1.RequestContextHeaders().fillFromRequest(request);
|
|
39
39
|
core_context_1.RequestContext.putTrusted(McpInvocationContext_1.MCP_INVOCATION_CONTEXT, invocation);
|
|
40
|
+
// THIS hop is the edge, and its caller is a model. Stamped here rather than in
|
|
41
|
+
// `AuthFilter` because the local binding's HTTP auth mode describes the bridge's own
|
|
42
|
+
// endpoint JWT, not the LLM on the other end of it — `@WpMcpAuthJwt` is separate
|
|
43
|
+
// metadata and never reaches an `AuthMode`. `AuthFilter` only ever SETS a surface when
|
|
44
|
+
// none arrived, so this one survives the filter chain the invocation runs through.
|
|
45
|
+
core_context_1.RequestContext.putTrusted(core_util_1.WebpiecesCoreHeaders.SURFACE, 'llm');
|
|
40
46
|
if (tool.binding.topology === 'remote') {
|
|
41
47
|
for (const tuple of credential.trustedContext)
|
|
42
48
|
core_context_1.RequestContext.putTrusted(tuple.key, tuple.value);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpApiDispatcher.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpApiDispatcher.ts"],"names":[],"mappings":";;;AAAA,oDAQ8B;AAC9B,0DAA6F;AAG7F,iEAAsF;AAEtF;;;;;GAKG;AACH,MAAa,gBAAgB;IACR,aAAa,GAAG,IAAI,4BAAgB,EAAE,CAAC;IAExD,KAAK,CAAC,IAAI,CACN,IAAuB,EACvB,UAAoB,EACpB,UAAiC,EACjC,UAAgC,EAChC,mBAA4B;QAE5B,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAChF,IAAI,YAAY,EAAE,CAAC;YACf,MAAM,IAAI,8BAAkB,CACxB,qBAAqB,IAAI,CAAC,IAAI,kBAAkB,IAAI,CAAC,YAAY,CAAC,IAAI,KAAK,YAAY,CAAC,OAAO,EAAE,EACjG,YAAY,CAAC,KAAK,EAClB,YAAY,CAAC,OAAO,CACvB,CAAC;QACN,CAAC;QACD,MAAM,QAAQ,GAAG,IAAA,yBAAa,EAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACzD,IACI,QAAQ,CAAC,MAAM,GAAG,CAAC;YACnB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAC1E,CAAC;YACC,MAAM,IAAI,6BAAiB,CACvB,iBAAiB,UAAU,CAAC,OAAO,8CAA8C,IAAI,CAAC,IAAI,GAAG,CAChG,CAAC;QACN,CAAC;QACD,MAAM,eAAe,GAAG,6BAAc,CAAC,YAAY,CAAC,gCAAoB,CAAC,UAAU,CAAC,CAAC;QACrF,OAAO,6BAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YAC9C,IAAI,eAAe,EAAE,CAAC;gBAClB,6BAAc,CAAC,YAAY,CAAC,gCAAoB,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;YAClF,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;YAC5C,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBACpC,IAAI,CAAC,mBAAmB,EAAE,CAAC;oBACvB,MAAM,IAAI,kCAAsB,CAC5B,kBAAkB,IAAI,CAAC,IAAI,uBAAuB,CACrD,CAAC;gBACN,CAAC;gBACD,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,UAAU,mBAAmB,EAAE,CAAC,CAAC,CAAC;YACpE,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,0BAAW,CAAC,MAAM,EAAE,oBAAoB,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;YAClF,IAAI,oCAAqB,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YACrD,6BAAc,CAAC,UAAU,CAAC,6CAAsB,EAAE,UAAU,CAAC,CAAC;YAC9D,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACrC,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,cAAc;oBACzC,6BAAc,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;
|
|
1
|
+
{"version":3,"file":"McpApiDispatcher.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpApiDispatcher.ts"],"names":[],"mappings":";;;AAAA,oDAQ8B;AAC9B,0DAA6F;AAG7F,iEAAsF;AAEtF;;;;;GAKG;AACH,MAAa,gBAAgB;IACR,aAAa,GAAG,IAAI,4BAAgB,EAAE,CAAC;IAExD,KAAK,CAAC,IAAI,CACN,IAAuB,EACvB,UAAoB,EACpB,UAAiC,EACjC,UAAgC,EAChC,mBAA4B;QAE5B,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAChF,IAAI,YAAY,EAAE,CAAC;YACf,MAAM,IAAI,8BAAkB,CACxB,qBAAqB,IAAI,CAAC,IAAI,kBAAkB,IAAI,CAAC,YAAY,CAAC,IAAI,KAAK,YAAY,CAAC,OAAO,EAAE,EACjG,YAAY,CAAC,KAAK,EAClB,YAAY,CAAC,OAAO,CACvB,CAAC;QACN,CAAC;QACD,MAAM,QAAQ,GAAG,IAAA,yBAAa,EAAC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;QACzD,IACI,QAAQ,CAAC,MAAM,GAAG,CAAC;YACnB,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAC1E,CAAC;YACC,MAAM,IAAI,6BAAiB,CACvB,iBAAiB,UAAU,CAAC,OAAO,8CAA8C,IAAI,CAAC,IAAI,GAAG,CAChG,CAAC;QACN,CAAC;QACD,MAAM,eAAe,GAAG,6BAAc,CAAC,YAAY,CAAC,gCAAoB,CAAC,UAAU,CAAC,CAAC;QACrF,OAAO,6BAAc,CAAC,gBAAgB,CAAC,KAAK,IAAI,EAAE;YAC9C,IAAI,eAAe,EAAE,CAAC;gBAClB,6BAAc,CAAC,YAAY,CAAC,gCAAoB,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;YAClF,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAoB,CAAC;YAC5C,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;gBACpC,IAAI,CAAC,mBAAmB,EAAE,CAAC;oBACvB,MAAM,IAAI,kCAAsB,CAC5B,kBAAkB,IAAI,CAAC,IAAI,uBAAuB,CACrD,CAAC;gBACN,CAAC;gBACD,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,UAAU,mBAAmB,EAAE,CAAC,CAAC,CAAC;YACpE,CAAC;YACD,MAAM,OAAO,GAAG,IAAI,0BAAW,CAAC,MAAM,EAAE,oBAAoB,IAAI,CAAC,IAAI,EAAE,EAAE,OAAO,CAAC,CAAC;YAClF,IAAI,oCAAqB,EAAE,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC;YACrD,6BAAc,CAAC,UAAU,CAAC,6CAAsB,EAAE,UAAU,CAAC,CAAC;YAC9D,+EAA+E;YAC/E,qFAAqF;YACrF,iFAAiF;YACjF,uFAAuF;YACvF,mFAAmF;YACnF,6BAAc,CAAC,UAAU,CAAC,gCAAoB,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;YAC/D,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;gBACrC,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,cAAc;oBACzC,6BAAc,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;YAC1D,CAAC;YACD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;IACP,CAAC;CACJ;AAzDD,4CAyDC","sourcesContent":["import {\n ApiBadRequestError,\n ApiForbiddenError,\n ApiImplementationError,\n DtoSchemaBuilder,\n DtoValue,\n rolesRequired,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { HttpRequest, RequestContext, RequestContextHeaders } from '@webpieces/core-context';\nimport { RegisteredMcpTool } from './McpToolRegistry';\nimport { VerifiedMcpCredential } from './McpAuth';\nimport { MCP_INVOCATION_CONTEXT, McpInvocationContext } from './McpInvocationContext';\n\n/**\n * Executes one tool call through the explicit local or remote Webpieces client, never a controller\n * reference. It has NO try/catch: every failure (bad arguments, MCP authorization, anything the local\n * proxy or the remote generated client throws) propagates as a typed error to the single tools/call\n * boundary in `WpMcpServer`, so local and remote bindings are translated identically.\n */\nexport class McpApiDispatcher {\n private readonly schemaBuilder = new DtoSchemaBuilder();\n\n async call(\n tool: RegisteredMcpTool,\n requestDto: DtoValue,\n credential: VerifiedMcpCredential,\n invocation: McpInvocationContext,\n endpointBearerToken?: string,\n ): Promise<DtoValue> {\n const inputFailure = this.schemaBuilder.validate(tool.requestClass, requestDto);\n if (inputFailure) {\n throw new ApiBadRequestError(\n `MCP arguments for ${tool.name} did not match ${tool.requestClass.name}: ${inputFailure.message}`,\n inputFailure.field,\n inputFailure.message,\n );\n }\n const required = rolesRequired(tool.mcpAuth.requirement);\n if (\n required.length > 0 &&\n !required.some((role: string) => credential.listingRoles.includes(role))\n ) {\n throw new ApiForbiddenError(\n `MCP principal ${credential.subject} lacks a role required by @WpMcpAuthJwt on ${tool.name}.`,\n );\n }\n const parentRequestId = RequestContext.getUntrusted(WebpiecesCoreHeaders.REQUEST_ID);\n return RequestContext.runDetachedScope(async () => {\n if (parentRequestId) {\n RequestContext.putUntrusted(WebpiecesCoreHeaders.REQUEST_ID, parentRequestId);\n }\n const headers = new Map<string, string[]>();\n if (tool.binding.topology === 'local') {\n if (!endpointBearerToken) {\n throw new ApiImplementationError(\n `Local MCP tool ${tool.name} has no endpoint JWT.`,\n );\n }\n headers.set('authorization', [`Bearer ${endpointBearerToken}`]);\n }\n const request = new HttpRequest('POST', `/__webpieces/mcp/${tool.name}`, headers);\n new RequestContextHeaders().fillFromRequest(request);\n RequestContext.putTrusted(MCP_INVOCATION_CONTEXT, invocation);\n // THIS hop is the edge, and its caller is a model. Stamped here rather than in\n // `AuthFilter` because the local binding's HTTP auth mode describes the bridge's own\n // endpoint JWT, not the LLM on the other end of it — `@WpMcpAuthJwt` is separate\n // metadata and never reaches an `AuthMode`. `AuthFilter` only ever SETS a surface when\n // none arrived, so this one survives the filter chain the invocation runs through.\n RequestContext.putTrusted(WebpiecesCoreHeaders.SURFACE, 'llm');\n if (tool.binding.topology === 'remote') {\n for (const tuple of credential.trustedContext)\n RequestContext.putTrusted(tuple.key, tuple.value);\n }\n return tool.binding.invoke(tool.methodName, requestDto);\n });\n }\n}\n"]}
|
package/src/McpAuth.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { JwtHook } from '@webpieces/http-routing';
|
|
2
2
|
import { ContextTuple } from '@webpieces/core-util';
|
|
3
|
-
import { McpErrorTranslators } from './WpMcpErrorTranslator';
|
|
4
3
|
export declare const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS: number;
|
|
5
4
|
export declare const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS: number;
|
|
6
5
|
export declare const MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS: number;
|
|
@@ -91,8 +90,10 @@ export declare class McpProtectedResourceMetadata {
|
|
|
91
90
|
* .setEndpointJwtAuthority(jwtHook)
|
|
92
91
|
* .setEndpointMintRequest((credential) => new MyMintRequest(credential.subject))
|
|
93
92
|
* .setAuthorizationServers(['https://login.example.com'])
|
|
94
|
-
* .setRequiredScopes(['tools'])
|
|
95
|
-
*
|
|
93
|
+
* .setRequiredScopes(['tools']);
|
|
94
|
+
*
|
|
95
|
+
* // the tools/call error translator is a PROCESS-GLOBAL, not a member of this config:
|
|
96
|
+
* McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());
|
|
96
97
|
* ```
|
|
97
98
|
*/
|
|
98
99
|
export declare class WpMcpServerConfig<TGrant, TMintRequest> {
|
|
@@ -104,7 +105,6 @@ export declare class WpMcpServerConfig<TGrant, TMintRequest> {
|
|
|
104
105
|
private endpointMintRequestValue?;
|
|
105
106
|
private authorizationServersValue?;
|
|
106
107
|
private requiredScopesValue?;
|
|
107
|
-
private errorTranslatorValue?;
|
|
108
108
|
private maxAccountValidationAgeSecondsValue;
|
|
109
109
|
private maxEndpointJwtLifetimeSecondsValue;
|
|
110
110
|
/** REQUIRED. The MCP server name reported by `initialize`. */
|
|
@@ -142,12 +142,6 @@ export declare class WpMcpServerConfig<TGrant, TMintRequest> {
|
|
|
142
142
|
* any tool is dispatched.
|
|
143
143
|
*/
|
|
144
144
|
setRequiredScopes(requiredScopes: readonly string[]): this;
|
|
145
|
-
/**
|
|
146
|
-
* OPTIONAL. The application's `tools/call` error translator — it REPLACES the webpieces default
|
|
147
|
-
* for every tool failure, owning the ENTIRE result, and declines by delegating to
|
|
148
|
-
* `new McpDefaultToolCallRenderer().toToolCallResult(error)`. See {@link McpErrorTranslators}.
|
|
149
|
-
*/
|
|
150
|
-
setErrorTranslator(translators: McpErrorTranslators): this;
|
|
151
145
|
/** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */
|
|
152
146
|
setMaxAccountValidationAgeSeconds(seconds: number): this;
|
|
153
147
|
/** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */
|
|
@@ -165,8 +159,6 @@ export declare class WpMcpServerConfig<TGrant, TMintRequest> {
|
|
|
165
159
|
get endpointMintRequest(): McpEndpointMintRequestFactory<TMintRequest>;
|
|
166
160
|
get authorizationServers(): readonly string[];
|
|
167
161
|
get requiredScopes(): readonly string[];
|
|
168
|
-
/** `undefined` when the app registered none: webpieces then renders every tool failure. */
|
|
169
|
-
get errorTranslator(): McpErrorTranslators | undefined;
|
|
170
162
|
get maxAccountValidationAgeSeconds(): number;
|
|
171
163
|
get maxEndpointJwtLifetimeSeconds(): number;
|
|
172
164
|
/**
|
package/src/McpAuth.js
CHANGED
|
@@ -116,8 +116,10 @@ exports.McpProtectedResourceMetadata = McpProtectedResourceMetadata;
|
|
|
116
116
|
* .setEndpointJwtAuthority(jwtHook)
|
|
117
117
|
* .setEndpointMintRequest((credential) => new MyMintRequest(credential.subject))
|
|
118
118
|
* .setAuthorizationServers(['https://login.example.com'])
|
|
119
|
-
* .setRequiredScopes(['tools'])
|
|
120
|
-
*
|
|
119
|
+
* .setRequiredScopes(['tools']);
|
|
120
|
+
*
|
|
121
|
+
* // the tools/call error translator is a PROCESS-GLOBAL, not a member of this config:
|
|
122
|
+
* McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());
|
|
121
123
|
* ```
|
|
122
124
|
*/
|
|
123
125
|
class WpMcpServerConfig {
|
|
@@ -129,7 +131,6 @@ class WpMcpServerConfig {
|
|
|
129
131
|
endpointMintRequestValue;
|
|
130
132
|
authorizationServersValue;
|
|
131
133
|
requiredScopesValue;
|
|
132
|
-
errorTranslatorValue;
|
|
133
134
|
maxAccountValidationAgeSecondsValue = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS;
|
|
134
135
|
maxEndpointJwtLifetimeSecondsValue = exports.MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS;
|
|
135
136
|
/** REQUIRED. The MCP server name reported by `initialize`. */
|
|
@@ -203,19 +204,6 @@ class WpMcpServerConfig {
|
|
|
203
204
|
this.requiredScopesValue = scopes;
|
|
204
205
|
return this;
|
|
205
206
|
}
|
|
206
|
-
/**
|
|
207
|
-
* OPTIONAL. The application's `tools/call` error translator — it REPLACES the webpieces default
|
|
208
|
-
* for every tool failure, owning the ENTIRE result, and declines by delegating to
|
|
209
|
-
* `new McpDefaultToolCallRenderer().toToolCallResult(error)`. See {@link McpErrorTranslators}.
|
|
210
|
-
*/
|
|
211
|
-
setErrorTranslator(translators) {
|
|
212
|
-
const value = this.requirePresent(translators, 'setErrorTranslator');
|
|
213
|
-
if (typeof value.toToolCallResult !== 'function') {
|
|
214
|
-
throw new Error('WpMcpServerConfig.setErrorTranslator(...) requires a toToolCallResult(error) method.');
|
|
215
|
-
}
|
|
216
|
-
this.errorTranslatorValue = value;
|
|
217
|
-
return this;
|
|
218
|
-
}
|
|
219
207
|
/** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */
|
|
220
208
|
setMaxAccountValidationAgeSeconds(seconds) {
|
|
221
209
|
this.maxAccountValidationAgeSecondsValue = this.requireSeconds(seconds, exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS, 'setMaxAccountValidationAgeSeconds');
|
|
@@ -280,10 +268,6 @@ class WpMcpServerConfig {
|
|
|
280
268
|
get requiredScopes() {
|
|
281
269
|
return this.read(this.requiredScopesValue, 'setRequiredScopes');
|
|
282
270
|
}
|
|
283
|
-
/** `undefined` when the app registered none: webpieces then renders every tool failure. */
|
|
284
|
-
get errorTranslator() {
|
|
285
|
-
return this.errorTranslatorValue;
|
|
286
|
-
}
|
|
287
271
|
get maxAccountValidationAgeSeconds() {
|
|
288
272
|
return this.maxAccountValidationAgeSecondsValue;
|
|
289
273
|
}
|
package/src/McpAuth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpAuth.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuth.ts"],"names":[],"mappings":"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","sourcesContent":["import { JwtHook } from '@webpieces/http-routing';\nimport { ContextTuple } from '@webpieces/core-util';\nimport { McpErrorTranslators } from './WpMcpErrorTranslator';\n\nexport const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;\nexport const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = 60 * 60;\nexport const MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS = 60 * 60;\n\n/** Framework-normalized access-token result. The token representation remains application-owned. */\nexport class MintedMcpAccessToken {\n constructor(\n public readonly token: string,\n public readonly resource: string,\n public readonly issuedAtEpochSeconds: number,\n public readonly expiresAtEpochSeconds: number,\n ) {\n if (token.trim() === '' || resource.trim() === '') {\n throw new Error('Minted MCP access token and resource must be non-empty.');\n }\n if (!Number.isFinite(issuedAtEpochSeconds) || !Number.isFinite(expiresAtEpochSeconds)) {\n throw new Error(\n 'Minted MCP access token timestamps must be finite epoch-second values.',\n );\n }\n if (expiresAtEpochSeconds <= issuedAtEpochSeconds) {\n throw new Error('MCP access token expiry must be after issuance.');\n }\n if (expiresAtEpochSeconds - issuedAtEpochSeconds > MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) {\n throw new Error('MCP access token lifetime must not exceed 30 days.');\n }\n }\n}\n\n/**\n * Fresh, authoritative security facts resolved at the MCP resource boundary. Implementations must\n * verify issuer, exact resource/audience, expiry, token type/version, key/algorithm policy, and\n * current account state. Opaque access tokens are fully supported.\n */\nexport class VerifiedMcpCredential {\n constructor(\n public readonly subject: string,\n public readonly issuer: string,\n public readonly resource: string,\n public readonly issuedAtEpochSeconds: number,\n public readonly expiresAtEpochSeconds: number,\n public readonly scopes: readonly string[],\n /** When current enabled/revoked state and roles were read from the authoritative source. */\n public readonly accountValidatedAtEpochSeconds: number,\n /** Advisory tools/list filtering only. Endpoint authorization always runs again. */\n public readonly listingRoles: readonly string[] = [],\n /** Trusted delegated identity derived only by the access-token authority. */\n public readonly trustedContext: readonly ContextTuple[] = [],\n ) {}\n}\n\n/**\n * Application-owned paired mint/verify authority for resource-bound MCP access tokens.\n * `verifyAccessToken` must throw `ApiUnauthorizedError` for a token it rejects (the bind boundary\n * answers 401 + `WWW-Authenticate`); any other throw is treated as an implementation failure (500).\n */\nexport interface McpAccessTokenAuthority<TGrant> {\n mintAccessToken(grant: TGrant): Promise<MintedMcpAccessToken>;\n verifyAccessToken(\n accessToken: string,\n expectedResource: string,\n ): Promise<VerifiedMcpCredential>;\n}\n\n/** Minimal tool identity supplied when the app creates its own endpoint-JWT mint request. */\nexport class McpEndpointDescriptor {\n constructor(\n public readonly toolName: string,\n public readonly apiClassName: string,\n public readonly methodName: string,\n ) {}\n}\n\n/** Application mapping from verified MCP identity to its unconstrained JwtHook mint request. */\nexport type McpEndpointMintRequestFactory<TMintRequest> = (\n credential: VerifiedMcpCredential,\n endpoint: McpEndpointDescriptor,\n) => TMintRequest;\n\n/** RFC 9728-style metadata exposed by the MCP protected resource. */\nexport class McpProtectedResourceMetadata {\n readonly resource: string;\n readonly authorization_servers: readonly string[];\n readonly bearer_methods_supported = ['header'];\n readonly scopes_supported?: readonly string[];\n\n constructor(\n resource: string,\n authorizationServers: readonly string[],\n scopesSupported?: readonly string[],\n ) {\n this.resource = resource;\n this.authorization_servers = authorizationServers;\n this.scopes_supported = scopesSupported;\n }\n}\n\n/**\n * Node MCP server configuration, built with fluent setters: every name sits next to its own value, so\n * no two settings can be swapped, and a future setting is an additive setter rather than a breaking\n * signature change. Pass the same concrete application authority to both\n * `setAccessTokenAuthority(...)` and `setEndpointJwtAuthority(...)` when it implements both security\n * seams.\n *\n * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource. {@link setResource} is\n * fixed for the lifetime of the process, is the audience of every access token, and is never derived\n * from the request `Host` — see that setter for why a host-derived audience is a confused deputy.\n *\n * Each setter validates its own value immediately and names itself in the failure. Setters cannot\n * make a field impossible to forget the way a positional constructor could, so\n * {@link WpMcpServerConfig.validate} runs from `WpMcpServer.bind(...)` and fails at startup listing\n * EVERY missing required setter at once. A misconfiguration is therefore a boot failure naming the\n * setter, instead of a first-request 401 + `WWW-Authenticate` — which is the OAuth discovery signal,\n * so the client's correct response is to re-authenticate and fail again, forever.\n *\n * ```ts\n * const config = new WpMcpServerConfig<MyGrant, MyMintRequest>()\n * .setName('my-server')\n * .setVersion('1.0.0')\n * .setResource('https://api.example.com/mcp')\n * .setAccessTokenAuthority(authority)\n * .setEndpointJwtAuthority(jwtHook)\n * .setEndpointMintRequest((credential) => new MyMintRequest(credential.subject))\n * .setAuthorizationServers(['https://login.example.com'])\n * .setRequiredScopes(['tools'])\n * .setErrorTranslator(new MyMcpErrorTranslators());\n * ```\n */\nexport class WpMcpServerConfig<TGrant, TMintRequest> {\n private nameValue?: string;\n private versionValue?: string;\n private resourceValue?: string;\n private accessTokenAuthorityValue?: McpAccessTokenAuthority<TGrant>;\n private endpointJwtAuthorityValue?: JwtHook<TMintRequest>;\n private endpointMintRequestValue?: McpEndpointMintRequestFactory<TMintRequest>;\n private authorizationServersValue?: readonly string[];\n private requiredScopesValue?: readonly string[];\n private errorTranslatorValue?: McpErrorTranslators;\n private maxAccountValidationAgeSecondsValue = MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS;\n private maxEndpointJwtLifetimeSecondsValue = MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS;\n\n /** REQUIRED. The MCP server name reported by `initialize`. */\n setName(name: string): this {\n this.nameValue = this.requireText(name, 'setName');\n return this;\n }\n\n /** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */\n setVersion(version: string): this {\n this.versionValue = this.requireText(version, 'setVersion');\n return this;\n }\n\n /**\n * REQUIRED. The ONE canonical protected-resource URI, which must be an absolute URL: it is the\n * exact audience every access token is checked against, and {@link resourceMetadataUrl} is\n * derived from it for the 401 challenge, so a non-URL here is always a mistake — including a\n * name or version landed on it.\n *\n * It is fixed for the lifetime of the process and must NEVER be derived from the request `Host`\n * header: the boundary compares a token's audience against this value, so a host-derived\n * resource compares two values both taken from caller-controlled input — the confused-deputy\n * shape this check exists to prevent. Its path must equal `McpBindOptions.endpointPath`, which\n * `WpMcpServer.bind(...)` verifies. A second hostname is a second deployment, not a second\n * audience.\n */\n setResource(resource: string): this {\n this.resourceValue = this.requireUrl(resource, 'setResource');\n return this;\n }\n\n /** REQUIRED. Application-owned paired mint/verify authority for MCP access tokens. */\n setAccessTokenAuthority(authority: McpAccessTokenAuthority<TGrant>): this {\n this.accessTokenAuthorityValue = this.requirePresent(authority, 'setAccessTokenAuthority');\n return this;\n }\n\n /** REQUIRED. The `JwtHook` minting the short-lived per-endpoint JWT for local bindings. */\n setEndpointJwtAuthority(authority: JwtHook<TMintRequest>): this {\n this.endpointJwtAuthorityValue = this.requirePresent(authority, 'setEndpointJwtAuthority');\n return this;\n }\n\n /** REQUIRED. Maps a verified MCP identity plus tool identity to that JwtHook's mint request. */\n setEndpointMintRequest(factory: McpEndpointMintRequestFactory<TMintRequest>): this {\n if (typeof factory !== 'function') {\n throw new Error('WpMcpServerConfig.setEndpointMintRequest(...) requires a function.');\n }\n this.endpointMintRequestValue = factory;\n return this;\n }\n\n /**\n * REQUIRED. The trusted token issuers. Every entry must be an absolute URL, because each is\n * compared against a token's `issuer` claim and published as an `authorization_servers` entry —\n * which is what makes a scope list landed here fail at boot rather than on the first request.\n */\n setAuthorizationServers(authorizationServers: readonly string[]): this {\n const servers = this.requireArray(authorizationServers, 'setAuthorizationServers');\n if (servers.length === 0) {\n throw new Error(\n 'WpMcpServerConfig.setAuthorizationServers(...) requires at least one issuer URL.',\n );\n }\n for (const server of servers) this.requireUrl(server, 'setAuthorizationServers');\n this.authorizationServersValue = servers;\n return this;\n }\n\n /**\n * REQUIRED, and empty only by saying so explicitly. Scopes every access token must carry before\n * any tool is dispatched.\n */\n setRequiredScopes(requiredScopes: readonly string[]): this {\n const scopes = this.requireArray(requiredScopes, 'setRequiredScopes');\n for (const scope of scopes) this.requireText(scope, 'setRequiredScopes');\n this.requiredScopesValue = scopes;\n return this;\n }\n\n /**\n * OPTIONAL. The application's `tools/call` error translator — it REPLACES the webpieces default\n * for every tool failure, owning the ENTIRE result, and declines by delegating to\n * `new McpDefaultToolCallRenderer().toToolCallResult(error)`. See {@link McpErrorTranslators}.\n */\n setErrorTranslator(translators: McpErrorTranslators): this {\n const value = this.requirePresent(translators, 'setErrorTranslator');\n if (typeof value.toToolCallResult !== 'function') {\n throw new Error(\n 'WpMcpServerConfig.setErrorTranslator(...) requires a toToolCallResult(error) method.',\n );\n }\n this.errorTranslatorValue = value;\n return this;\n }\n\n /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */\n setMaxAccountValidationAgeSeconds(seconds: number): this {\n this.maxAccountValidationAgeSecondsValue = this.requireSeconds(\n seconds,\n MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS,\n 'setMaxAccountValidationAgeSeconds',\n );\n return this;\n }\n\n /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */\n setMaxEndpointJwtLifetimeSeconds(seconds: number): this {\n this.maxEndpointJwtLifetimeSecondsValue = this.requireSeconds(\n seconds,\n MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS,\n 'setMaxEndpointJwtLifetimeSeconds',\n );\n return this;\n }\n\n /**\n * Called by `WpMcpServer.bind(...)`. Throws naming EVERY missing required setter at once, so a\n * half-built config is repaired in one pass rather than one boot failure at a time.\n */\n validate(): void {\n const missing: string[] = [];\n if (this.nameValue === undefined) missing.push('setName(...)');\n if (this.versionValue === undefined) missing.push('setVersion(...)');\n if (this.resourceValue === undefined) missing.push('setResource(...)');\n if (this.accessTokenAuthorityValue === undefined) {\n missing.push('setAccessTokenAuthority(...)');\n }\n if (this.endpointJwtAuthorityValue === undefined) {\n missing.push('setEndpointJwtAuthority(...)');\n }\n if (this.endpointMintRequestValue === undefined) {\n missing.push('setEndpointMintRequest(...)');\n }\n if (this.authorizationServersValue === undefined) {\n missing.push('setAuthorizationServers(...)');\n }\n if (this.requiredScopesValue === undefined) missing.push('setRequiredScopes(...)');\n if (missing.length > 0) {\n throw new Error(`WpMcpServerConfig is missing ${missing.join(', ')}`);\n }\n }\n\n get name(): string {\n return this.read(this.nameValue, 'setName');\n }\n\n get version(): string {\n return this.read(this.versionValue, 'setVersion');\n }\n\n get resource(): string {\n return this.read(this.resourceValue, 'setResource');\n }\n\n get accessTokenAuthority(): McpAccessTokenAuthority<TGrant> {\n return this.read(this.accessTokenAuthorityValue, 'setAccessTokenAuthority');\n }\n\n get endpointJwtAuthority(): JwtHook<TMintRequest> {\n return this.read(this.endpointJwtAuthorityValue, 'setEndpointJwtAuthority');\n }\n\n get endpointMintRequest(): McpEndpointMintRequestFactory<TMintRequest> {\n return this.read(this.endpointMintRequestValue, 'setEndpointMintRequest');\n }\n\n get authorizationServers(): readonly string[] {\n return this.read(this.authorizationServersValue, 'setAuthorizationServers');\n }\n\n get requiredScopes(): readonly string[] {\n return this.read(this.requiredScopesValue, 'setRequiredScopes');\n }\n\n /** `undefined` when the app registered none: webpieces then renders every tool failure. */\n get errorTranslator(): McpErrorTranslators | undefined {\n return this.errorTranslatorValue;\n }\n\n get maxAccountValidationAgeSeconds(): number {\n return this.maxAccountValidationAgeSecondsValue;\n }\n\n get maxEndpointJwtLifetimeSeconds(): number {\n return this.maxEndpointJwtLifetimeSecondsValue;\n }\n\n /**\n * RFC 9728 §3.1: the URL of this resource's protected-resource METADATA DOCUMENT, derived from\n * {@link resource} by inserting `/.well-known/oauth-protected-resource` between origin and path\n * (`https://host/mcp` → `https://host/.well-known/oauth-protected-resource/mcp`).\n *\n * This — never the resource identifier itself — is what RFC 9728 §5.1 defines `resource_metadata`\n * to be, so it is the value webpieces puts in the 401 `WWW-Authenticate` challenge. webpieces\n * mounts no `.well-known` route; publish {@link protectedResourceMetadata} at this URL.\n */\n get resourceMetadataUrl(): string {\n const url = new URL(this.resource);\n const path = url.pathname === '/' ? '' : url.pathname;\n return `${url.origin}/.well-known/oauth-protected-resource${path}`;\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return new McpProtectedResourceMetadata(\n this.resource,\n this.authorizationServers,\n this.requiredScopes,\n );\n }\n\n private requireText(value: string, setter: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires a non-empty string.`);\n }\n return value;\n }\n\n private requireUrl(value: string, setter: string): string {\n this.requireText(value, setter);\n if (!URL.canParse(value)) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) requires an absolute URL, got '${value}'.`,\n );\n }\n return value;\n }\n\n private requireArray(value: readonly string[], setter: string): readonly string[] {\n if (!Array.isArray(value)) {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires an array of strings.`);\n }\n return value;\n }\n\n private requireSeconds(value: number, ceiling: number, setter: string): number {\n if (!Number.isFinite(value) || value <= 0 || value > ceiling) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) requires 1..${ceiling} seconds, got ${String(value)}.`,\n );\n }\n return value;\n }\n\n private requirePresent<T>(value: T, setter: string): T {\n if (value === undefined || value === null) {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires a value.`);\n }\n return value;\n }\n\n private read<T>(value: T | undefined, setter: string): T {\n if (value === undefined) {\n throw new Error(`WpMcpServerConfig is missing ${setter}(...)`);\n }\n return value;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"McpAuth.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuth.ts"],"names":[],"mappings":"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","sourcesContent":["import { JwtHook } from '@webpieces/http-routing';\nimport { ContextTuple } from '@webpieces/core-util';\n\nexport const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;\nexport const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = 60 * 60;\nexport const MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS = 60 * 60;\n\n/** Framework-normalized access-token result. The token representation remains application-owned. */\nexport class MintedMcpAccessToken {\n constructor(\n public readonly token: string,\n public readonly resource: string,\n public readonly issuedAtEpochSeconds: number,\n public readonly expiresAtEpochSeconds: number,\n ) {\n if (token.trim() === '' || resource.trim() === '') {\n throw new Error('Minted MCP access token and resource must be non-empty.');\n }\n if (!Number.isFinite(issuedAtEpochSeconds) || !Number.isFinite(expiresAtEpochSeconds)) {\n throw new Error(\n 'Minted MCP access token timestamps must be finite epoch-second values.',\n );\n }\n if (expiresAtEpochSeconds <= issuedAtEpochSeconds) {\n throw new Error('MCP access token expiry must be after issuance.');\n }\n if (expiresAtEpochSeconds - issuedAtEpochSeconds > MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) {\n throw new Error('MCP access token lifetime must not exceed 30 days.');\n }\n }\n}\n\n/**\n * Fresh, authoritative security facts resolved at the MCP resource boundary. Implementations must\n * verify issuer, exact resource/audience, expiry, token type/version, key/algorithm policy, and\n * current account state. Opaque access tokens are fully supported.\n */\nexport class VerifiedMcpCredential {\n constructor(\n public readonly subject: string,\n public readonly issuer: string,\n public readonly resource: string,\n public readonly issuedAtEpochSeconds: number,\n public readonly expiresAtEpochSeconds: number,\n public readonly scopes: readonly string[],\n /** When current enabled/revoked state and roles were read from the authoritative source. */\n public readonly accountValidatedAtEpochSeconds: number,\n /** Advisory tools/list filtering only. Endpoint authorization always runs again. */\n public readonly listingRoles: readonly string[] = [],\n /** Trusted delegated identity derived only by the access-token authority. */\n public readonly trustedContext: readonly ContextTuple[] = [],\n ) {}\n}\n\n/**\n * Application-owned paired mint/verify authority for resource-bound MCP access tokens.\n * `verifyAccessToken` must throw `ApiUnauthorizedError` for a token it rejects (the bind boundary\n * answers 401 + `WWW-Authenticate`); any other throw is treated as an implementation failure (500).\n */\nexport interface McpAccessTokenAuthority<TGrant> {\n mintAccessToken(grant: TGrant): Promise<MintedMcpAccessToken>;\n verifyAccessToken(\n accessToken: string,\n expectedResource: string,\n ): Promise<VerifiedMcpCredential>;\n}\n\n/** Minimal tool identity supplied when the app creates its own endpoint-JWT mint request. */\nexport class McpEndpointDescriptor {\n constructor(\n public readonly toolName: string,\n public readonly apiClassName: string,\n public readonly methodName: string,\n ) {}\n}\n\n/** Application mapping from verified MCP identity to its unconstrained JwtHook mint request. */\nexport type McpEndpointMintRequestFactory<TMintRequest> = (\n credential: VerifiedMcpCredential,\n endpoint: McpEndpointDescriptor,\n) => TMintRequest;\n\n/** RFC 9728-style metadata exposed by the MCP protected resource. */\nexport class McpProtectedResourceMetadata {\n readonly resource: string;\n readonly authorization_servers: readonly string[];\n readonly bearer_methods_supported = ['header'];\n readonly scopes_supported?: readonly string[];\n\n constructor(\n resource: string,\n authorizationServers: readonly string[],\n scopesSupported?: readonly string[],\n ) {\n this.resource = resource;\n this.authorization_servers = authorizationServers;\n this.scopes_supported = scopesSupported;\n }\n}\n\n/**\n * Node MCP server configuration, built with fluent setters: every name sits next to its own value, so\n * no two settings can be swapped, and a future setting is an additive setter rather than a breaking\n * signature change. Pass the same concrete application authority to both\n * `setAccessTokenAuthority(...)` and `setEndpointJwtAuthority(...)` when it implements both security\n * seams.\n *\n * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource. {@link setResource} is\n * fixed for the lifetime of the process, is the audience of every access token, and is never derived\n * from the request `Host` — see that setter for why a host-derived audience is a confused deputy.\n *\n * Each setter validates its own value immediately and names itself in the failure. Setters cannot\n * make a field impossible to forget the way a positional constructor could, so\n * {@link WpMcpServerConfig.validate} runs from `WpMcpServer.bind(...)` and fails at startup listing\n * EVERY missing required setter at once. A misconfiguration is therefore a boot failure naming the\n * setter, instead of a first-request 401 + `WWW-Authenticate` — which is the OAuth discovery signal,\n * so the client's correct response is to re-authenticate and fail again, forever.\n *\n * ```ts\n * const config = new WpMcpServerConfig<MyGrant, MyMintRequest>()\n * .setName('my-server')\n * .setVersion('1.0.0')\n * .setResource('https://api.example.com/mcp')\n * .setAccessTokenAuthority(authority)\n * .setEndpointJwtAuthority(jwtHook)\n * .setEndpointMintRequest((credential) => new MyMintRequest(credential.subject))\n * .setAuthorizationServers(['https://login.example.com'])\n * .setRequiredScopes(['tools']);\n *\n * // the tools/call error translator is a PROCESS-GLOBAL, not a member of this config:\n * McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());\n * ```\n */\nexport class WpMcpServerConfig<TGrant, TMintRequest> {\n private nameValue?: string;\n private versionValue?: string;\n private resourceValue?: string;\n private accessTokenAuthorityValue?: McpAccessTokenAuthority<TGrant>;\n private endpointJwtAuthorityValue?: JwtHook<TMintRequest>;\n private endpointMintRequestValue?: McpEndpointMintRequestFactory<TMintRequest>;\n private authorizationServersValue?: readonly string[];\n private requiredScopesValue?: readonly string[];\n private maxAccountValidationAgeSecondsValue = MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS;\n private maxEndpointJwtLifetimeSecondsValue = MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS;\n\n /** REQUIRED. The MCP server name reported by `initialize`. */\n setName(name: string): this {\n this.nameValue = this.requireText(name, 'setName');\n return this;\n }\n\n /** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */\n setVersion(version: string): this {\n this.versionValue = this.requireText(version, 'setVersion');\n return this;\n }\n\n /**\n * REQUIRED. The ONE canonical protected-resource URI, which must be an absolute URL: it is the\n * exact audience every access token is checked against, and {@link resourceMetadataUrl} is\n * derived from it for the 401 challenge, so a non-URL here is always a mistake — including a\n * name or version landed on it.\n *\n * It is fixed for the lifetime of the process and must NEVER be derived from the request `Host`\n * header: the boundary compares a token's audience against this value, so a host-derived\n * resource compares two values both taken from caller-controlled input — the confused-deputy\n * shape this check exists to prevent. Its path must equal `McpBindOptions.endpointPath`, which\n * `WpMcpServer.bind(...)` verifies. A second hostname is a second deployment, not a second\n * audience.\n */\n setResource(resource: string): this {\n this.resourceValue = this.requireUrl(resource, 'setResource');\n return this;\n }\n\n /** REQUIRED. Application-owned paired mint/verify authority for MCP access tokens. */\n setAccessTokenAuthority(authority: McpAccessTokenAuthority<TGrant>): this {\n this.accessTokenAuthorityValue = this.requirePresent(authority, 'setAccessTokenAuthority');\n return this;\n }\n\n /** REQUIRED. The `JwtHook` minting the short-lived per-endpoint JWT for local bindings. */\n setEndpointJwtAuthority(authority: JwtHook<TMintRequest>): this {\n this.endpointJwtAuthorityValue = this.requirePresent(authority, 'setEndpointJwtAuthority');\n return this;\n }\n\n /** REQUIRED. Maps a verified MCP identity plus tool identity to that JwtHook's mint request. */\n setEndpointMintRequest(factory: McpEndpointMintRequestFactory<TMintRequest>): this {\n if (typeof factory !== 'function') {\n throw new Error('WpMcpServerConfig.setEndpointMintRequest(...) requires a function.');\n }\n this.endpointMintRequestValue = factory;\n return this;\n }\n\n /**\n * REQUIRED. The trusted token issuers. Every entry must be an absolute URL, because each is\n * compared against a token's `issuer` claim and published as an `authorization_servers` entry —\n * which is what makes a scope list landed here fail at boot rather than on the first request.\n */\n setAuthorizationServers(authorizationServers: readonly string[]): this {\n const servers = this.requireArray(authorizationServers, 'setAuthorizationServers');\n if (servers.length === 0) {\n throw new Error(\n 'WpMcpServerConfig.setAuthorizationServers(...) requires at least one issuer URL.',\n );\n }\n for (const server of servers) this.requireUrl(server, 'setAuthorizationServers');\n this.authorizationServersValue = servers;\n return this;\n }\n\n /**\n * REQUIRED, and empty only by saying so explicitly. Scopes every access token must carry before\n * any tool is dispatched.\n */\n setRequiredScopes(requiredScopes: readonly string[]): this {\n const scopes = this.requireArray(requiredScopes, 'setRequiredScopes');\n for (const scope of scopes) this.requireText(scope, 'setRequiredScopes');\n this.requiredScopesValue = scopes;\n return this;\n }\n\n /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */\n setMaxAccountValidationAgeSeconds(seconds: number): this {\n this.maxAccountValidationAgeSecondsValue = this.requireSeconds(\n seconds,\n MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS,\n 'setMaxAccountValidationAgeSeconds',\n );\n return this;\n }\n\n /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */\n setMaxEndpointJwtLifetimeSeconds(seconds: number): this {\n this.maxEndpointJwtLifetimeSecondsValue = this.requireSeconds(\n seconds,\n MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS,\n 'setMaxEndpointJwtLifetimeSeconds',\n );\n return this;\n }\n\n /**\n * Called by `WpMcpServer.bind(...)`. Throws naming EVERY missing required setter at once, so a\n * half-built config is repaired in one pass rather than one boot failure at a time.\n */\n validate(): void {\n const missing: string[] = [];\n if (this.nameValue === undefined) missing.push('setName(...)');\n if (this.versionValue === undefined) missing.push('setVersion(...)');\n if (this.resourceValue === undefined) missing.push('setResource(...)');\n if (this.accessTokenAuthorityValue === undefined) {\n missing.push('setAccessTokenAuthority(...)');\n }\n if (this.endpointJwtAuthorityValue === undefined) {\n missing.push('setEndpointJwtAuthority(...)');\n }\n if (this.endpointMintRequestValue === undefined) {\n missing.push('setEndpointMintRequest(...)');\n }\n if (this.authorizationServersValue === undefined) {\n missing.push('setAuthorizationServers(...)');\n }\n if (this.requiredScopesValue === undefined) missing.push('setRequiredScopes(...)');\n if (missing.length > 0) {\n throw new Error(`WpMcpServerConfig is missing ${missing.join(', ')}`);\n }\n }\n\n get name(): string {\n return this.read(this.nameValue, 'setName');\n }\n\n get version(): string {\n return this.read(this.versionValue, 'setVersion');\n }\n\n get resource(): string {\n return this.read(this.resourceValue, 'setResource');\n }\n\n get accessTokenAuthority(): McpAccessTokenAuthority<TGrant> {\n return this.read(this.accessTokenAuthorityValue, 'setAccessTokenAuthority');\n }\n\n get endpointJwtAuthority(): JwtHook<TMintRequest> {\n return this.read(this.endpointJwtAuthorityValue, 'setEndpointJwtAuthority');\n }\n\n get endpointMintRequest(): McpEndpointMintRequestFactory<TMintRequest> {\n return this.read(this.endpointMintRequestValue, 'setEndpointMintRequest');\n }\n\n get authorizationServers(): readonly string[] {\n return this.read(this.authorizationServersValue, 'setAuthorizationServers');\n }\n\n get requiredScopes(): readonly string[] {\n return this.read(this.requiredScopesValue, 'setRequiredScopes');\n }\n\n get maxAccountValidationAgeSeconds(): number {\n return this.maxAccountValidationAgeSecondsValue;\n }\n\n get maxEndpointJwtLifetimeSeconds(): number {\n return this.maxEndpointJwtLifetimeSecondsValue;\n }\n\n /**\n * RFC 9728 §3.1: the URL of this resource's protected-resource METADATA DOCUMENT, derived from\n * {@link resource} by inserting `/.well-known/oauth-protected-resource` between origin and path\n * (`https://host/mcp` → `https://host/.well-known/oauth-protected-resource/mcp`).\n *\n * This — never the resource identifier itself — is what RFC 9728 §5.1 defines `resource_metadata`\n * to be, so it is the value webpieces puts in the 401 `WWW-Authenticate` challenge. webpieces\n * mounts no `.well-known` route; publish {@link protectedResourceMetadata} at this URL.\n */\n get resourceMetadataUrl(): string {\n const url = new URL(this.resource);\n const path = url.pathname === '/' ? '' : url.pathname;\n return `${url.origin}/.well-known/oauth-protected-resource${path}`;\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return new McpProtectedResourceMetadata(\n this.resource,\n this.authorizationServers,\n this.requiredScopes,\n );\n }\n\n private requireText(value: string, setter: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires a non-empty string.`);\n }\n return value;\n }\n\n private requireUrl(value: string, setter: string): string {\n this.requireText(value, setter);\n if (!URL.canParse(value)) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) requires an absolute URL, got '${value}'.`,\n );\n }\n return value;\n }\n\n private requireArray(value: readonly string[], setter: string): readonly string[] {\n if (!Array.isArray(value)) {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires an array of strings.`);\n }\n return value;\n }\n\n private requireSeconds(value: number, ceiling: number, setter: string): number {\n if (!Number.isFinite(value) || value <= 0 || value > ceiling) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) requires 1..${ceiling} seconds, got ${String(value)}.`,\n );\n }\n return value;\n }\n\n private requirePresent<T>(value: T, setter: string): T {\n if (value === undefined || value === null) {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires a value.`);\n }\n return value;\n }\n\n private read<T>(value: T | undefined, setter: string): T {\n if (value === undefined) {\n throw new Error(`WpMcpServerConfig is missing ${setter}(...)`);\n }\n return value;\n }\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { McpErrorTranslator } from './McpToolCallRendering';
|
|
2
|
+
/**
|
|
3
|
+
* McpRegistry - the process-global home of the ONE {@link McpErrorTranslator}, mirroring
|
|
4
|
+
* `ClientRegistry` for HTTP and `IpcRegistry` for IPC.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* // startup, ONCE per process
|
|
8
|
+
* McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* # This REVERSES the constructor-argument decision of #953, deliberately
|
|
12
|
+
*
|
|
13
|
+
* `WpMcpServerConfig.setErrorTranslator(...)` used to carry this, on the argument that `WpMcpServer`
|
|
14
|
+
* is constructed by app code and two servers may run in one process. The trade is now taken the other
|
|
15
|
+
* way, with the repo owner's explicit agreement: two MCP servers in one process share ONE translator,
|
|
16
|
+
* and registration order matters rather than being fixed at construction.
|
|
17
|
+
*
|
|
18
|
+
* What buys that back is uniformity. HTTP, MCP and IPC now have the identical shape — one registry per
|
|
19
|
+
* protocol, a non-optional `getErrorTranslator()`, a webpieces default that is always installed, and
|
|
20
|
+
* therefore exactly one unconditional line at every call site. The "did anyone register one" branch
|
|
21
|
+
* that the optional config member forced on `WpMcpErrorTranslator` is gone, and an app reads one
|
|
22
|
+
* pattern instead of three.
|
|
23
|
+
*/
|
|
24
|
+
export declare class McpRegistry {
|
|
25
|
+
/**
|
|
26
|
+
* THE translator. NEVER undefined — it starts life holding a {@link McpDefaultToolCallRenderer},
|
|
27
|
+
* and {@link McpRegistry.setErrorTranslator} REPLACES it.
|
|
28
|
+
*/
|
|
29
|
+
private static errorTranslator;
|
|
30
|
+
/**
|
|
31
|
+
* Install the app's {@link McpErrorTranslator} — it REPLACES the webpieces default for every
|
|
32
|
+
* `tools/call` failure, owning the ENTIRE result, and declines by delegating to
|
|
33
|
+
* `new McpDefaultToolCallRenderer().toWire(error)`.
|
|
34
|
+
*/
|
|
35
|
+
static setErrorTranslator(translator: McpErrorTranslator): void;
|
|
36
|
+
/** THE {@link McpErrorTranslator} for this process — never `undefined`, so no caller branches. */
|
|
37
|
+
static getErrorTranslator(): McpErrorTranslator;
|
|
38
|
+
/** Put the process-global back to the webpieces default. For TESTS, so specs cannot leak. */
|
|
39
|
+
static resetForTests(): void;
|
|
40
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.McpRegistry = void 0;
|
|
4
|
+
const McpToolCallRendering_1 = require("./McpToolCallRendering");
|
|
5
|
+
/**
|
|
6
|
+
* McpRegistry - the process-global home of the ONE {@link McpErrorTranslator}, mirroring
|
|
7
|
+
* `ClientRegistry` for HTTP and `IpcRegistry` for IPC.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // startup, ONCE per process
|
|
11
|
+
* McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* # This REVERSES the constructor-argument decision of #953, deliberately
|
|
15
|
+
*
|
|
16
|
+
* `WpMcpServerConfig.setErrorTranslator(...)` used to carry this, on the argument that `WpMcpServer`
|
|
17
|
+
* is constructed by app code and two servers may run in one process. The trade is now taken the other
|
|
18
|
+
* way, with the repo owner's explicit agreement: two MCP servers in one process share ONE translator,
|
|
19
|
+
* and registration order matters rather than being fixed at construction.
|
|
20
|
+
*
|
|
21
|
+
* What buys that back is uniformity. HTTP, MCP and IPC now have the identical shape — one registry per
|
|
22
|
+
* protocol, a non-optional `getErrorTranslator()`, a webpieces default that is always installed, and
|
|
23
|
+
* therefore exactly one unconditional line at every call site. The "did anyone register one" branch
|
|
24
|
+
* that the optional config member forced on `WpMcpErrorTranslator` is gone, and an app reads one
|
|
25
|
+
* pattern instead of three.
|
|
26
|
+
*/
|
|
27
|
+
class McpRegistry {
|
|
28
|
+
/**
|
|
29
|
+
* THE translator. NEVER undefined — it starts life holding a {@link McpDefaultToolCallRenderer},
|
|
30
|
+
* and {@link McpRegistry.setErrorTranslator} REPLACES it.
|
|
31
|
+
*/
|
|
32
|
+
static errorTranslator = new McpToolCallRendering_1.McpDefaultToolCallRenderer();
|
|
33
|
+
/**
|
|
34
|
+
* Install the app's {@link McpErrorTranslator} — it REPLACES the webpieces default for every
|
|
35
|
+
* `tools/call` failure, owning the ENTIRE result, and declines by delegating to
|
|
36
|
+
* `new McpDefaultToolCallRenderer().toWire(error)`.
|
|
37
|
+
*/
|
|
38
|
+
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
39
|
+
static setErrorTranslator(translator) {
|
|
40
|
+
McpRegistry.errorTranslator = translator;
|
|
41
|
+
}
|
|
42
|
+
/** THE {@link McpErrorTranslator} for this process — never `undefined`, so no caller branches. */
|
|
43
|
+
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
44
|
+
static getErrorTranslator() {
|
|
45
|
+
return McpRegistry.errorTranslator;
|
|
46
|
+
}
|
|
47
|
+
/** Put the process-global back to the webpieces default. For TESTS, so specs cannot leak. */
|
|
48
|
+
// webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected
|
|
49
|
+
static resetForTests() {
|
|
50
|
+
McpRegistry.errorTranslator = new McpToolCallRendering_1.McpDefaultToolCallRenderer();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.McpRegistry = McpRegistry;
|
|
54
|
+
//# sourceMappingURL=McpRegistry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpRegistry.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpRegistry.ts"],"names":[],"mappings":";;;AAAA,iEAAwF;AAExF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAa,WAAW;IACpB;;;OAGG;IACK,MAAM,CAAC,eAAe,GAAuB,IAAI,iDAA0B,EAAE,CAAC;IAEtF;;;;OAIG;IACH,4JAA4J;IAC5J,MAAM,CAAC,kBAAkB,CAAC,UAA8B;QACpD,WAAW,CAAC,eAAe,GAAG,UAAU,CAAC;IAC7C,CAAC;IAED,kGAAkG;IAClG,4JAA4J;IAC5J,MAAM,CAAC,kBAAkB;QACrB,OAAO,WAAW,CAAC,eAAe,CAAC;IACvC,CAAC;IAED,6FAA6F;IAC7F,4JAA4J;IAC5J,MAAM,CAAC,aAAa;QAChB,WAAW,CAAC,eAAe,GAAG,IAAI,iDAA0B,EAAE,CAAC;IACnE,CAAC;;AA3BL,kCA4BC","sourcesContent":["import { McpDefaultToolCallRenderer, McpErrorTranslator } from './McpToolCallRendering';\n\n/**\n * McpRegistry - the process-global home of the ONE {@link McpErrorTranslator}, mirroring\n * `ClientRegistry` for HTTP and `IpcRegistry` for IPC.\n *\n * ```ts\n * // startup, ONCE per process\n * McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());\n * ```\n *\n * # This REVERSES the constructor-argument decision of #953, deliberately\n *\n * `WpMcpServerConfig.setErrorTranslator(...)` used to carry this, on the argument that `WpMcpServer`\n * is constructed by app code and two servers may run in one process. The trade is now taken the other\n * way, with the repo owner's explicit agreement: two MCP servers in one process share ONE translator,\n * and registration order matters rather than being fixed at construction.\n *\n * What buys that back is uniformity. HTTP, MCP and IPC now have the identical shape — one registry per\n * protocol, a non-optional `getErrorTranslator()`, a webpieces default that is always installed, and\n * therefore exactly one unconditional line at every call site. The \"did anyone register one\" branch\n * that the optional config member forced on `WpMcpErrorTranslator` is gone, and an app reads one\n * pattern instead of three.\n */\nexport class McpRegistry {\n /**\n * THE translator. NEVER undefined — it starts life holding a {@link McpDefaultToolCallRenderer},\n * and {@link McpRegistry.setErrorTranslator} REPLACES it.\n */\n private static errorTranslator: McpErrorTranslator = new McpDefaultToolCallRenderer();\n\n /**\n * Install the app's {@link McpErrorTranslator} — it REPLACES the webpieces default for every\n * `tools/call` failure, owning the ENTIRE result, and declines by delegating to\n * `new McpDefaultToolCallRenderer().toWire(error)`.\n */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static setErrorTranslator(translator: McpErrorTranslator): void {\n McpRegistry.errorTranslator = translator;\n }\n\n /** THE {@link McpErrorTranslator} for this process — never `undefined`, so no caller branches. */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static getErrorTranslator(): McpErrorTranslator {\n return McpRegistry.errorTranslator;\n }\n\n /** Put the process-global back to the webpieces default. For TESTS, so specs cannot leak. */\n // webpieces-disable no-function-outside-class -- static global singleton (like HeaderRegistry/ClientRegistry); populated once at startup, never DI-injected\n static resetForTests(): void {\n McpRegistry.errorTranslator = new McpDefaultToolCallRenderer();\n }\n}\n"]}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { CallToolResult } from '@modelcontextprotocol/server';
|
|
2
|
+
import { ContextKey } from '@webpieces/core-util';
|
|
3
|
+
/** `_meta` key carrying the Webpieces requestId on every tools/call result, success or failure. */
|
|
4
|
+
export declare const MCP_REQUEST_ID_META_KEY = "webpieces/requestId";
|
|
5
|
+
/** JSON-RPC error `data` so a user can quote the requestId of any protocol-level failure. */
|
|
6
|
+
export declare class McpErrorData {
|
|
7
|
+
readonly requestId: string;
|
|
8
|
+
constructor(requestId: string);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The two per-request facts a renderer needs that are NOT the error: the JSON-RPC `id` it must echo,
|
|
12
|
+
* and the tool being called. They are stamped into the request scope by `WpMcpServer` rather than
|
|
13
|
+
* threaded through every renderer signature, because `requestId` already travels that way
|
|
14
|
+
* ({@link WebpiecesCoreHeaders.REQUEST_ID}) and a second, parallel correlation parameter was a COPY
|
|
15
|
+
* of the context — which is what the deleted `McpFailureScope` was.
|
|
16
|
+
*
|
|
17
|
+
* Context-only and never logged: the logging backends read `RequestContext` for correlation on every
|
|
18
|
+
* record already, and this object exists purely so the renderers can be one-argument functions.
|
|
19
|
+
*/
|
|
20
|
+
export declare class McpCorrelation {
|
|
21
|
+
readonly jsonRpcId: string | number | null;
|
|
22
|
+
readonly toolName?: string | undefined;
|
|
23
|
+
static readonly KEY: ContextKey<McpCorrelation, "untrusted">;
|
|
24
|
+
constructor(jsonRpcId: string | number | null, toolName?: string | undefined);
|
|
25
|
+
/** Stamp what this request knows so far; `WpMcpServer` re-stamps once the tool name is known. */
|
|
26
|
+
static stamp(correlation: McpCorrelation): void;
|
|
27
|
+
/** A renderer reached before anything stamped (a failure with no parseable body) still renders. */
|
|
28
|
+
static current(): McpCorrelation;
|
|
29
|
+
/** The webpieces requestId of the request being served, as every MCP reply reports it. */
|
|
30
|
+
static requestId(): string;
|
|
31
|
+
}
|
|
32
|
+
/** The only error shape rendered into model-visible MCP tool content. */
|
|
33
|
+
export declare class ModelVisibleToolError {
|
|
34
|
+
readonly kind: string;
|
|
35
|
+
readonly message: string;
|
|
36
|
+
readonly requestId: string;
|
|
37
|
+
readonly field?: string | undefined;
|
|
38
|
+
readonly callerMessage?: string | undefined;
|
|
39
|
+
readonly errorCode?: string | undefined;
|
|
40
|
+
readonly retryAfterSeconds?: number | undefined;
|
|
41
|
+
readonly statusCode?: number | undefined;
|
|
42
|
+
constructor(kind: string, message: string, requestId: string, field?: string | undefined, callerMessage?: string | undefined, errorCode?: string | undefined, retryAfterSeconds?: number | undefined, statusCode?: number | undefined);
|
|
43
|
+
}
|
|
44
|
+
/** JSON-RPC error body written by the HTTP boundary before the SDK is involved. */
|
|
45
|
+
export declare class McpHttpErrorBody {
|
|
46
|
+
readonly error: McpHttpErrorDetail;
|
|
47
|
+
readonly id: string | number | null;
|
|
48
|
+
readonly jsonrpc = "2.0";
|
|
49
|
+
constructor(error: McpHttpErrorDetail, id: string | number | null);
|
|
50
|
+
}
|
|
51
|
+
export declare class McpHttpErrorDetail {
|
|
52
|
+
readonly code: number;
|
|
53
|
+
readonly message: string;
|
|
54
|
+
readonly data: McpErrorData;
|
|
55
|
+
constructor(code: number, message: string, data: McpErrorData);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The application's own `tools/call` error translation — the MCP twin of the `ErrorTranslator` an app
|
|
59
|
+
* registers on `ClientRegistry` for HTTP and the `IpcErrorTranslator` it registers on `IpcRegistry`.
|
|
60
|
+
* Register one with {@link McpRegistry.setErrorTranslator}.
|
|
61
|
+
*
|
|
62
|
+
* An app owns its error taxonomy and owns how those errors should be explained to a model, so a
|
|
63
|
+
* claimed error yields the ENTIRE `CallToolResult` — content, `structuredContent`, `isError`, the lot.
|
|
64
|
+
*
|
|
65
|
+
* # There is NO `fromWire`, and that is the protocol, not an oversight
|
|
66
|
+
*
|
|
67
|
+
* The HTTP and IPC translators have two halves because webpieces sits on BOTH ends of those wires: it
|
|
68
|
+
* writes the response AND, in another process, reads one. **webpieces is never the MCP client.** The
|
|
69
|
+
* client is Claude (or another model host); there is no return path for webpieces to translate, so
|
|
70
|
+
* the pair is one-directional by nature. Do not "restore the symmetry" by adding a half with nothing
|
|
71
|
+
* to call it.
|
|
72
|
+
*
|
|
73
|
+
* # The pre-SDK boundary does NOT share the HTTP `ErrorTranslator`, despite the identical type
|
|
74
|
+
*
|
|
75
|
+
* `WpMcpErrorTranslator.toBearerBoundaryResponse` returns an `HttpResponseDto`, exactly like
|
|
76
|
+
* `ErrorTranslator.toWire` — and it must STILL not be routed through it. Two reasons, both hard:
|
|
77
|
+
*
|
|
78
|
+
* - its body must stay JSON-RPC shaped (`McpHttpErrorBody`) or an MCP client cannot parse the reply
|
|
79
|
+
* at all, whereas an app's HTTP translator publishes the app's own REST envelope;
|
|
80
|
+
* - it emits the `401 + WWW-Authenticate: Bearer resource_metadata=...` that MCP clients use for OAuth
|
|
81
|
+
* discovery, so an app rewriting it breaks connector onboarding.
|
|
82
|
+
*
|
|
83
|
+
* Same TYPE, different OWNER. `tools/list` is framework-owned for the same reason.
|
|
84
|
+
*
|
|
85
|
+
* Contract:
|
|
86
|
+
* - `error` is the RAW thrown value: an app must be able to `instanceof` its own error classes.
|
|
87
|
+
* `ApiErrorBoundary` never substitutes an object for the thrown error, so there is exactly one
|
|
88
|
+
* error value on this path.
|
|
89
|
+
* - There is no "not mine" return. A registered translator REPLACES the webpieces default and
|
|
90
|
+
* declines by DELEGATING to it: `new McpDefaultToolCallRenderer().toWire(error)`.
|
|
91
|
+
* - webpieces default-fills `_meta['webpieces/requestId']` only when the returned result has NO
|
|
92
|
+
* `_meta`. An app that sets `_meta` owns it untouched.
|
|
93
|
+
* - scope is `tools/call` ONLY.
|
|
94
|
+
*/
|
|
95
|
+
export interface McpErrorTranslator {
|
|
96
|
+
/** An error in, the whole `tools/call` reply out. Named for its HTTP and IPC twins. */
|
|
97
|
+
toWire(error: Error): CallToolResult;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* webpieces' DEFAULT `tools/call` rendering, and what {@link McpRegistry} holds until an app installs
|
|
101
|
+
* its own. Public so an app's own {@link McpErrorTranslator} can DECLINE an error by delegating to it
|
|
102
|
+
* — the MCP twin of `WebpiecesDefaultErrorTranslator.toWire` on the HTTP side.
|
|
103
|
+
*
|
|
104
|
+
* Published text comes only from {@link ApiErrorBoundary.encode}: this never puts an error's own
|
|
105
|
+
* message in front of a model unless it is an `ApiEndUserError`.
|
|
106
|
+
*/
|
|
107
|
+
export declare class McpDefaultToolCallRenderer implements McpErrorTranslator {
|
|
108
|
+
private readonly boundary;
|
|
109
|
+
toWire(error: Error): CallToolResult;
|
|
110
|
+
private modelVisible;
|
|
111
|
+
}
|