@webpieces/mcp-server 0.4.782 → 0.4.784

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -75,6 +75,36 @@ A map is still a closed schema: no key can carry an unspecified value. Check clo
75
75
  `new DtoSchemaBuilder().isClosedSchema(schema)`, not `schema.additionalProperties === false`, which wrongly
76
76
  rejects typed maps.
77
77
 
78
+ ## Configuration
79
+
80
+ `WpMcpServerConfig` is built with fluent setters — every name sits next to its own value, so no two
81
+ settings can be swapped, and a future setting is an additive setter rather than a breaking signature
82
+ change. Expect to supply the type arguments explicitly:
83
+
84
+ ```ts
85
+ const config = new WpMcpServerConfig<MyGrant, MyMintRequest>()
86
+ .setName('my-server')
87
+ .setVersion('1.0.0')
88
+ .setResource('https://api.example.com/mcp')
89
+ .setAccessTokenAuthority(authority)
90
+ .setEndpointJwtAuthority(jwtHook)
91
+ .setEndpointMintRequest((credential) => new MyMintRequest(credential.subject))
92
+ .setAuthorizationServers(['https://login.example.com'])
93
+ .setRequiredScopes(['tools'])
94
+ .setErrorTranslator(new MyMcpErrorTranslators())
95
+ .setMaxAccountValidationAgeSeconds(15 * 60);
96
+ ```
97
+
98
+ Each setter validates its own value immediately and names itself in the failure: `setResource(...)`
99
+ and every entry of `setAuthorizationServers(...)` must be an absolute URL, because they are compared
100
+ against a token's audience and issuer. `setMaxAccountValidationAgeSeconds` and
101
+ `setMaxEndpointJwtLifetimeSeconds` default to one hour and are capped there.
102
+ `setErrorTranslator(...)` is the only optional non-ceiling setting; everything else is required and
103
+ `WpMcpServer.bind(...)` fails at startup listing **every** missing setter
104
+ (`WpMcpServerConfig is missing setResource(...)`). That is deliberate: a misconfigured audience or
105
+ issuer otherwise surfaces as a first-request `401 + WWW-Authenticate`, which is the OAuth discovery
106
+ signal, so a client answers it by re-authenticating and failing again — forever.
107
+
78
108
  Applications supply the endpoint path, explicit bindings, and deployment topology. There is no
79
109
  framework-owned `/mcp` path:
80
110
 
@@ -90,6 +120,14 @@ mcp.bind(expressApp, new McpBindOptions(
90
120
  ));
91
121
  ```
92
122
 
123
+ `bind(...)` registers POST at that path plus a 405 (`Allow: POST`) for every other method there — MCP
124
+ 2026-07-28 has no session GET, so a probe gets a method error rather than a 404. It may be called only
125
+ once: one server serves exactly one canonical resource URI. `WpMcpServerConfig.resource` is that URI,
126
+ fixed for the process lifetime and never derived from the request `Host` (a host-derived audience would
127
+ make the boundary's own audience check circular — a confused deputy), and its path must equal
128
+ `endpointPath`, which `bind(...)` verifies. A second hostname is a second deployment, not a second
129
+ audience.
130
+
93
131
  The official MCP v2 server and Node adapter own MCP 2026-07-28 JSON-RPC validation, JSON versus
94
132
  request-scoped SSE responses, backpressure, cancellation, keepalives, and subscriptions. Webpieces
95
133
  owns the Express path, root `RequestContext`, authentication boundary, schema-derived tool registry,
@@ -150,6 +188,40 @@ Every reply carries the requestId so a user can quote it: `_meta["webpieces/requ
150
188
  `tools/call` result (success or `isError`), `requestId` in the `isError` payload, and
151
189
  `error.data.requestId` on JSON-RPC and HTTP-boundary errors.
152
190
 
191
+ ### The application's own `tools/call` translation
192
+
193
+ An app owns its error taxonomy and owns how those errors should be explained to a model, so it gets
194
+ FIRST REFUSAL on every `tools/call` failure — the same convention `ExpressWrapper.handleError`
195
+ applies on the HTTP path through `ClientRegistry.tryTranslateToWire`. Register one with
196
+ `WpMcpServerConfig.setErrorTranslator(...)`; it is a setter and not a global, because `WpMcpServer` is
197
+ constructed by app code and two servers may run in one process.
198
+
199
+ ```ts
200
+ class LangMcpErrorTranslators implements McpErrorTranslators {
201
+ toToolResult(error: Error, scope: McpFailureScope): CallToolResult | undefined {
202
+ if (!(error instanceof LangPassageLockedError)) return undefined; // not mine
203
+ return {
204
+ content: [{ type: 'text', text: `Passage ${error.passageId} is locked.` }],
205
+ structuredContent: { passageId: error.passageId, action: 'ask_the_user_to_unlock' },
206
+ isError: true,
207
+ };
208
+ }
209
+ }
210
+ ```
211
+
212
+ - `error` is the RAW thrown value, NOT normalized, so `instanceof` on the app's own classes works.
213
+ - Returning `undefined` means "not mine" and the webpieces default renders, unchanged.
214
+ - A claimed error owns the ENTIRE `CallToolResult` — content, `structuredContent`, `isError`.
215
+ - webpieces default-fills `_meta["webpieces/requestId"]` only when the returned result has no
216
+ `_meta`; an app that sets `_meta` keeps it untouched.
217
+ - Operator-detail logging has already run when the translator is called, so claiming an error can
218
+ never silently kill observability. A translator that THROWS is itself reported through the same
219
+ boundary and the original error still renders the reply.
220
+ - The scope is `tools/call` ONLY. `tools/list` and the pre-SDK HTTP boundary stay framework-owned:
221
+ that boundary emits the `401 + WWW-Authenticate: Bearer resource_metadata=...` MCP clients depend
222
+ on for OAuth discovery, and an app rewriting it breaks connector onboarding in a way that is
223
+ extremely hard to debug.
224
+
153
225
  A remote binding's generated Node client turns a dependency's 4xx into the gateway's own
154
226
  `ApiImplementationError` (see `NodeProxyClient.adaptDownstreamFailure`). A gateway that wants the model
155
227
  to see the peer's typed 4xx instead registers an `ErrorTranslators` via
@@ -164,3 +236,9 @@ rotation metadata. Opaque tokens remain valid implementations of the same author
164
236
 
165
237
  `protectedResourceMetadata()` returns the resource metadata an HTTP adapter can publish at the
166
238
  well-known OAuth protected-resource endpoint. OAuth token issuance remains pluggable.
239
+
240
+ That endpoint's URL is `WpMcpServerConfig.resourceMetadataUrl` — RFC 9728 §3.1 path insertion, so
241
+ `https://host/mcp` publishes at `https://host/.well-known/oauth-protected-resource/mcp` — and it is the
242
+ value of `resource_metadata` in the 401 `WWW-Authenticate` challenge. RFC 9728 §5.1 defines that
243
+ parameter as the URL of the metadata DOCUMENT, never the resource identifier itself. The framework
244
+ mounts no `.well-known` route; the app publishes the document there.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/mcp-server",
3
- "version": "0.4.782",
3
+ "version": "0.4.784",
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.782",
28
- "@webpieces/core-util": "0.4.782",
29
- "@webpieces/http-routing": "0.4.782",
27
+ "@webpieces/core-context": "0.4.784",
28
+ "@webpieces/core-util": "0.4.784",
29
+ "@webpieces/http-routing": "0.4.784",
30
30
  "express": "5.1.0",
31
31
  "reflect-metadata": "0.2.2",
32
32
  "tslib": "2.8.1"
33
33
  },
34
34
  "devDependencies": {
35
- "@webpieces/http-client-node": "0.4.782",
36
- "@webpieces/http-server": "0.4.782"
35
+ "@webpieces/http-client-node": "0.4.784",
36
+ "@webpieces/http-server": "0.4.784"
37
37
  }
38
38
  }
package/src/McpAuth.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { JwtHook } from '@webpieces/http-routing';
2
2
  import { ContextTuple } from '@webpieces/core-util';
3
+ import { McpErrorTranslators } from './WpMcpErrorTranslator';
3
4
  export declare const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS: number;
4
5
  export declare const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS: number;
5
6
  export declare const MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS: number;
@@ -64,20 +65,124 @@ export declare class McpProtectedResourceMetadata {
64
65
  constructor(resource: string, authorizationServers: readonly string[], scopesSupported?: readonly string[]);
65
66
  }
66
67
  /**
67
- * Node MCP server configuration. Pass the same concrete application authority as both
68
- * accessTokenAuthority and endpointJwtAuthority when it implements both security seams.
68
+ * Node MCP server configuration, built with fluent setters: every name sits next to its own value, so
69
+ * no two settings can be swapped, and a future setting is an additive setter rather than a breaking
70
+ * signature change. Pass the same concrete application authority to both
71
+ * `setAccessTokenAuthority(...)` and `setEndpointJwtAuthority(...)` when it implements both security
72
+ * seams.
73
+ *
74
+ * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource. {@link setResource} is
75
+ * fixed for the lifetime of the process, is the audience of every access token, and is never derived
76
+ * from the request `Host` — see that setter for why a host-derived audience is a confused deputy.
77
+ *
78
+ * Each setter validates its own value immediately and names itself in the failure. Setters cannot
79
+ * make a field impossible to forget the way a positional constructor could, so
80
+ * {@link WpMcpServerConfig.validate} runs from `WpMcpServer.bind(...)` and fails at startup listing
81
+ * EVERY missing required setter at once. A misconfiguration is therefore a boot failure naming the
82
+ * setter, instead of a first-request 401 + `WWW-Authenticate` — which is the OAuth discovery signal,
83
+ * so the client's correct response is to re-authenticate and fail again, forever.
84
+ *
85
+ * ```ts
86
+ * const config = new WpMcpServerConfig<MyGrant, MyMintRequest>()
87
+ * .setName('my-server')
88
+ * .setVersion('1.0.0')
89
+ * .setResource('https://api.example.com/mcp')
90
+ * .setAccessTokenAuthority(authority)
91
+ * .setEndpointJwtAuthority(jwtHook)
92
+ * .setEndpointMintRequest((credential) => new MyMintRequest(credential.subject))
93
+ * .setAuthorizationServers(['https://login.example.com'])
94
+ * .setRequiredScopes(['tools'])
95
+ * .setErrorTranslator(new MyMcpErrorTranslators());
96
+ * ```
69
97
  */
70
98
  export declare class WpMcpServerConfig<TGrant, TMintRequest> {
71
- readonly name: string;
72
- readonly version: string;
73
- readonly resource: string;
74
- readonly accessTokenAuthority: McpAccessTokenAuthority<TGrant>;
75
- readonly endpointJwtAuthority: JwtHook<TMintRequest>;
76
- readonly endpointMintRequest: McpEndpointMintRequestFactory<TMintRequest>;
77
- readonly authorizationServers: readonly string[];
78
- readonly requiredScopes: readonly string[];
79
- readonly maxAccountValidationAgeSeconds: number;
80
- readonly maxEndpointJwtLifetimeSeconds: number;
81
- constructor(name: string, version: string, resource: string, accessTokenAuthority: McpAccessTokenAuthority<TGrant>, endpointJwtAuthority: JwtHook<TMintRequest>, endpointMintRequest: McpEndpointMintRequestFactory<TMintRequest>, authorizationServers: readonly string[], requiredScopes: readonly string[], maxAccountValidationAgeSeconds?: number, maxEndpointJwtLifetimeSeconds?: number);
99
+ private nameValue?;
100
+ private versionValue?;
101
+ private resourceValue?;
102
+ private accessTokenAuthorityValue?;
103
+ private endpointJwtAuthorityValue?;
104
+ private endpointMintRequestValue?;
105
+ private authorizationServersValue?;
106
+ private requiredScopesValue?;
107
+ private errorTranslatorValue?;
108
+ private maxAccountValidationAgeSecondsValue;
109
+ private maxEndpointJwtLifetimeSecondsValue;
110
+ /** REQUIRED. The MCP server name reported by `initialize`. */
111
+ setName(name: string): this;
112
+ /** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */
113
+ setVersion(version: string): this;
114
+ /**
115
+ * REQUIRED. The ONE canonical protected-resource URI, which must be an absolute URL: it is the
116
+ * exact audience every access token is checked against, and {@link resourceMetadataUrl} is
117
+ * derived from it for the 401 challenge, so a non-URL here is always a mistake — including a
118
+ * name or version landed on it.
119
+ *
120
+ * It is fixed for the lifetime of the process and must NEVER be derived from the request `Host`
121
+ * header: the boundary compares a token's audience against this value, so a host-derived
122
+ * resource compares two values both taken from caller-controlled input — the confused-deputy
123
+ * shape this check exists to prevent. Its path must equal `McpBindOptions.endpointPath`, which
124
+ * `WpMcpServer.bind(...)` verifies. A second hostname is a second deployment, not a second
125
+ * audience.
126
+ */
127
+ setResource(resource: string): this;
128
+ /** REQUIRED. Application-owned paired mint/verify authority for MCP access tokens. */
129
+ setAccessTokenAuthority(authority: McpAccessTokenAuthority<TGrant>): this;
130
+ /** REQUIRED. The `JwtHook` minting the short-lived per-endpoint JWT for local bindings. */
131
+ setEndpointJwtAuthority(authority: JwtHook<TMintRequest>): this;
132
+ /** REQUIRED. Maps a verified MCP identity plus tool identity to that JwtHook's mint request. */
133
+ setEndpointMintRequest(factory: McpEndpointMintRequestFactory<TMintRequest>): this;
134
+ /**
135
+ * REQUIRED. The trusted token issuers. Every entry must be an absolute URL, because each is
136
+ * compared against a token's `issuer` claim and published as an `authorization_servers` entry —
137
+ * which is what makes a scope list landed here fail at boot rather than on the first request.
138
+ */
139
+ setAuthorizationServers(authorizationServers: readonly string[]): this;
140
+ /**
141
+ * REQUIRED, and empty only by saying so explicitly. Scopes every access token must carry before
142
+ * any tool is dispatched.
143
+ */
144
+ setRequiredScopes(requiredScopes: readonly string[]): this;
145
+ /**
146
+ * OPTIONAL. The application's `tools/call` error translators — first refusal on every tool
147
+ * failure, owning the ENTIRE result it claims. See {@link McpErrorTranslators}.
148
+ */
149
+ setErrorTranslator(translators: McpErrorTranslators): this;
150
+ /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */
151
+ setMaxAccountValidationAgeSeconds(seconds: number): this;
152
+ /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */
153
+ setMaxEndpointJwtLifetimeSeconds(seconds: number): this;
154
+ /**
155
+ * Called by `WpMcpServer.bind(...)`. Throws naming EVERY missing required setter at once, so a
156
+ * half-built config is repaired in one pass rather than one boot failure at a time.
157
+ */
158
+ validate(): void;
159
+ get name(): string;
160
+ get version(): string;
161
+ get resource(): string;
162
+ get accessTokenAuthority(): McpAccessTokenAuthority<TGrant>;
163
+ get endpointJwtAuthority(): JwtHook<TMintRequest>;
164
+ get endpointMintRequest(): McpEndpointMintRequestFactory<TMintRequest>;
165
+ get authorizationServers(): readonly string[];
166
+ get requiredScopes(): readonly string[];
167
+ /** `undefined` when the app registered none: webpieces then renders every tool failure. */
168
+ get errorTranslator(): McpErrorTranslators | undefined;
169
+ get maxAccountValidationAgeSeconds(): number;
170
+ get maxEndpointJwtLifetimeSeconds(): number;
171
+ /**
172
+ * RFC 9728 §3.1: the URL of this resource's protected-resource METADATA DOCUMENT, derived from
173
+ * {@link resource} by inserting `/.well-known/oauth-protected-resource` between origin and path
174
+ * (`https://host/mcp` → `https://host/.well-known/oauth-protected-resource/mcp`).
175
+ *
176
+ * This — never the resource identifier itself — is what RFC 9728 §5.1 defines `resource_metadata`
177
+ * to be, so it is the value webpieces puts in the 401 `WWW-Authenticate` challenge. webpieces
178
+ * mounts no `.well-known` route; publish {@link protectedResourceMetadata} at this URL.
179
+ */
180
+ get resourceMetadataUrl(): string;
82
181
  protectedResourceMetadata(): McpProtectedResourceMetadata;
182
+ private requireText;
183
+ private requireUrl;
184
+ private requireArray;
185
+ private requireSeconds;
186
+ private requirePresent;
187
+ private read;
83
188
  }
package/src/McpAuth.js CHANGED
@@ -90,46 +90,259 @@ class McpProtectedResourceMetadata {
90
90
  }
91
91
  exports.McpProtectedResourceMetadata = McpProtectedResourceMetadata;
92
92
  /**
93
- * Node MCP server configuration. Pass the same concrete application authority as both
94
- * accessTokenAuthority and endpointJwtAuthority when it implements both security seams.
93
+ * Node MCP server configuration, built with fluent setters: every name sits next to its own value, so
94
+ * no two settings can be swapped, and a future setting is an additive setter rather than a breaking
95
+ * signature change. Pass the same concrete application authority to both
96
+ * `setAccessTokenAuthority(...)` and `setEndpointJwtAuthority(...)` when it implements both security
97
+ * seams.
98
+ *
99
+ * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource. {@link setResource} is
100
+ * fixed for the lifetime of the process, is the audience of every access token, and is never derived
101
+ * from the request `Host` — see that setter for why a host-derived audience is a confused deputy.
102
+ *
103
+ * Each setter validates its own value immediately and names itself in the failure. Setters cannot
104
+ * make a field impossible to forget the way a positional constructor could, so
105
+ * {@link WpMcpServerConfig.validate} runs from `WpMcpServer.bind(...)` and fails at startup listing
106
+ * EVERY missing required setter at once. A misconfiguration is therefore a boot failure naming the
107
+ * setter, instead of a first-request 401 + `WWW-Authenticate` — which is the OAuth discovery signal,
108
+ * so the client's correct response is to re-authenticate and fail again, forever.
109
+ *
110
+ * ```ts
111
+ * const config = new WpMcpServerConfig<MyGrant, MyMintRequest>()
112
+ * .setName('my-server')
113
+ * .setVersion('1.0.0')
114
+ * .setResource('https://api.example.com/mcp')
115
+ * .setAccessTokenAuthority(authority)
116
+ * .setEndpointJwtAuthority(jwtHook)
117
+ * .setEndpointMintRequest((credential) => new MyMintRequest(credential.subject))
118
+ * .setAuthorizationServers(['https://login.example.com'])
119
+ * .setRequiredScopes(['tools'])
120
+ * .setErrorTranslator(new MyMcpErrorTranslators());
121
+ * ```
95
122
  */
96
123
  class WpMcpServerConfig {
97
- name;
98
- version;
99
- resource;
100
- accessTokenAuthority;
101
- endpointJwtAuthority;
102
- endpointMintRequest;
103
- authorizationServers;
104
- requiredScopes;
105
- maxAccountValidationAgeSeconds;
106
- maxEndpointJwtLifetimeSeconds;
107
- constructor(name, version, resource, accessTokenAuthority, endpointJwtAuthority, endpointMintRequest, authorizationServers, requiredScopes, maxAccountValidationAgeSeconds = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS, maxEndpointJwtLifetimeSeconds = exports.MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS) {
108
- this.name = name;
109
- this.version = version;
110
- this.resource = resource;
111
- this.accessTokenAuthority = accessTokenAuthority;
112
- this.endpointJwtAuthority = endpointJwtAuthority;
113
- this.endpointMintRequest = endpointMintRequest;
114
- this.authorizationServers = authorizationServers;
115
- this.requiredScopes = requiredScopes;
116
- this.maxAccountValidationAgeSeconds = maxAccountValidationAgeSeconds;
117
- this.maxEndpointJwtLifetimeSeconds = maxEndpointJwtLifetimeSeconds;
118
- if (name.trim() === '' || version.trim() === '' || resource.trim() === '') {
119
- throw new Error('MCP name, version, and resource must be non-empty.');
124
+ nameValue;
125
+ versionValue;
126
+ resourceValue;
127
+ accessTokenAuthorityValue;
128
+ endpointJwtAuthorityValue;
129
+ endpointMintRequestValue;
130
+ authorizationServersValue;
131
+ requiredScopesValue;
132
+ errorTranslatorValue;
133
+ maxAccountValidationAgeSecondsValue = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS;
134
+ maxEndpointJwtLifetimeSecondsValue = exports.MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS;
135
+ /** REQUIRED. The MCP server name reported by `initialize`. */
136
+ setName(name) {
137
+ this.nameValue = this.requireText(name, 'setName');
138
+ return this;
139
+ }
140
+ /** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */
141
+ setVersion(version) {
142
+ this.versionValue = this.requireText(version, 'setVersion');
143
+ return this;
144
+ }
145
+ /**
146
+ * REQUIRED. The ONE canonical protected-resource URI, which must be an absolute URL: it is the
147
+ * exact audience every access token is checked against, and {@link resourceMetadataUrl} is
148
+ * derived from it for the 401 challenge, so a non-URL here is always a mistake — including a
149
+ * name or version landed on it.
150
+ *
151
+ * It is fixed for the lifetime of the process and must NEVER be derived from the request `Host`
152
+ * header: the boundary compares a token's audience against this value, so a host-derived
153
+ * resource compares two values both taken from caller-controlled input — the confused-deputy
154
+ * shape this check exists to prevent. Its path must equal `McpBindOptions.endpointPath`, which
155
+ * `WpMcpServer.bind(...)` verifies. A second hostname is a second deployment, not a second
156
+ * audience.
157
+ */
158
+ setResource(resource) {
159
+ this.resourceValue = this.requireUrl(resource, 'setResource');
160
+ return this;
161
+ }
162
+ /** REQUIRED. Application-owned paired mint/verify authority for MCP access tokens. */
163
+ setAccessTokenAuthority(authority) {
164
+ this.accessTokenAuthorityValue = this.requirePresent(authority, 'setAccessTokenAuthority');
165
+ return this;
166
+ }
167
+ /** REQUIRED. The `JwtHook` minting the short-lived per-endpoint JWT for local bindings. */
168
+ setEndpointJwtAuthority(authority) {
169
+ this.endpointJwtAuthorityValue = this.requirePresent(authority, 'setEndpointJwtAuthority');
170
+ return this;
171
+ }
172
+ /** REQUIRED. Maps a verified MCP identity plus tool identity to that JwtHook's mint request. */
173
+ setEndpointMintRequest(factory) {
174
+ if (typeof factory !== 'function') {
175
+ throw new Error('WpMcpServerConfig.setEndpointMintRequest(...) requires a function.');
120
176
  }
121
- if (maxAccountValidationAgeSeconds <= 0 ||
122
- maxAccountValidationAgeSeconds > exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS) {
123
- throw new Error('MCP account validation cache ceiling must be between 1 second and 1 hour.');
177
+ this.endpointMintRequestValue = factory;
178
+ return this;
179
+ }
180
+ /**
181
+ * REQUIRED. The trusted token issuers. Every entry must be an absolute URL, because each is
182
+ * compared against a token's `issuer` claim and published as an `authorization_servers` entry —
183
+ * which is what makes a scope list landed here fail at boot rather than on the first request.
184
+ */
185
+ setAuthorizationServers(authorizationServers) {
186
+ const servers = this.requireArray(authorizationServers, 'setAuthorizationServers');
187
+ if (servers.length === 0) {
188
+ throw new Error('WpMcpServerConfig.setAuthorizationServers(...) requires at least one issuer URL.');
124
189
  }
125
- if (maxEndpointJwtLifetimeSeconds <= 0 ||
126
- maxEndpointJwtLifetimeSeconds > exports.MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS) {
127
- throw new Error('MCP endpoint JWT lifetime must be between 1 second and 1 hour.');
190
+ for (const server of servers)
191
+ this.requireUrl(server, 'setAuthorizationServers');
192
+ this.authorizationServersValue = servers;
193
+ return this;
194
+ }
195
+ /**
196
+ * REQUIRED, and empty only by saying so explicitly. Scopes every access token must carry before
197
+ * any tool is dispatched.
198
+ */
199
+ setRequiredScopes(requiredScopes) {
200
+ const scopes = this.requireArray(requiredScopes, 'setRequiredScopes');
201
+ for (const scope of scopes)
202
+ this.requireText(scope, 'setRequiredScopes');
203
+ this.requiredScopesValue = scopes;
204
+ return this;
205
+ }
206
+ /**
207
+ * OPTIONAL. The application's `tools/call` error translators — first refusal on every tool
208
+ * failure, owning the ENTIRE result it claims. See {@link McpErrorTranslators}.
209
+ */
210
+ setErrorTranslator(translators) {
211
+ const value = this.requirePresent(translators, 'setErrorTranslator');
212
+ if (typeof value.toToolResult !== 'function') {
213
+ throw new Error('WpMcpServerConfig.setErrorTranslator(...) requires a toToolResult(error, scope) method.');
128
214
  }
215
+ this.errorTranslatorValue = value;
216
+ return this;
217
+ }
218
+ /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */
219
+ setMaxAccountValidationAgeSeconds(seconds) {
220
+ this.maxAccountValidationAgeSecondsValue = this.requireSeconds(seconds, exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS, 'setMaxAccountValidationAgeSeconds');
221
+ return this;
222
+ }
223
+ /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */
224
+ setMaxEndpointJwtLifetimeSeconds(seconds) {
225
+ this.maxEndpointJwtLifetimeSecondsValue = this.requireSeconds(seconds, exports.MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS, 'setMaxEndpointJwtLifetimeSeconds');
226
+ return this;
227
+ }
228
+ /**
229
+ * Called by `WpMcpServer.bind(...)`. Throws naming EVERY missing required setter at once, so a
230
+ * half-built config is repaired in one pass rather than one boot failure at a time.
231
+ */
232
+ validate() {
233
+ const missing = [];
234
+ if (this.nameValue === undefined)
235
+ missing.push('setName(...)');
236
+ if (this.versionValue === undefined)
237
+ missing.push('setVersion(...)');
238
+ if (this.resourceValue === undefined)
239
+ missing.push('setResource(...)');
240
+ if (this.accessTokenAuthorityValue === undefined) {
241
+ missing.push('setAccessTokenAuthority(...)');
242
+ }
243
+ if (this.endpointJwtAuthorityValue === undefined) {
244
+ missing.push('setEndpointJwtAuthority(...)');
245
+ }
246
+ if (this.endpointMintRequestValue === undefined) {
247
+ missing.push('setEndpointMintRequest(...)');
248
+ }
249
+ if (this.authorizationServersValue === undefined) {
250
+ missing.push('setAuthorizationServers(...)');
251
+ }
252
+ if (this.requiredScopesValue === undefined)
253
+ missing.push('setRequiredScopes(...)');
254
+ if (missing.length > 0) {
255
+ throw new Error(`WpMcpServerConfig is missing ${missing.join(', ')}`);
256
+ }
257
+ }
258
+ get name() {
259
+ return this.read(this.nameValue, 'setName');
260
+ }
261
+ get version() {
262
+ return this.read(this.versionValue, 'setVersion');
263
+ }
264
+ get resource() {
265
+ return this.read(this.resourceValue, 'setResource');
266
+ }
267
+ get accessTokenAuthority() {
268
+ return this.read(this.accessTokenAuthorityValue, 'setAccessTokenAuthority');
269
+ }
270
+ get endpointJwtAuthority() {
271
+ return this.read(this.endpointJwtAuthorityValue, 'setEndpointJwtAuthority');
272
+ }
273
+ get endpointMintRequest() {
274
+ return this.read(this.endpointMintRequestValue, 'setEndpointMintRequest');
275
+ }
276
+ get authorizationServers() {
277
+ return this.read(this.authorizationServersValue, 'setAuthorizationServers');
278
+ }
279
+ get requiredScopes() {
280
+ return this.read(this.requiredScopesValue, 'setRequiredScopes');
281
+ }
282
+ /** `undefined` when the app registered none: webpieces then renders every tool failure. */
283
+ get errorTranslator() {
284
+ return this.errorTranslatorValue;
285
+ }
286
+ get maxAccountValidationAgeSeconds() {
287
+ return this.maxAccountValidationAgeSecondsValue;
288
+ }
289
+ get maxEndpointJwtLifetimeSeconds() {
290
+ return this.maxEndpointJwtLifetimeSecondsValue;
291
+ }
292
+ /**
293
+ * RFC 9728 §3.1: the URL of this resource's protected-resource METADATA DOCUMENT, derived from
294
+ * {@link resource} by inserting `/.well-known/oauth-protected-resource` between origin and path
295
+ * (`https://host/mcp` → `https://host/.well-known/oauth-protected-resource/mcp`).
296
+ *
297
+ * This — never the resource identifier itself — is what RFC 9728 §5.1 defines `resource_metadata`
298
+ * to be, so it is the value webpieces puts in the 401 `WWW-Authenticate` challenge. webpieces
299
+ * mounts no `.well-known` route; publish {@link protectedResourceMetadata} at this URL.
300
+ */
301
+ get resourceMetadataUrl() {
302
+ const url = new URL(this.resource);
303
+ const path = url.pathname === '/' ? '' : url.pathname;
304
+ return `${url.origin}/.well-known/oauth-protected-resource${path}`;
129
305
  }
130
306
  protectedResourceMetadata() {
131
307
  return new McpProtectedResourceMetadata(this.resource, this.authorizationServers, this.requiredScopes);
132
308
  }
309
+ requireText(value, setter) {
310
+ if (typeof value !== 'string' || value.trim() === '') {
311
+ throw new Error(`WpMcpServerConfig.${setter}(...) requires a non-empty string.`);
312
+ }
313
+ return value;
314
+ }
315
+ requireUrl(value, setter) {
316
+ this.requireText(value, setter);
317
+ if (!URL.canParse(value)) {
318
+ throw new Error(`WpMcpServerConfig.${setter}(...) requires an absolute URL, got '${value}'.`);
319
+ }
320
+ return value;
321
+ }
322
+ requireArray(value, setter) {
323
+ if (!Array.isArray(value)) {
324
+ throw new Error(`WpMcpServerConfig.${setter}(...) requires an array of strings.`);
325
+ }
326
+ return value;
327
+ }
328
+ requireSeconds(value, ceiling, setter) {
329
+ if (!Number.isFinite(value) || value <= 0 || value > ceiling) {
330
+ throw new Error(`WpMcpServerConfig.${setter}(...) requires 1..${ceiling} seconds, got ${String(value)}.`);
331
+ }
332
+ return value;
333
+ }
334
+ requirePresent(value, setter) {
335
+ if (value === undefined || value === null) {
336
+ throw new Error(`WpMcpServerConfig.${setter}(...) requires a value.`);
337
+ }
338
+ return value;
339
+ }
340
+ read(value, setter) {
341
+ if (value === undefined) {
342
+ throw new Error(`WpMcpServerConfig is missing ${setter}(...)`);
343
+ }
344
+ return value;
345
+ }
133
346
  }
134
347
  exports.WpMcpServerConfig = WpMcpServerConfig;
135
348
  //# sourceMappingURL=McpAuth.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"McpAuth.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuth.ts"],"names":[],"mappings":";;;AAGa,QAAA,qCAAqC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAC1D,QAAA,sCAAsC,GAAG,EAAE,GAAG,EAAE,CAAC;AACjD,QAAA,qCAAqC,GAAG,EAAE,GAAG,EAAE,CAAC;AAE7D,oGAAoG;AACpG,MAAa,oBAAoB;IAET;IACA;IACA;IACA;IAJpB,YACoB,KAAa,EACb,QAAgB,EAChB,oBAA4B,EAC5B,qBAA6B;QAH7B,UAAK,GAAL,KAAK,CAAQ;QACb,aAAQ,GAAR,QAAQ,CAAQ;QAChB,yBAAoB,GAApB,oBAAoB,CAAQ;QAC5B,0BAAqB,GAArB,qBAAqB,CAAQ;QAE7C,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC/E,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,oBAAoB,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,qBAAqB,CAAC,EAAE,CAAC;YACpF,MAAM,IAAI,KAAK,CACX,wEAAwE,CAC3E,CAAC;QACN,CAAC;QACD,IAAI,qBAAqB,IAAI,oBAAoB,EAAE,CAAC;YAChD,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,qBAAqB,GAAG,oBAAoB,GAAG,6CAAqC,EAAE,CAAC;YACvF,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QAC1E,CAAC;IACL,CAAC;CACJ;AAtBD,oDAsBC;AAED;;;;GAIG;AACH,MAAa,qBAAqB;IAEV;IACA;IACA;IACA;IACA;IACA;IAEA;IAEA;IAEA;IAZpB,YACoB,OAAe,EACf,MAAc,EACd,QAAgB,EAChB,oBAA4B,EAC5B,qBAA6B,EAC7B,MAAyB;IACzC,4FAA4F;IAC5E,8BAAsC;IACtD,oFAAoF;IACpE,eAAkC,EAAE;IACpD,6EAA6E;IAC7D,iBAA0C,EAAE;QAX5C,YAAO,GAAP,OAAO,CAAQ;QACf,WAAM,GAAN,MAAM,CAAQ;QACd,aAAQ,GAAR,QAAQ,CAAQ;QAChB,yBAAoB,GAApB,oBAAoB,CAAQ;QAC5B,0BAAqB,GAArB,qBAAqB,CAAQ;QAC7B,WAAM,GAAN,MAAM,CAAmB;QAEzB,mCAA8B,GAA9B,8BAA8B,CAAQ;QAEtC,iBAAY,GAAZ,YAAY,CAAwB;QAEpC,mBAAc,GAAd,cAAc,CAA8B;IAC7D,CAAC;CACP;AAfD,sDAeC;AAeD,6FAA6F;AAC7F,MAAa,qBAAqB;IAEV;IACA;IACA;IAHpB,YACoB,QAAgB,EAChB,YAAoB,EACpB,UAAkB;QAFlB,aAAQ,GAAR,QAAQ,CAAQ;QAChB,iBAAY,GAAZ,YAAY,CAAQ;QACpB,eAAU,GAAV,UAAU,CAAQ;IACnC,CAAC;CACP;AAND,sDAMC;AAQD,qEAAqE;AACrE,MAAa,4BAA4B;IAC5B,QAAQ,CAAS;IACjB,qBAAqB,CAAoB;IACzC,wBAAwB,GAAG,CAAC,QAAQ,CAAC,CAAC;IACtC,gBAAgB,CAAqB;IAE9C,YACI,QAAgB,EAChB,oBAAuC,EACvC,eAAmC;QAEnC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,qBAAqB,GAAG,oBAAoB,CAAC;QAClD,IAAI,CAAC,gBAAgB,GAAG,eAAe,CAAC;IAC5C,CAAC;CACJ;AAfD,oEAeC;AAED;;;GAGG;AACH,MAAa,iBAAiB;IAEN;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAVpB,YACoB,IAAY,EACZ,OAAe,EACf,QAAgB,EAChB,oBAAqD,EACrD,oBAA2C,EAC3C,mBAAgE,EAChE,oBAAuC,EACvC,cAAiC,EACjC,iCAAiC,8CAAsC,EACvE,gCAAgC,6CAAqC;QATrE,SAAI,GAAJ,IAAI,CAAQ;QACZ,YAAO,GAAP,OAAO,CAAQ;QACf,aAAQ,GAAR,QAAQ,CAAQ;QAChB,yBAAoB,GAApB,oBAAoB,CAAiC;QACrD,yBAAoB,GAApB,oBAAoB,CAAuB;QAC3C,wBAAmB,GAAnB,mBAAmB,CAA6C;QAChE,yBAAoB,GAApB,oBAAoB,CAAmB;QACvC,mBAAc,GAAd,cAAc,CAAmB;QACjC,mCAA8B,GAA9B,8BAA8B,CAAyC;QACvE,kCAA6B,GAA7B,6BAA6B,CAAwC;QAErF,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,OAAO,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,QAAQ,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACxE,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QAC1E,CAAC;QACD,IACI,8BAA8B,IAAI,CAAC;YACnC,8BAA8B,GAAG,8CAAsC,EACzE,CAAC;YACC,MAAM,IAAI,KAAK,CACX,2EAA2E,CAC9E,CAAC;QACN,CAAC;QACD,IACI,6BAA6B,IAAI,CAAC;YAClC,6BAA6B,GAAG,6CAAqC,EACvE,CAAC;YACC,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;QACtF,CAAC;IACL,CAAC;IAED,yBAAyB;QACrB,OAAO,IAAI,4BAA4B,CACnC,IAAI,CAAC,QAAQ,EACb,IAAI,CAAC,oBAAoB,EACzB,IAAI,CAAC,cAAc,CACtB,CAAC;IACN,CAAC;CACJ;AAvCD,8CAuCC","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. Pass the same concrete application authority as both\n * accessTokenAuthority and endpointJwtAuthority when it implements both security seams.\n */\nexport class WpMcpServerConfig<TGrant, TMintRequest> {\n constructor(\n public readonly name: string,\n public readonly version: string,\n public readonly resource: string,\n public readonly accessTokenAuthority: McpAccessTokenAuthority<TGrant>,\n public readonly endpointJwtAuthority: JwtHook<TMintRequest>,\n public readonly endpointMintRequest: McpEndpointMintRequestFactory<TMintRequest>,\n public readonly authorizationServers: readonly string[],\n public readonly requiredScopes: readonly string[],\n public readonly maxAccountValidationAgeSeconds = MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS,\n public readonly maxEndpointJwtLifetimeSeconds = MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS,\n ) {\n if (name.trim() === '' || version.trim() === '' || resource.trim() === '') {\n throw new Error('MCP name, version, and resource must be non-empty.');\n }\n if (\n maxAccountValidationAgeSeconds <= 0 ||\n maxAccountValidationAgeSeconds > MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS\n ) {\n throw new Error(\n 'MCP account validation cache ceiling must be between 1 second and 1 hour.',\n );\n }\n if (\n maxEndpointJwtLifetimeSeconds <= 0 ||\n maxEndpointJwtLifetimeSeconds > MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS\n ) {\n throw new Error('MCP endpoint JWT lifetime must be between 1 second and 1 hour.');\n }\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return new McpProtectedResourceMetadata(\n this.resource,\n this.authorizationServers,\n this.requiredScopes,\n );\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';\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 translators — first refusal on every tool\n * failure, owning the ENTIRE result it claims. See {@link McpErrorTranslators}.\n */\n setErrorTranslator(translators: McpErrorTranslators): this {\n const value = this.requirePresent(translators, 'setErrorTranslator');\n if (typeof value.toToolResult !== 'function') {\n throw new Error(\n 'WpMcpServerConfig.setErrorTranslator(...) requires a toToolResult(error, scope) 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"]}
@@ -16,6 +16,31 @@ export declare class McpFailureScope {
16
16
  constructor(requestId: string, jsonRpcId: string | number | null, method: string, toolName?: string | undefined);
17
17
  describe(): string;
18
18
  }
19
+ /**
20
+ * The application's own `tools/call` error translation — the MCP twin of the `ErrorTranslators` that
21
+ * `ExpressWrapper.handleError` consults through `ClientRegistry.tryTranslateToWire` on the HTTP path.
22
+ * Register one with `WpMcpServerConfig.setErrorTranslator(...)`; it is NOT a global, because
23
+ * `WpMcpServer` is constructed by app code and two servers may run in one process.
24
+ *
25
+ * An app owns its error taxonomy and owns how those errors should be explained to a model, so a
26
+ * claimed error yields the ENTIRE `CallToolResult` — content, `structuredContent`, `isError`, the lot.
27
+ *
28
+ * Contract:
29
+ * - `error` is the RAW thrown value, NOT normalized: an app must be able to `instanceof` its own
30
+ * error classes, which `ApiErrorBoundary.normalize` would have collapsed to
31
+ * `ApiImplementationError`.
32
+ * - return `undefined` for "not mine" — webpieces then renders its own default, unchanged.
33
+ * - webpieces default-fills `_meta['webpieces/requestId']` only when the returned result has NO
34
+ * `_meta`. An app that sets `_meta` owns it untouched.
35
+ * - operator-detail logging has ALREADY happened when this is called, so claiming an error can never
36
+ * silently kill observability.
37
+ * - scope is `tools/call` ONLY. The pre-SDK HTTP boundary and `tools/list` stay framework-owned:
38
+ * that boundary emits the `401 + WWW-Authenticate: Bearer resource_metadata=...` MCP clients
39
+ * depend on for OAuth discovery, and an app rewriting it breaks connector onboarding.
40
+ */
41
+ export interface McpErrorTranslators {
42
+ toToolResult(error: Error, scope: McpFailureScope): CallToolResult | undefined;
43
+ }
19
44
  /** The only error shape rendered into model-visible MCP tool content. */
20
45
  export declare class ModelVisibleToolError {
21
46
  readonly kind: string;
@@ -49,7 +74,10 @@ export declare class McpHttpErrorDetail {
49
74
  * error's own message on the wire unless it is an `ApiEndUserError`.
50
75
  */
51
76
  export declare class WpMcpErrorTranslator {
77
+ private readonly appTranslators?;
52
78
  private readonly boundary;
79
+ /** `appTranslators` is the app's `tools/call` seam; `undefined` means webpieces renders all. */
80
+ constructor(appTranslators?: McpErrorTranslators | undefined);
53
81
  /**
54
82
  * Pre-SDK failures: 401 + WWW-Authenticate, 403, 400 (-32700), anything else 500 (-32603).
55
83
  * A failure after response headers were sent can only end the stream.
@@ -61,10 +89,30 @@ export declare class WpMcpErrorTranslator {
61
89
  toProtocolError(thrown: Error, scope: McpFailureScope): ProtocolError;
62
90
  /** tools/call with a name no binding registered: a JSON-RPC protocol error per MCP 2026-07-28. */
63
91
  unknownTool(toolName: string, scope: McpFailureScope): ProtocolError;
64
- /** Every tools/call failure after the tool is found: an `isError: true` result the model sees. */
92
+ /**
93
+ * Every tools/call failure after the tool is found: an `isError: true` result the model sees.
94
+ *
95
+ * The app's {@link McpErrorTranslators} gets FIRST REFUSAL — the same convention
96
+ * `ExpressWrapper.handleError` applies on the HTTP path — and owns the entire result it claims.
97
+ * Operator-detail logging runs BEFORE it, so exactly one operator line is written per failure
98
+ * whoever renders the reply.
99
+ */
65
100
  toToolResult(thrown: Error, scope: McpFailureScope): CallToolResult;
66
101
  /** The `_meta` every tools/call result carries so a user can quote its requestId. */
67
102
  resultMeta(requestId: string): Record<string, string>;
103
+ /**
104
+ * The app's first refusal. A translator that THROWS must not replace the failure being reported:
105
+ * the throw is reported through the same boundary as its own implementation failure and the
106
+ * ORIGINAL error still renders through the webpieces default, so the model still gets a reply
107
+ * carrying the requestId.
108
+ */
109
+ private appToolResult;
110
+ /**
111
+ * webpieces fills `_meta` only when the app left it off, keeping the README's "every reply
112
+ * carries the requestId" promise a DEFAULT rather than a restriction: an app that set `_meta`
113
+ * keeps it byte-for-byte.
114
+ */
115
+ private withDefaultMeta;
68
116
  private modelVisible;
69
117
  private writeHttp;
70
118
  }
@@ -84,7 +84,12 @@ exports.McpHttpErrorDetail = McpHttpErrorDetail;
84
84
  * error's own message on the wire unless it is an `ApiEndUserError`.
85
85
  */
86
86
  class WpMcpErrorTranslator {
87
+ appTranslators;
87
88
  boundary = new core_util_1.ApiErrorBoundary(log);
89
+ /** `appTranslators` is the app's `tools/call` seam; `undefined` means webpieces renders all. */
90
+ constructor(appTranslators) {
91
+ this.appTranslators = appTranslators;
92
+ }
88
93
  /**
89
94
  * Pre-SDK failures: 401 + WWW-Authenticate, 403, 400 (-32700), anything else 500 (-32603).
90
95
  * A failure after response headers were sent can only end the stream.
@@ -132,10 +137,20 @@ class WpMcpErrorTranslator {
132
137
  log.info(`Unknown MCP tool: ${scope.describe()}`);
133
138
  return new server_1.ProtocolError(server_1.ProtocolErrorCode.InvalidParams, `Unknown tool: ${toolName}`, new McpErrorData(scope.requestId));
134
139
  }
135
- /** Every tools/call failure after the tool is found: an `isError: true` result the model sees. */
140
+ /**
141
+ * Every tools/call failure after the tool is found: an `isError: true` result the model sees.
142
+ *
143
+ * The app's {@link McpErrorTranslators} gets FIRST REFUSAL — the same convention
144
+ * `ExpressWrapper.handleError` applies on the HTTP path — and owns the entire result it claims.
145
+ * Operator-detail logging runs BEFORE it, so exactly one operator line is written per failure
146
+ * whoever renders the reply.
147
+ */
136
148
  toToolResult(thrown, scope) {
137
149
  const error = this.boundary.normalize(thrown);
138
150
  this.boundary.logOperatorDetail(error, scope.describe());
151
+ const claimed = this.appToolResult(thrown, scope);
152
+ if (claimed)
153
+ return this.withDefaultMeta(claimed, scope.requestId);
139
154
  const visible = this.modelVisible(core_util_1.ApiErrorCodec.encode(error), scope);
140
155
  const text = JSON.stringify(visible);
141
156
  return {
@@ -148,6 +163,36 @@ class WpMcpErrorTranslator {
148
163
  resultMeta(requestId) {
149
164
  return { [exports.MCP_REQUEST_ID_META_KEY]: requestId };
150
165
  }
166
+ /**
167
+ * The app's first refusal. A translator that THROWS must not replace the failure being reported:
168
+ * the throw is reported through the same boundary as its own implementation failure and the
169
+ * ORIGINAL error still renders through the webpieces default, so the model still gets a reply
170
+ * carrying the requestId.
171
+ */
172
+ appToolResult(thrown, scope) {
173
+ if (!this.appTranslators)
174
+ return undefined;
175
+ // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an app translator's own bug must not replace the failure it was asked to render
176
+ try {
177
+ return this.appTranslators.toToolResult(thrown, scope);
178
+ }
179
+ catch (err) {
180
+ const error = (0, core_util_1.toError)(err);
181
+ const failure = new core_util_1.ApiImplementationError('Application McpErrorTranslators.toToolResult threw; rendering the webpieces default.', error);
182
+ this.boundary.logOperatorDetail(failure, scope.describe());
183
+ return undefined;
184
+ }
185
+ }
186
+ /**
187
+ * webpieces fills `_meta` only when the app left it off, keeping the README's "every reply
188
+ * carries the requestId" promise a DEFAULT rather than a restriction: an app that set `_meta`
189
+ * keeps it byte-for-byte.
190
+ */
191
+ withDefaultMeta(result, requestId) {
192
+ if (result._meta !== undefined)
193
+ return result;
194
+ return { ...result, _meta: this.resultMeta(requestId) };
195
+ }
151
196
  modelVisible(payload, scope) {
152
197
  switch (payload.kind) {
153
198
  case 'end-user':
@@ -1 +1 @@
1
- {"version":3,"file":"WpMcpErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpErrorTranslator.ts"],"names":[],"mappings":"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","sourcesContent":["import { CallToolResult, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';\nimport { Response } from 'express';\nimport {\n ApiBadRequestError,\n ApiErrorBoundary,\n ApiErrorCodec,\n ApiErrorPayload,\n DtoValue,\n LogManager,\n} from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('WpMcpErrorTranslator');\n\n/** `_meta` key carrying the Webpieces requestId on every tools/call result, success or failure. */\nexport const MCP_REQUEST_ID_META_KEY = 'webpieces/requestId';\n\n/** JSON-RPC error `data` so a user can quote the requestId of any protocol-level failure. */\nexport class McpErrorData {\n constructor(public readonly requestId: string) {}\n}\n\n/** Correlation facts for one failure; rendered into the single operator log line. */\nexport class McpFailureScope {\n constructor(\n public readonly requestId: string,\n public readonly jsonRpcId: string | number | null,\n public readonly method: string,\n public readonly toolName?: string,\n ) {}\n\n describe(): string {\n const tool = this.toolName ? ` tool=${this.toolName}` : '';\n return `requestId=${this.requestId} jsonRpcId=${String(this.jsonRpcId)} method=${this.method}${tool}`;\n }\n}\n\n/** The only error shape rendered into model-visible MCP tool content. */\nexport class ModelVisibleToolError {\n constructor(\n public readonly kind: string,\n public readonly message: string,\n public readonly requestId: string,\n public readonly field?: string,\n public readonly callerMessage?: string,\n public readonly errorCode?: string,\n public readonly retryAfterSeconds?: number,\n public readonly statusCode?: number,\n ) {}\n}\n\n/** JSON-RPC error body written by the HTTP boundary before the SDK is involved. */\nexport class McpHttpErrorBody {\n readonly jsonrpc = '2.0';\n constructor(\n public readonly error: McpHttpErrorDetail,\n public readonly id: string | number | null,\n ) {}\n}\n\nexport class McpHttpErrorDetail {\n constructor(\n public readonly code: number,\n public readonly message: string,\n public readonly data: McpErrorData,\n ) {}\n}\n\n/**\n * The ONE place every MCP failure is mapped to a reply, mirroring `ApiErrorHttpMapper` for HTTP.\n * Each MCP entry point (the bind HTTP handler, tools/list, tools/call) has exactly one catch that\n * only delegates here. Normalization and per-kind log levels are the shared `ApiErrorBoundary`\n * rules, and published text comes only from `ApiErrorCodec.encode`: this class never puts an\n * error's own message on the wire unless it is an `ApiEndUserError`.\n */\nexport class WpMcpErrorTranslator {\n private readonly boundary = new ApiErrorBoundary(log);\n\n /**\n * Pre-SDK failures: 401 + WWW-Authenticate, 403, 400 (-32700), anything else 500 (-32603).\n * A failure after response headers were sent can only end the stream.\n */\n toHttp(thrown: Error, res: Response, scope: McpFailureScope, wwwAuthenticate: string): void {\n const error = this.boundary.normalize(thrown);\n this.boundary.logOperatorDetail(error, scope.describe());\n if (res.headersSent) {\n res.end();\n return;\n }\n const generic = ApiErrorCodec.encode(error).message;\n switch (error.kind) {\n case 'unauthorized':\n res.setHeader('WWW-Authenticate', wwwAuthenticate);\n this.writeHttp(res, 401, -32_000, generic, scope);\n return;\n case 'forbidden':\n this.writeHttp(res, 403, -32_000, generic, scope);\n return;\n case 'bad-request':\n this.writeHttp(res, 400, ProtocolErrorCode.ParseError, generic, scope);\n return;\n default:\n this.writeHttp(res, 500, ProtocolErrorCode.InternalError, 'Internal Error', scope);\n }\n }\n\n /** Express body-parser failures (malformed JSON, body too large) are caller input errors. */\n toHttpBodyFailure(cause: Error, res: Response, scope: McpFailureScope, wwwAuth: string): void {\n this.toHttp(\n new ApiBadRequestError(\n `MCP request body rejected: ${cause.message}`,\n undefined,\n undefined,\n cause,\n ),\n res,\n scope,\n wwwAuth,\n );\n }\n\n /** tools/list and other result-less methods: bad-request → -32602, anything else → -32603. */\n toProtocolError(thrown: Error, scope: McpFailureScope): ProtocolError {\n const error = this.boundary.normalize(thrown);\n this.boundary.logOperatorDetail(error, scope.describe());\n const payload = ApiErrorCodec.encode(error);\n if (error.kind === 'bad-request') {\n return new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n payload.callerMessage ?? payload.message,\n new McpErrorData(scope.requestId),\n );\n }\n const message = error.kind === 'end-user' ? payload.message : 'Internal Error';\n return new ProtocolError(\n ProtocolErrorCode.InternalError,\n message,\n new McpErrorData(scope.requestId),\n );\n }\n\n /** tools/call with a name no binding registered: a JSON-RPC protocol error per MCP 2026-07-28. */\n unknownTool(toolName: string, scope: McpFailureScope): ProtocolError {\n log.info(`Unknown MCP tool: ${scope.describe()}`);\n return new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n `Unknown tool: ${toolName}`,\n new McpErrorData(scope.requestId),\n );\n }\n\n /** Every tools/call failure after the tool is found: an `isError: true` result the model sees. */\n toToolResult(thrown: Error, scope: McpFailureScope): CallToolResult {\n const error = this.boundary.normalize(thrown);\n this.boundary.logOperatorDetail(error, scope.describe());\n const visible = this.modelVisible(ApiErrorCodec.encode(error), scope);\n const text = JSON.stringify(visible as DtoValue);\n return {\n content: [{ type: 'text', text }],\n isError: true,\n _meta: this.resultMeta(scope.requestId),\n };\n }\n\n /** The `_meta` every tools/call result carries so a user can quote its requestId. */\n resultMeta(requestId: string): Record<string, string> {\n return { [MCP_REQUEST_ID_META_KEY]: requestId };\n }\n\n private modelVisible(payload: ApiErrorPayload, scope: McpFailureScope): ModelVisibleToolError {\n switch (payload.kind) {\n case 'end-user':\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n scope.requestId,\n undefined,\n undefined,\n payload.errorCode,\n );\n case 'bad-request':\n return new ModelVisibleToolError(\n payload.kind,\n payload.callerMessage ?? payload.message,\n scope.requestId,\n payload.field,\n payload.callerMessage,\n );\n case 'coded':\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n scope.requestId,\n undefined,\n undefined,\n payload.errorCode,\n undefined,\n payload.statusCode,\n );\n case 'implementation':\n return new ModelVisibleToolError(\n payload.kind,\n `Internal error in tool ${scope.toolName ?? 'unknown'} (requestId ${scope.requestId}). ` +\n 'This is a bug in the tool, not in your arguments; retrying with different ' +\n 'arguments will not help.',\n scope.requestId,\n );\n default:\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n scope.requestId,\n undefined,\n undefined,\n undefined,\n payload.retryAfterSeconds,\n );\n }\n }\n\n private writeHttp(\n res: Response,\n status: number,\n code: number,\n message: string,\n scope: McpFailureScope,\n ): void {\n res.status(status).json(\n new McpHttpErrorBody(\n new McpHttpErrorDetail(code, message, new McpErrorData(scope.requestId)),\n scope.jsonRpcId,\n ),\n );\n }\n}\n"]}
1
+ {"version":3,"file":"WpMcpErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpErrorTranslator.ts"],"names":[],"mappings":"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","sourcesContent":["import { CallToolResult, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';\nimport { Response } from 'express';\nimport {\n ApiBadRequestError,\n ApiErrorBoundary,\n ApiErrorCodec,\n ApiErrorPayload,\n ApiImplementationError,\n DtoValue,\n LogManager,\n toError,\n} from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('WpMcpErrorTranslator');\n\n/** `_meta` key carrying the Webpieces requestId on every tools/call result, success or failure. */\nexport const MCP_REQUEST_ID_META_KEY = 'webpieces/requestId';\n\n/** JSON-RPC error `data` so a user can quote the requestId of any protocol-level failure. */\nexport class McpErrorData {\n constructor(public readonly requestId: string) {}\n}\n\n/** Correlation facts for one failure; rendered into the single operator log line. */\nexport class McpFailureScope {\n constructor(\n public readonly requestId: string,\n public readonly jsonRpcId: string | number | null,\n public readonly method: string,\n public readonly toolName?: string,\n ) {}\n\n describe(): string {\n const tool = this.toolName ? ` tool=${this.toolName}` : '';\n return `requestId=${this.requestId} jsonRpcId=${String(this.jsonRpcId)} method=${this.method}${tool}`;\n }\n}\n\n/**\n * The application's own `tools/call` error translation — the MCP twin of the `ErrorTranslators` that\n * `ExpressWrapper.handleError` consults through `ClientRegistry.tryTranslateToWire` on the HTTP path.\n * Register one with `WpMcpServerConfig.setErrorTranslator(...)`; it is NOT a global, because\n * `WpMcpServer` is constructed by app code and two servers may run in one process.\n *\n * An app owns its error taxonomy and owns how those errors should be explained to a model, so a\n * claimed error yields the ENTIRE `CallToolResult` — content, `structuredContent`, `isError`, the lot.\n *\n * Contract:\n * - `error` is the RAW thrown value, NOT normalized: an app must be able to `instanceof` its own\n * error classes, which `ApiErrorBoundary.normalize` would have collapsed to\n * `ApiImplementationError`.\n * - return `undefined` for \"not mine\" — webpieces then renders its own default, unchanged.\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 * - operator-detail logging has ALREADY happened when this is called, so claiming an error can never\n * silently kill observability.\n * - scope is `tools/call` ONLY. The pre-SDK HTTP boundary and `tools/list` stay framework-owned:\n * that boundary emits the `401 + WWW-Authenticate: Bearer resource_metadata=...` MCP clients\n * depend on for OAuth discovery, and an app rewriting it breaks connector onboarding.\n */\nexport interface McpErrorTranslators {\n toToolResult(error: Error, scope: McpFailureScope): CallToolResult | undefined;\n}\n\n/** The only error shape rendered into model-visible MCP tool content. */\nexport class ModelVisibleToolError {\n constructor(\n public readonly kind: string,\n public readonly message: string,\n public readonly requestId: string,\n public readonly field?: string,\n public readonly callerMessage?: string,\n public readonly errorCode?: string,\n public readonly retryAfterSeconds?: number,\n public readonly statusCode?: number,\n ) {}\n}\n\n/** JSON-RPC error body written by the HTTP boundary before the SDK is involved. */\nexport class McpHttpErrorBody {\n readonly jsonrpc = '2.0';\n constructor(\n public readonly error: McpHttpErrorDetail,\n public readonly id: string | number | null,\n ) {}\n}\n\nexport class McpHttpErrorDetail {\n constructor(\n public readonly code: number,\n public readonly message: string,\n public readonly data: McpErrorData,\n ) {}\n}\n\n/**\n * The ONE place every MCP failure is mapped to a reply, mirroring `ApiErrorHttpMapper` for HTTP.\n * Each MCP entry point (the bind HTTP handler, tools/list, tools/call) has exactly one catch that\n * only delegates here. Normalization and per-kind log levels are the shared `ApiErrorBoundary`\n * rules, and published text comes only from `ApiErrorCodec.encode`: this class never puts an\n * error's own message on the wire unless it is an `ApiEndUserError`.\n */\nexport class WpMcpErrorTranslator {\n private readonly boundary = new ApiErrorBoundary(log);\n\n /** `appTranslators` is the app's `tools/call` seam; `undefined` means webpieces renders all. */\n constructor(private readonly appTranslators?: McpErrorTranslators) {}\n\n /**\n * Pre-SDK failures: 401 + WWW-Authenticate, 403, 400 (-32700), anything else 500 (-32603).\n * A failure after response headers were sent can only end the stream.\n */\n toHttp(thrown: Error, res: Response, scope: McpFailureScope, wwwAuthenticate: string): void {\n const error = this.boundary.normalize(thrown);\n this.boundary.logOperatorDetail(error, scope.describe());\n if (res.headersSent) {\n res.end();\n return;\n }\n const generic = ApiErrorCodec.encode(error).message;\n switch (error.kind) {\n case 'unauthorized':\n res.setHeader('WWW-Authenticate', wwwAuthenticate);\n this.writeHttp(res, 401, -32_000, generic, scope);\n return;\n case 'forbidden':\n this.writeHttp(res, 403, -32_000, generic, scope);\n return;\n case 'bad-request':\n this.writeHttp(res, 400, ProtocolErrorCode.ParseError, generic, scope);\n return;\n default:\n this.writeHttp(res, 500, ProtocolErrorCode.InternalError, 'Internal Error', scope);\n }\n }\n\n /** Express body-parser failures (malformed JSON, body too large) are caller input errors. */\n toHttpBodyFailure(cause: Error, res: Response, scope: McpFailureScope, wwwAuth: string): void {\n this.toHttp(\n new ApiBadRequestError(\n `MCP request body rejected: ${cause.message}`,\n undefined,\n undefined,\n cause,\n ),\n res,\n scope,\n wwwAuth,\n );\n }\n\n /** tools/list and other result-less methods: bad-request → -32602, anything else → -32603. */\n toProtocolError(thrown: Error, scope: McpFailureScope): ProtocolError {\n const error = this.boundary.normalize(thrown);\n this.boundary.logOperatorDetail(error, scope.describe());\n const payload = ApiErrorCodec.encode(error);\n if (error.kind === 'bad-request') {\n return new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n payload.callerMessage ?? payload.message,\n new McpErrorData(scope.requestId),\n );\n }\n const message = error.kind === 'end-user' ? payload.message : 'Internal Error';\n return new ProtocolError(\n ProtocolErrorCode.InternalError,\n message,\n new McpErrorData(scope.requestId),\n );\n }\n\n /** tools/call with a name no binding registered: a JSON-RPC protocol error per MCP 2026-07-28. */\n unknownTool(toolName: string, scope: McpFailureScope): ProtocolError {\n log.info(`Unknown MCP tool: ${scope.describe()}`);\n return new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n `Unknown tool: ${toolName}`,\n new McpErrorData(scope.requestId),\n );\n }\n\n /**\n * Every tools/call failure after the tool is found: an `isError: true` result the model sees.\n *\n * The app's {@link McpErrorTranslators} gets FIRST REFUSAL — the same convention\n * `ExpressWrapper.handleError` applies on the HTTP path — and owns the entire result it claims.\n * Operator-detail logging runs BEFORE it, so exactly one operator line is written per failure\n * whoever renders the reply.\n */\n toToolResult(thrown: Error, scope: McpFailureScope): CallToolResult {\n const error = this.boundary.normalize(thrown);\n this.boundary.logOperatorDetail(error, scope.describe());\n const claimed = this.appToolResult(thrown, scope);\n if (claimed) return this.withDefaultMeta(claimed, scope.requestId);\n const visible = this.modelVisible(ApiErrorCodec.encode(error), scope);\n const text = JSON.stringify(visible as DtoValue);\n return {\n content: [{ type: 'text', text }],\n isError: true,\n _meta: this.resultMeta(scope.requestId),\n };\n }\n\n /** The `_meta` every tools/call result carries so a user can quote its requestId. */\n resultMeta(requestId: string): Record<string, string> {\n return { [MCP_REQUEST_ID_META_KEY]: requestId };\n }\n\n /**\n * The app's first refusal. A translator that THROWS must not replace the failure being reported:\n * the throw is reported through the same boundary as its own implementation failure and the\n * ORIGINAL error still renders through the webpieces default, so the model still gets a reply\n * carrying the requestId.\n */\n private appToolResult(thrown: Error, scope: McpFailureScope): CallToolResult | undefined {\n if (!this.appTranslators) return undefined;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- an app translator's own bug must not replace the failure it was asked to render\n try {\n return this.appTranslators.toToolResult(thrown, scope);\n } catch (err: unknown) {\n const error = toError(err);\n const failure = new ApiImplementationError(\n 'Application McpErrorTranslators.toToolResult threw; rendering the webpieces default.',\n error,\n );\n this.boundary.logOperatorDetail(failure, scope.describe());\n return undefined;\n }\n }\n\n /**\n * webpieces fills `_meta` only when the app left it off, keeping the README's \"every reply\n * carries the requestId\" promise a DEFAULT rather than a restriction: an app that set `_meta`\n * keeps it byte-for-byte.\n */\n private withDefaultMeta(result: CallToolResult, requestId: string): CallToolResult {\n if (result._meta !== undefined) return result;\n return { ...result, _meta: this.resultMeta(requestId) };\n }\n\n private modelVisible(payload: ApiErrorPayload, scope: McpFailureScope): ModelVisibleToolError {\n switch (payload.kind) {\n case 'end-user':\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n scope.requestId,\n undefined,\n undefined,\n payload.errorCode,\n );\n case 'bad-request':\n return new ModelVisibleToolError(\n payload.kind,\n payload.callerMessage ?? payload.message,\n scope.requestId,\n payload.field,\n payload.callerMessage,\n );\n case 'coded':\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n scope.requestId,\n undefined,\n undefined,\n payload.errorCode,\n undefined,\n payload.statusCode,\n );\n case 'implementation':\n return new ModelVisibleToolError(\n payload.kind,\n `Internal error in tool ${scope.toolName ?? 'unknown'} (requestId ${scope.requestId}). ` +\n 'This is a bug in the tool, not in your arguments; retrying with different ' +\n 'arguments will not help.',\n scope.requestId,\n );\n default:\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n scope.requestId,\n undefined,\n undefined,\n undefined,\n payload.retryAfterSeconds,\n );\n }\n }\n\n private writeHttp(\n res: Response,\n status: number,\n code: number,\n message: string,\n scope: McpFailureScope,\n ): void {\n res.status(status).json(\n new McpHttpErrorBody(\n new McpHttpErrorDetail(code, message, new McpErrorData(scope.requestId)),\n scope.jsonRpcId,\n ),\n );\n }\n}\n"]}
@@ -13,6 +13,7 @@ import { McpBindOptions } from './McpBindOptions';
13
13
  export declare class WpMcpServer<TGrant, TMintRequest> {
14
14
  private readonly config;
15
15
  private readonly dispatcher;
16
+ /** tools/call gives the app's translators first refusal; every other reply stays framework-owned. */
16
17
  private readonly translator;
17
18
  private registry?;
18
19
  private handler?;
@@ -21,6 +22,20 @@ export declare class WpMcpServer<TGrant, TMintRequest> {
21
22
  private streamingNodeHandler?;
22
23
  private revision?;
23
24
  constructor(config: WpMcpServerConfig<TGrant, TMintRequest>);
25
+ /**
26
+ * Mounts this MCP endpoint on `app`: POST at `options.endpointPath`, plus a 405 (`Allow: POST`)
27
+ * for every other method there.
28
+ *
29
+ * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource, so this may be
30
+ * called only once and `WpMcpServerConfig.setResource(...)`'s path must equal `endpointPath`.
31
+ * That resource URI is fixed for the lifetime of the process and is never derived from the
32
+ * request `Host` — a host-derived audience would make the boundary's own audience check compare
33
+ * two caller-controlled values, which is the confused deputy this check exists to prevent.
34
+ * Serving a second hostname is a second deployment, not a second audience.
35
+ *
36
+ * The OAuth/discovery half stays app-owned: publish `protectedResourceMetadata()` at
37
+ * `WpMcpServerConfig.resourceMetadataUrl`, which is where the 401 challenge sends clients.
38
+ */
24
39
  bind(app: Express, options: McpBindOptions): void;
25
40
  protectedResourceMetadata(): McpProtectedResourceMetadata;
26
41
  /** Gracefully closes active listen/request streams; clients must reconnect and refresh lists. */
@@ -13,6 +13,10 @@ const McpInvocationContext_1 = require("./McpInvocationContext");
13
13
  const McpToolRegistry_1 = require("./McpToolRegistry");
14
14
  const WpMcpErrorTranslator_1 = require("./WpMcpErrorTranslator");
15
15
  const log = core_util_1.LogManager.getLogger('WpMcpServer');
16
+ /** Body of the 405 answered for every non-POST method at the bound MCP endpoint path. */
17
+ class McpMethodNotAllowedBody {
18
+ error = 'method_not_allowed';
19
+ }
16
20
  /** Facts established once per POST at the bind boundary and handed to the SDK handlers. */
17
21
  class McpPostAuthentication {
18
22
  accessToken;
@@ -52,7 +56,8 @@ class WpSdkMcpServer extends server_1.McpServer {
52
56
  class WpMcpServer {
53
57
  config;
54
58
  dispatcher = new McpApiDispatcher_1.McpApiDispatcher();
55
- translator = new WpMcpErrorTranslator_1.WpMcpErrorTranslator();
59
+ /** tools/call gives the app's translators first refusal; every other reply stays framework-owned. */
60
+ translator;
56
61
  registry;
57
62
  handler;
58
63
  streamingHandler;
@@ -61,10 +66,36 @@ class WpMcpServer {
61
66
  revision;
62
67
  constructor(config) {
63
68
  this.config = config;
69
+ this.translator = new WpMcpErrorTranslator_1.WpMcpErrorTranslator(config.errorTranslator);
64
70
  }
71
+ /**
72
+ * Mounts this MCP endpoint on `app`: POST at `options.endpointPath`, plus a 405 (`Allow: POST`)
73
+ * for every other method there.
74
+ *
75
+ * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource, so this may be
76
+ * called only once and `WpMcpServerConfig.setResource(...)`'s path must equal `endpointPath`.
77
+ * That resource URI is fixed for the lifetime of the process and is never derived from the
78
+ * request `Host` — a host-derived audience would make the boundary's own audience check compare
79
+ * two caller-controlled values, which is the confused deputy this check exists to prevent.
80
+ * Serving a second hostname is a second deployment, not a second audience.
81
+ *
82
+ * The OAuth/discovery half stays app-owned: publish `protectedResourceMetadata()` at
83
+ * `WpMcpServerConfig.resourceMetadataUrl`, which is where the 401 challenge sends clients.
84
+ */
65
85
  bind(app, options) {
66
- if (this.handler)
67
- throw new Error('WpMcpServer.bind(...) may be called only once.');
86
+ if (this.handler) {
87
+ throw new Error('WpMcpServer.bind(...) may be called only once; an MCP resource has exactly one ' +
88
+ 'canonical URI, fixed for the lifetime of the process.');
89
+ }
90
+ // Fail at boot naming the forgotten setter, never on the first request with a 401 the client
91
+ // answers by re-authenticating (see WpMcpServerConfig).
92
+ this.config.validate();
93
+ const resourcePath = new URL(this.config.resource).pathname;
94
+ if (resourcePath !== options.endpointPath) {
95
+ throw new Error(`MCP resource path '${resourcePath}' must equal endpointPath ` +
96
+ `'${options.endpointPath}'; the canonical resource URI ` +
97
+ `'${this.config.resource}' and the bound route are the same endpoint.`);
98
+ }
68
99
  this.registry = new McpToolRegistry_1.McpToolRegistry(options.bindings);
69
100
  this.revision = this.calculateRegistryRevision(this.registry);
70
101
  this.handler = this.createHandler(options, 'auto');
@@ -81,6 +112,13 @@ class WpMcpServer {
81
112
  new core_context_1.RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));
82
113
  this.translator.toHttpBodyFailure(err, res, this.httpScope(req), this.challenge());
83
114
  }));
115
+ // Registered AFTER the POST route so POST still wins. MCP 2026-07-28 has no session GET
116
+ // (`subscriptions/listen` is a POST method), so every other method is a method error — and a
117
+ // discovery probe gets that instead of Express' bare 404.
118
+ app.all(options.endpointPath, (_req, res) => {
119
+ res.setHeader('Allow', 'POST');
120
+ res.status(405).json(new McpMethodNotAllowedBody());
121
+ });
84
122
  }
85
123
  protectedResourceMetadata() {
86
124
  return this.config.protectedResourceMetadata();
@@ -341,7 +379,7 @@ class WpMcpServer {
341
379
  return new WpMcpErrorTranslator_1.McpFailureScope(this.requestId(), typeof id === 'string' || typeof id === 'number' ? id : null, typeof method === 'string' ? method : 'unknown', typeof name === 'string' ? name : undefined);
342
380
  }
343
381
  challenge() {
344
- return `Bearer resource_metadata="${this.config.resource}"`;
382
+ return `Bearer resource_metadata="${this.config.resourceMetadataUrl}"`;
345
383
  }
346
384
  bodyObject(value) {
347
385
  if (typeof value !== 'object' || value === null || Array.isArray(value))
@@ -1 +1 @@
1
- {"version":3,"file":"WpMcpServer.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpServer.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport {\n AuthInfo,\n CallToolRequest,\n CallToolResult,\n createMcpHandler,\n ListToolsResult,\n McpHttpHandler,\n McpRequestContext,\n McpServer,\n ProgressToken,\n ServerContext,\n ServerOptions,\n Tool,\n Implementation,\n} from '@modelcontextprotocol/server';\nimport { NodeMcpRequestHandler, toNodeHandler } from '@modelcontextprotocol/node';\nimport { Express, json, NextFunction, Request, Response } from 'express';\nimport {\n ApiForbiddenError,\n ApiImplementationError,\n ApiUnauthorizedError,\n DtoValue,\n LogManager,\n toError,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { HttpRequest, RequestContext, RequestContextHeaders } from '@webpieces/core-context';\nimport { MintedJwt } from '@webpieces/http-routing';\nimport {\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS,\n McpEndpointDescriptor,\n McpProtectedResourceMetadata,\n VerifiedMcpCredential,\n WpMcpServerConfig,\n} from './McpAuth';\nimport { McpApiDispatcher } from './McpApiDispatcher';\nimport { McpBindOptions } from './McpBindOptions';\nimport { McpInvocationContext, McpProgressReporter } from './McpInvocationContext';\nimport { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';\nimport { McpFailureScope, WpMcpErrorTranslator } from './WpMcpErrorTranslator';\n\nconst log = LogManager.getLogger('WpMcpServer');\n\ntype AuthenticatedExpressRequest = Request & { auth?: AuthInfo };\n\n/** Facts established once per POST at the bind boundary and handed to the SDK handlers. */\nclass McpPostAuthentication {\n constructor(\n public readonly accessToken: string,\n public readonly credential: VerifiedMcpCredential,\n public readonly disconnectSignal: AbortSignal,\n ) {}\n}\n\n/**\n * The official SDK server with Webpieces-owned tools/list and tools/call handlers. It answers\n * `toolInputSchemaJson` from the Webpieces registry so the SDK's pre-dispatch SEP-2243\n * `Mcp-Param-*` header validation still runs without registering SDK-side tool callbacks.\n */\nclass WpSdkMcpServer extends McpServer {\n constructor(\n info: Implementation,\n options: ServerOptions,\n private readonly registry: McpToolRegistry,\n ) {\n super(info, options);\n }\n\n // webpieces-disable no-any-unknown -- signature is fixed by the SDK's JSON Schema record type\n override toolInputSchemaJson(name: string): Record<string, unknown> | undefined {\n return this.registry.find(name)?.inputSchema as Record<string, DtoValue> | undefined;\n }\n}\n\n/**\n * Modern-only MCP 2026-07-28 adapter around the official SDK's request-scoped HTTP handler.\n *\n * Error boundary: every failure is mapped by the one `WpMcpErrorTranslator`, and each entry point\n * has exactly one catch that only delegates to it: the bind HTTP handler (`toHttp`), tools/list\n * (`toProtocolError`) and tools/call (`toToolResult`). `subscriptions/listen` is served entirely by\n * the SDK's listen router, so Webpieces has no handler (and no catch) there. The external bearer is\n * verified exactly once per POST, at the HTTP boundary, before the SDK is involved.\n */\nexport class WpMcpServer<TGrant, TMintRequest> {\n private readonly dispatcher = new McpApiDispatcher();\n private readonly translator = new WpMcpErrorTranslator();\n private registry?: McpToolRegistry;\n private handler?: McpHttpHandler;\n private streamingHandler?: McpHttpHandler;\n private nodeHandler?: NodeMcpRequestHandler;\n private streamingNodeHandler?: NodeMcpRequestHandler;\n private revision?: string;\n\n constructor(private readonly config: WpMcpServerConfig<TGrant, TMintRequest>) {}\n\n bind(app: Express, options: McpBindOptions): void {\n if (this.handler) throw new Error('WpMcpServer.bind(...) may be called only once.');\n this.registry = new McpToolRegistry(options.bindings);\n this.revision = this.calculateRegistryRevision(this.registry);\n this.handler = this.createHandler(options, 'auto');\n this.streamingHandler = this.createHandler(options, 'sse');\n this.nodeHandler = toNodeHandler(this.handler, {\n onerror: (error: Error) => log.error('MCP Node transport failed', error),\n });\n this.streamingNodeHandler = toNodeHandler(this.streamingHandler, {\n onerror: (error: Error) => log.error('MCP streaming Node transport failed', error),\n });\n app.post(\n options.endpointPath,\n json(),\n async (req: Request, res: Response): Promise<void> =>\n this.handleHttp(req, res, options),\n // Express routes body-parser failures (bad JSON, body too large) to an error handler.\n (err: Error, req: Request, res: Response, _next: NextFunction): void =>\n RequestContext.run(() => {\n new RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));\n this.translator.toHttpBodyFailure(\n err,\n res,\n this.httpScope(req),\n this.challenge(),\n );\n }),\n );\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return this.config.protectedResourceMetadata();\n }\n\n /** Gracefully closes active listen/request streams; clients must reconnect and refresh lists. */\n async close(): Promise<void> {\n await Promise.all([this.handler?.close(), this.streamingHandler?.close()]);\n }\n\n /** Level-triggered invalidation; the authoritative list is always fetched again by clients. */\n toolsChanged(): void {\n this.handler?.notify.toolsChanged();\n }\n\n get registryRevision(): string | undefined {\n return this.revision;\n }\n\n private createHandler(options: McpBindOptions, responseMode: 'auto' | 'sse'): McpHttpHandler {\n return createMcpHandler(\n (context: McpRequestContext) => this.buildSdkServer(context, options),\n {\n legacy: 'reject',\n responseMode,\n bus: options.deployment.bus,\n maxSubscriptions: options.maxSubscriptions,\n keepAliveMs: options.keepAliveMs,\n onerror: (error: Error) => log.warn('MCP protocol request rejected', error),\n },\n );\n }\n\n /** Entry point #1: the one catch for everything before and around the SDK exchange. */\n private async handleHttp(req: Request, res: Response, options: McpBindOptions): Promise<void> {\n res.setHeader('X-Accel-Buffering', 'no');\n await RequestContext.run(async () => {\n new RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP HTTP entry point; delegates only to WpMcpErrorTranslator\n try {\n await this.serveExpress(req, res, options);\n } catch (err: unknown) {\n const error = toError(err);\n this.translator.toHttp(error, res, this.httpScope(req), this.challenge());\n }\n });\n }\n\n private async serveExpress(\n req: Request,\n res: Response,\n options: McpBindOptions,\n ): Promise<void> {\n const origin = req.header('origin');\n if (origin && !options.allowedOrigins.includes(origin)) {\n throw new ApiForbiddenError(`MCP request Origin '${origin}' is not allowed.`);\n }\n const authentication = await this.authenticate(req);\n const request = req as AuthenticatedExpressRequest;\n request.auth = this.authInfo(authentication);\n const serve = this.hasProgressToken(req.body)\n ? this.streamingNodeHandler\n : this.nodeHandler;\n if (!serve) throw new ApiImplementationError('MCP Node handlers are not initialized.');\n await serve(request, res, req.body);\n }\n\n /** Verifies the external bearer once for this POST. Handlers never re-verify it. */\n private async authenticate(req: Request): Promise<McpPostAuthentication> {\n const match = req.header('authorization')?.match(/^Bearer ([^\\s]+)$/i);\n const token = match?.[1];\n if (!token) throw new ApiUnauthorizedError('MCP request has no bearer access token.');\n const credential = await this.config.accessTokenAuthority.verifyAccessToken(\n token,\n this.config.resource,\n );\n this.validateCredential(credential);\n return new McpPostAuthentication(token, credential, this.disconnectSignal(req));\n }\n\n private disconnectSignal(req: Request): AbortSignal {\n const disconnected = new AbortController();\n const res = req.res;\n const cancel = (): void => disconnected.abort();\n req.once('aborted', cancel);\n req.socket.once('close', cancel);\n res?.once('close', cancel);\n res?.once('finish', (): void => {\n req.off('aborted', cancel);\n req.socket.off('close', cancel);\n res.off('close', cancel);\n });\n return disconnected.signal;\n }\n\n private buildSdkServer(context: McpRequestContext, options: McpBindOptions): McpServer {\n const authentication = context.authInfo?.extra?.['webpiecesAuthentication'];\n if (context.era !== 'modern' || !(authentication instanceof McpPostAuthentication)) {\n throw new ApiImplementationError(\n 'WpMcpServer serves only MCP 2026-07-28 requests authenticated by its bind boundary.',\n );\n }\n const registry = this.requireRegistry();\n const server = new WpSdkMcpServer(\n { name: this.config.name, version: `${this.config.version}+${this.revision}` },\n {\n capabilities: { tools: { listChanged: true } },\n cacheHints: {\n 'tools/list': { ttlMs: options.deployment.ttlMs, cacheScope: 'private' },\n 'server/discover': { ttlMs: options.deployment.ttlMs, cacheScope: 'private' },\n },\n },\n registry,\n );\n server.server.removeRequestHandler('tools/list');\n server.server.removeRequestHandler('tools/call');\n server.server.setRequestHandler(\n 'tools/list',\n async (_request: object, sdkContext: ServerContext): Promise<ListToolsResult> =>\n this.handleListTools(authentication, sdkContext),\n );\n server.server.setRequestHandler(\n 'tools/call',\n async (request: CallToolRequest, sdkContext: ServerContext): Promise<CallToolResult> =>\n this.handleCallTool(server, request, authentication, sdkContext),\n );\n return server;\n }\n\n /** Entry point #2: tools/list has no tool-result channel, so failures are JSON-RPC errors. */\n private async handleListTools(\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<ListToolsResult> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP tools/list entry point; delegates only to WpMcpErrorTranslator\n try {\n return this.listTools(authentication.credential);\n } catch (err: unknown) {\n const error = toError(err);\n throw this.translator.toProtocolError(error, this.sdkScope(sdkContext, 'tools/list'));\n }\n }\n\n private listTools(credential: VerifiedMcpCredential): ListToolsResult {\n const tools: Tool[] = [];\n for (const tool of this.requireRegistry().tools) {\n if (tool.isVisibleTo(credential.listingRoles)) tools.push(this.toolDefinition(tool));\n }\n return { tools };\n }\n\n private toolDefinition(tool: RegisteredMcpTool): Tool {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema as Tool['inputSchema'],\n outputSchema: tool.outputSchema as Tool['outputSchema'],\n annotations: tool.annotations,\n _meta: { webpiecesRegistryRevision: this.revision },\n };\n }\n\n /**\n * Entry point #3: an unknown tool is a JSON-RPC -32602; every failure after the tool is found\n * is an `isError: true` result, identically for local and remote bindings.\n */\n private async handleCallTool(\n server: WpSdkMcpServer,\n request: CallToolRequest,\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<CallToolResult> {\n const name = request.params.name;\n const scope = this.sdkScope(sdkContext, 'tools/call', name);\n const tool = this.requireRegistry().find(name);\n if (!tool) throw this.translator.unknownTool(name, scope);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP tools/call entry point; delegates only to WpMcpErrorTranslator\n try {\n const result = await this.callTool(tool, request, authentication, sdkContext);\n result._meta = this.translator.resultMeta(scope.requestId);\n return server.server.projectCallToolResult(\n result,\n tool.outputSchema as Record<string, DtoValue>,\n );\n } catch (err: unknown) {\n const error = toError(err);\n return this.translator.toToolResult(error, scope);\n }\n }\n\n /** Everything a tool call does. No catch in here: failures propagate to handleCallTool. */\n private async callTool(\n tool: RegisteredMcpTool,\n request: CallToolRequest,\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<CallToolResult> {\n const credential = authentication.credential;\n const endpointJwt =\n tool.binding.topology === 'local'\n ? await this.mintEndpointJwt(tool, authentication)\n : undefined;\n const invocation = new McpInvocationContext(\n sdkContext.mcpReq.id,\n tool.name,\n credential.subject,\n credential.listingRoles,\n AbortSignal.any([sdkContext.mcpReq.signal, authentication.disconnectSignal]),\n this.progressReporter(sdkContext),\n );\n const args = (request.params.arguments ?? {}) as DtoValue;\n const value = await this.dispatcher.call(\n tool,\n args,\n credential,\n invocation,\n endpointJwt?.token,\n );\n const outputFailure = this.requireRegistry().schemaBuilder.validate(\n tool.responseClass,\n value,\n );\n if (outputFailure) {\n throw new ApiImplementationError(\n `MCP output schema violation for ${tool.apiClass.name}.${tool.methodName}: ${outputFailure.message}`,\n );\n }\n const structured = this.record(value);\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n structuredContent: structured,\n };\n }\n\n private async mintEndpointJwt(\n tool: RegisteredMcpTool,\n authentication: McpPostAuthentication,\n ): Promise<MintedJwt> {\n const descriptor = new McpEndpointDescriptor(\n tool.name,\n tool.apiClass.name,\n tool.methodName,\n );\n const minted = await this.config.endpointJwtAuthority.mint(\n this.config.endpointMintRequest(authentication.credential, descriptor),\n );\n const now = Math.floor(Date.now() / 1000);\n if (minted.token === authentication.accessToken) {\n throw new ApiImplementationError(\n `MCP access-token passthrough is forbidden (endpoint JWT for ${tool.name}).`,\n );\n }\n if (\n minted.expiresAtEpochSeconds <= now ||\n minted.expiresAtEpochSeconds - now > this.config.maxEndpointJwtLifetimeSeconds\n ) {\n throw new ApiImplementationError(\n `MCP endpoint JWT lifetime is invalid for ${tool.name}.`,\n );\n }\n return minted;\n }\n\n private validateCredential(credential: VerifiedMcpCredential): void {\n const now = Math.floor(Date.now() / 1000);\n const reject = (reason: string): never => {\n throw new ApiUnauthorizedError(`MCP access token rejected: ${reason}`);\n };\n if (credential.subject.trim() === '') reject('no subject');\n const stamps = [\n credential.issuedAtEpochSeconds,\n credential.expiresAtEpochSeconds,\n credential.accountValidatedAtEpochSeconds,\n ];\n if (!stamps.every(Number.isFinite)) reject('security timestamps must be finite');\n if (credential.resource !== this.config.resource) reject('resource mismatch');\n if (!this.config.authorizationServers.includes(credential.issuer)) {\n reject('issuer is not trusted');\n }\n if (credential.issuedAtEpochSeconds > now || credential.expiresAtEpochSeconds <= now) {\n reject('outside its valid time window');\n }\n const lifetime = credential.expiresAtEpochSeconds - credential.issuedAtEpochSeconds;\n if (lifetime > MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) reject('lifetime exceeds 30 days');\n for (const scope of this.config.requiredScopes) {\n if (!credential.scopes.includes(scope)) reject(`missing required scope '${scope}'`);\n }\n const validatedAge = now - credential.accountValidatedAtEpochSeconds;\n if (\n credential.accountValidatedAtEpochSeconds > now ||\n validatedAge > this.config.maxAccountValidationAgeSeconds\n ) {\n reject('account authorization state is not fresh enough for dispatch');\n }\n }\n\n private authInfo(authentication: McpPostAuthentication): AuthInfo {\n const credential = authentication.credential;\n return {\n token: authentication.accessToken,\n clientId: credential.subject,\n scopes: [...credential.scopes],\n expiresAt: credential.expiresAtEpochSeconds,\n resource: new URL(credential.resource),\n extra: { webpiecesAuthentication: authentication },\n };\n }\n\n private progressReporter(context: ServerContext): McpProgressReporter | undefined {\n const token = context.mcpReq._meta?.progressToken;\n if (token === undefined) return undefined;\n return async (progress: number, total?: number, message?: string): Promise<void> => {\n await context.mcpReq.notify({\n method: 'notifications/progress',\n params: this.progressParams(token, progress, total, message),\n });\n };\n }\n\n private progressParams(\n progressToken: ProgressToken,\n progress: number,\n total?: number,\n message?: string,\n ): Record<string, DtoValue> {\n const params: Record<string, DtoValue> = { progressToken, progress };\n if (total !== undefined) params['total'] = total;\n if (message !== undefined) params['message'] = message;\n return params;\n }\n\n private requestId(): string {\n return RequestContext.getUntrusted(WebpiecesCoreHeaders.REQUEST_ID) ?? 'missing-request-id';\n }\n\n private sdkScope(context: ServerContext, method: string, toolName?: string): McpFailureScope {\n return new McpFailureScope(this.requestId(), context.mcpReq.id, method, toolName);\n }\n\n private httpScope(req: Request): McpFailureScope {\n const body = this.bodyObject(req.body);\n const id = body?.['id'];\n const method = body?.['method'];\n const params = this.bodyObject(body?.['params']);\n const name = params?.['name'];\n return new McpFailureScope(\n this.requestId(),\n typeof id === 'string' || typeof id === 'number' ? id : null,\n typeof method === 'string' ? method : 'unknown',\n typeof name === 'string' ? name : undefined,\n );\n }\n\n private challenge(): string {\n return `Bearer resource_metadata=\"${this.config.resource}\"`;\n }\n\n private bodyObject(value: DtoValue | undefined): Record<string, DtoValue> | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n return value as Record<string, DtoValue>;\n }\n\n private hasProgressToken(body: DtoValue): boolean {\n const params = this.bodyObject(this.bodyObject(body)?.['params']);\n return this.bodyObject(params?.['_meta'])?.['progressToken'] !== undefined;\n }\n\n private toHttpRequest(req: Request): HttpRequest {\n const headers = new Map<string, string[]>();\n for (const [name, value] of Object.entries(req.headers)) {\n if (typeof value === 'string') headers.set(name, [value]);\n else if (Array.isArray(value)) headers.set(name, value);\n }\n return new HttpRequest(req.method, req.originalUrl || req.path, headers);\n }\n\n private calculateRegistryRevision(registry: McpToolRegistry): string {\n const surface = registry.tools.map((tool: RegisteredMcpTool) => [\n tool.name,\n tool.description,\n tool.inputSchema,\n tool.outputSchema,\n tool.annotations,\n ]);\n return createHash('sha256').update(JSON.stringify(surface)).digest('hex').slice(0, 12);\n }\n\n private requireRegistry(): McpToolRegistry {\n if (!this.registry) throw new Error('Call WpMcpServer.bind(...) before serving requests.');\n return this.registry;\n }\n\n private record(value: DtoValue): Record<string, DtoValue> {\n const jsonValue = JSON.stringify(value);\n if (jsonValue === undefined) {\n throw new ApiImplementationError('MCP structured content is not JSON serializable.');\n }\n const parsed = JSON.parse(jsonValue) as DtoValue;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return { value: parsed };\n }\n return parsed as Record<string, DtoValue>;\n }\n}\n"]}
1
+ {"version":3,"file":"WpMcpServer.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpServer.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from 'node:crypto';\nimport {\n AuthInfo,\n CallToolRequest,\n CallToolResult,\n createMcpHandler,\n ListToolsResult,\n McpHttpHandler,\n McpRequestContext,\n McpServer,\n ProgressToken,\n ServerContext,\n ServerOptions,\n Tool,\n Implementation,\n} from '@modelcontextprotocol/server';\nimport { NodeMcpRequestHandler, toNodeHandler } from '@modelcontextprotocol/node';\nimport { Express, json, NextFunction, Request, Response } from 'express';\nimport {\n ApiForbiddenError,\n ApiImplementationError,\n ApiUnauthorizedError,\n DtoValue,\n LogManager,\n toError,\n WebpiecesCoreHeaders,\n} from '@webpieces/core-util';\nimport { HttpRequest, RequestContext, RequestContextHeaders } from '@webpieces/core-context';\nimport { MintedJwt } from '@webpieces/http-routing';\nimport {\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS,\n McpEndpointDescriptor,\n McpProtectedResourceMetadata,\n VerifiedMcpCredential,\n WpMcpServerConfig,\n} from './McpAuth';\nimport { McpApiDispatcher } from './McpApiDispatcher';\nimport { McpBindOptions } from './McpBindOptions';\nimport { McpInvocationContext, McpProgressReporter } from './McpInvocationContext';\nimport { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';\nimport { McpFailureScope, WpMcpErrorTranslator } from './WpMcpErrorTranslator';\n\nconst log = LogManager.getLogger('WpMcpServer');\n\ntype AuthenticatedExpressRequest = Request & { auth?: AuthInfo };\n\n/** Body of the 405 answered for every non-POST method at the bound MCP endpoint path. */\nclass McpMethodNotAllowedBody {\n readonly error = 'method_not_allowed';\n}\n\n/** Facts established once per POST at the bind boundary and handed to the SDK handlers. */\nclass McpPostAuthentication {\n constructor(\n public readonly accessToken: string,\n public readonly credential: VerifiedMcpCredential,\n public readonly disconnectSignal: AbortSignal,\n ) {}\n}\n\n/**\n * The official SDK server with Webpieces-owned tools/list and tools/call handlers. It answers\n * `toolInputSchemaJson` from the Webpieces registry so the SDK's pre-dispatch SEP-2243\n * `Mcp-Param-*` header validation still runs without registering SDK-side tool callbacks.\n */\nclass WpSdkMcpServer extends McpServer {\n constructor(\n info: Implementation,\n options: ServerOptions,\n private readonly registry: McpToolRegistry,\n ) {\n super(info, options);\n }\n\n // webpieces-disable no-any-unknown -- signature is fixed by the SDK's JSON Schema record type\n override toolInputSchemaJson(name: string): Record<string, unknown> | undefined {\n return this.registry.find(name)?.inputSchema as Record<string, DtoValue> | undefined;\n }\n}\n\n/**\n * Modern-only MCP 2026-07-28 adapter around the official SDK's request-scoped HTTP handler.\n *\n * Error boundary: every failure is mapped by the one `WpMcpErrorTranslator`, and each entry point\n * has exactly one catch that only delegates to it: the bind HTTP handler (`toHttp`), tools/list\n * (`toProtocolError`) and tools/call (`toToolResult`). `subscriptions/listen` is served entirely by\n * the SDK's listen router, so Webpieces has no handler (and no catch) there. The external bearer is\n * verified exactly once per POST, at the HTTP boundary, before the SDK is involved.\n */\nexport class WpMcpServer<TGrant, TMintRequest> {\n private readonly dispatcher = new McpApiDispatcher();\n /** tools/call gives the app's translators first refusal; every other reply stays framework-owned. */\n private readonly translator: WpMcpErrorTranslator;\n private registry?: McpToolRegistry;\n private handler?: McpHttpHandler;\n private streamingHandler?: McpHttpHandler;\n private nodeHandler?: NodeMcpRequestHandler;\n private streamingNodeHandler?: NodeMcpRequestHandler;\n private revision?: string;\n\n constructor(private readonly config: WpMcpServerConfig<TGrant, TMintRequest>) {\n this.translator = new WpMcpErrorTranslator(config.errorTranslator);\n }\n\n /**\n * Mounts this MCP endpoint on `app`: POST at `options.endpointPath`, plus a 405 (`Allow: POST`)\n * for every other method there.\n *\n * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource, so this may be\n * called only once and `WpMcpServerConfig.setResource(...)`'s path must equal `endpointPath`.\n * That resource URI is fixed for the lifetime of the process and is never derived from the\n * request `Host` — a host-derived audience would make the boundary's own audience check compare\n * two caller-controlled values, which is the confused deputy this check exists to prevent.\n * Serving a second hostname is a second deployment, not a second audience.\n *\n * The OAuth/discovery half stays app-owned: publish `protectedResourceMetadata()` at\n * `WpMcpServerConfig.resourceMetadataUrl`, which is where the 401 challenge sends clients.\n */\n bind(app: Express, options: McpBindOptions): void {\n if (this.handler) {\n throw new Error(\n 'WpMcpServer.bind(...) may be called only once; an MCP resource has exactly one ' +\n 'canonical URI, fixed for the lifetime of the process.',\n );\n }\n // Fail at boot naming the forgotten setter, never on the first request with a 401 the client\n // answers by re-authenticating (see WpMcpServerConfig).\n this.config.validate();\n const resourcePath = new URL(this.config.resource).pathname;\n if (resourcePath !== options.endpointPath) {\n throw new Error(\n `MCP resource path '${resourcePath}' must equal endpointPath ` +\n `'${options.endpointPath}'; the canonical resource URI ` +\n `'${this.config.resource}' and the bound route are the same endpoint.`,\n );\n }\n this.registry = new McpToolRegistry(options.bindings);\n this.revision = this.calculateRegistryRevision(this.registry);\n this.handler = this.createHandler(options, 'auto');\n this.streamingHandler = this.createHandler(options, 'sse');\n this.nodeHandler = toNodeHandler(this.handler, {\n onerror: (error: Error) => log.error('MCP Node transport failed', error),\n });\n this.streamingNodeHandler = toNodeHandler(this.streamingHandler, {\n onerror: (error: Error) => log.error('MCP streaming Node transport failed', error),\n });\n app.post(\n options.endpointPath,\n json(),\n async (req: Request, res: Response): Promise<void> =>\n this.handleHttp(req, res, options),\n // Express routes body-parser failures (bad JSON, body too large) to an error handler.\n (err: Error, req: Request, res: Response, _next: NextFunction): void =>\n RequestContext.run(() => {\n new RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));\n this.translator.toHttpBodyFailure(\n err,\n res,\n this.httpScope(req),\n this.challenge(),\n );\n }),\n );\n // Registered AFTER the POST route so POST still wins. MCP 2026-07-28 has no session GET\n // (`subscriptions/listen` is a POST method), so every other method is a method error — and a\n // discovery probe gets that instead of Express' bare 404.\n app.all(options.endpointPath, (_req: Request, res: Response): void => {\n res.setHeader('Allow', 'POST');\n res.status(405).json(new McpMethodNotAllowedBody());\n });\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return this.config.protectedResourceMetadata();\n }\n\n /** Gracefully closes active listen/request streams; clients must reconnect and refresh lists. */\n async close(): Promise<void> {\n await Promise.all([this.handler?.close(), this.streamingHandler?.close()]);\n }\n\n /** Level-triggered invalidation; the authoritative list is always fetched again by clients. */\n toolsChanged(): void {\n this.handler?.notify.toolsChanged();\n }\n\n get registryRevision(): string | undefined {\n return this.revision;\n }\n\n private createHandler(options: McpBindOptions, responseMode: 'auto' | 'sse'): McpHttpHandler {\n return createMcpHandler(\n (context: McpRequestContext) => this.buildSdkServer(context, options),\n {\n legacy: 'reject',\n responseMode,\n bus: options.deployment.bus,\n maxSubscriptions: options.maxSubscriptions,\n keepAliveMs: options.keepAliveMs,\n onerror: (error: Error) => log.warn('MCP protocol request rejected', error),\n },\n );\n }\n\n /** Entry point #1: the one catch for everything before and around the SDK exchange. */\n private async handleHttp(req: Request, res: Response, options: McpBindOptions): Promise<void> {\n res.setHeader('X-Accel-Buffering', 'no');\n await RequestContext.run(async () => {\n new RequestContextHeaders().fillFromRequest(this.toHttpRequest(req));\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP HTTP entry point; delegates only to WpMcpErrorTranslator\n try {\n await this.serveExpress(req, res, options);\n } catch (err: unknown) {\n const error = toError(err);\n this.translator.toHttp(error, res, this.httpScope(req), this.challenge());\n }\n });\n }\n\n private async serveExpress(\n req: Request,\n res: Response,\n options: McpBindOptions,\n ): Promise<void> {\n const origin = req.header('origin');\n if (origin && !options.allowedOrigins.includes(origin)) {\n throw new ApiForbiddenError(`MCP request Origin '${origin}' is not allowed.`);\n }\n const authentication = await this.authenticate(req);\n const request = req as AuthenticatedExpressRequest;\n request.auth = this.authInfo(authentication);\n const serve = this.hasProgressToken(req.body)\n ? this.streamingNodeHandler\n : this.nodeHandler;\n if (!serve) throw new ApiImplementationError('MCP Node handlers are not initialized.');\n await serve(request, res, req.body);\n }\n\n /** Verifies the external bearer once for this POST. Handlers never re-verify it. */\n private async authenticate(req: Request): Promise<McpPostAuthentication> {\n const match = req.header('authorization')?.match(/^Bearer ([^\\s]+)$/i);\n const token = match?.[1];\n if (!token) throw new ApiUnauthorizedError('MCP request has no bearer access token.');\n const credential = await this.config.accessTokenAuthority.verifyAccessToken(\n token,\n this.config.resource,\n );\n this.validateCredential(credential);\n return new McpPostAuthentication(token, credential, this.disconnectSignal(req));\n }\n\n private disconnectSignal(req: Request): AbortSignal {\n const disconnected = new AbortController();\n const res = req.res;\n const cancel = (): void => disconnected.abort();\n req.once('aborted', cancel);\n req.socket.once('close', cancel);\n res?.once('close', cancel);\n res?.once('finish', (): void => {\n req.off('aborted', cancel);\n req.socket.off('close', cancel);\n res.off('close', cancel);\n });\n return disconnected.signal;\n }\n\n private buildSdkServer(context: McpRequestContext, options: McpBindOptions): McpServer {\n const authentication = context.authInfo?.extra?.['webpiecesAuthentication'];\n if (context.era !== 'modern' || !(authentication instanceof McpPostAuthentication)) {\n throw new ApiImplementationError(\n 'WpMcpServer serves only MCP 2026-07-28 requests authenticated by its bind boundary.',\n );\n }\n const registry = this.requireRegistry();\n const server = new WpSdkMcpServer(\n { name: this.config.name, version: `${this.config.version}+${this.revision}` },\n {\n capabilities: { tools: { listChanged: true } },\n cacheHints: {\n 'tools/list': { ttlMs: options.deployment.ttlMs, cacheScope: 'private' },\n 'server/discover': { ttlMs: options.deployment.ttlMs, cacheScope: 'private' },\n },\n },\n registry,\n );\n server.server.removeRequestHandler('tools/list');\n server.server.removeRequestHandler('tools/call');\n server.server.setRequestHandler(\n 'tools/list',\n async (_request: object, sdkContext: ServerContext): Promise<ListToolsResult> =>\n this.handleListTools(authentication, sdkContext),\n );\n server.server.setRequestHandler(\n 'tools/call',\n async (request: CallToolRequest, sdkContext: ServerContext): Promise<CallToolResult> =>\n this.handleCallTool(server, request, authentication, sdkContext),\n );\n return server;\n }\n\n /** Entry point #2: tools/list has no tool-result channel, so failures are JSON-RPC errors. */\n private async handleListTools(\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<ListToolsResult> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP tools/list entry point; delegates only to WpMcpErrorTranslator\n try {\n return this.listTools(authentication.credential);\n } catch (err: unknown) {\n const error = toError(err);\n throw this.translator.toProtocolError(error, this.sdkScope(sdkContext, 'tools/list'));\n }\n }\n\n private listTools(credential: VerifiedMcpCredential): ListToolsResult {\n const tools: Tool[] = [];\n for (const tool of this.requireRegistry().tools) {\n if (tool.isVisibleTo(credential.listingRoles)) tools.push(this.toolDefinition(tool));\n }\n return { tools };\n }\n\n private toolDefinition(tool: RegisteredMcpTool): Tool {\n return {\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema as Tool['inputSchema'],\n outputSchema: tool.outputSchema as Tool['outputSchema'],\n annotations: tool.annotations,\n _meta: { webpiecesRegistryRevision: this.revision },\n };\n }\n\n /**\n * Entry point #3: an unknown tool is a JSON-RPC -32602; every failure after the tool is found\n * is an `isError: true` result, identically for local and remote bindings.\n */\n private async handleCallTool(\n server: WpSdkMcpServer,\n request: CallToolRequest,\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<CallToolResult> {\n const name = request.params.name;\n const scope = this.sdkScope(sdkContext, 'tools/call', name);\n const tool = this.requireRegistry().find(name);\n if (!tool) throw this.translator.unknownTool(name, scope);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP tools/call entry point; delegates only to WpMcpErrorTranslator\n try {\n const result = await this.callTool(tool, request, authentication, sdkContext);\n result._meta = this.translator.resultMeta(scope.requestId);\n return server.server.projectCallToolResult(\n result,\n tool.outputSchema as Record<string, DtoValue>,\n );\n } catch (err: unknown) {\n const error = toError(err);\n return this.translator.toToolResult(error, scope);\n }\n }\n\n /** Everything a tool call does. No catch in here: failures propagate to handleCallTool. */\n private async callTool(\n tool: RegisteredMcpTool,\n request: CallToolRequest,\n authentication: McpPostAuthentication,\n sdkContext: ServerContext,\n ): Promise<CallToolResult> {\n const credential = authentication.credential;\n const endpointJwt =\n tool.binding.topology === 'local'\n ? await this.mintEndpointJwt(tool, authentication)\n : undefined;\n const invocation = new McpInvocationContext(\n sdkContext.mcpReq.id,\n tool.name,\n credential.subject,\n credential.listingRoles,\n AbortSignal.any([sdkContext.mcpReq.signal, authentication.disconnectSignal]),\n this.progressReporter(sdkContext),\n );\n const args = (request.params.arguments ?? {}) as DtoValue;\n const value = await this.dispatcher.call(\n tool,\n args,\n credential,\n invocation,\n endpointJwt?.token,\n );\n const outputFailure = this.requireRegistry().schemaBuilder.validate(\n tool.responseClass,\n value,\n );\n if (outputFailure) {\n throw new ApiImplementationError(\n `MCP output schema violation for ${tool.apiClass.name}.${tool.methodName}: ${outputFailure.message}`,\n );\n }\n const structured = this.record(value);\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n structuredContent: structured,\n };\n }\n\n private async mintEndpointJwt(\n tool: RegisteredMcpTool,\n authentication: McpPostAuthentication,\n ): Promise<MintedJwt> {\n const descriptor = new McpEndpointDescriptor(\n tool.name,\n tool.apiClass.name,\n tool.methodName,\n );\n const minted = await this.config.endpointJwtAuthority.mint(\n this.config.endpointMintRequest(authentication.credential, descriptor),\n );\n const now = Math.floor(Date.now() / 1000);\n if (minted.token === authentication.accessToken) {\n throw new ApiImplementationError(\n `MCP access-token passthrough is forbidden (endpoint JWT for ${tool.name}).`,\n );\n }\n if (\n minted.expiresAtEpochSeconds <= now ||\n minted.expiresAtEpochSeconds - now > this.config.maxEndpointJwtLifetimeSeconds\n ) {\n throw new ApiImplementationError(\n `MCP endpoint JWT lifetime is invalid for ${tool.name}.`,\n );\n }\n return minted;\n }\n\n private validateCredential(credential: VerifiedMcpCredential): void {\n const now = Math.floor(Date.now() / 1000);\n const reject = (reason: string): never => {\n throw new ApiUnauthorizedError(`MCP access token rejected: ${reason}`);\n };\n if (credential.subject.trim() === '') reject('no subject');\n const stamps = [\n credential.issuedAtEpochSeconds,\n credential.expiresAtEpochSeconds,\n credential.accountValidatedAtEpochSeconds,\n ];\n if (!stamps.every(Number.isFinite)) reject('security timestamps must be finite');\n if (credential.resource !== this.config.resource) reject('resource mismatch');\n if (!this.config.authorizationServers.includes(credential.issuer)) {\n reject('issuer is not trusted');\n }\n if (credential.issuedAtEpochSeconds > now || credential.expiresAtEpochSeconds <= now) {\n reject('outside its valid time window');\n }\n const lifetime = credential.expiresAtEpochSeconds - credential.issuedAtEpochSeconds;\n if (lifetime > MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) reject('lifetime exceeds 30 days');\n for (const scope of this.config.requiredScopes) {\n if (!credential.scopes.includes(scope)) reject(`missing required scope '${scope}'`);\n }\n const validatedAge = now - credential.accountValidatedAtEpochSeconds;\n if (\n credential.accountValidatedAtEpochSeconds > now ||\n validatedAge > this.config.maxAccountValidationAgeSeconds\n ) {\n reject('account authorization state is not fresh enough for dispatch');\n }\n }\n\n private authInfo(authentication: McpPostAuthentication): AuthInfo {\n const credential = authentication.credential;\n return {\n token: authentication.accessToken,\n clientId: credential.subject,\n scopes: [...credential.scopes],\n expiresAt: credential.expiresAtEpochSeconds,\n resource: new URL(credential.resource),\n extra: { webpiecesAuthentication: authentication },\n };\n }\n\n private progressReporter(context: ServerContext): McpProgressReporter | undefined {\n const token = context.mcpReq._meta?.progressToken;\n if (token === undefined) return undefined;\n return async (progress: number, total?: number, message?: string): Promise<void> => {\n await context.mcpReq.notify({\n method: 'notifications/progress',\n params: this.progressParams(token, progress, total, message),\n });\n };\n }\n\n private progressParams(\n progressToken: ProgressToken,\n progress: number,\n total?: number,\n message?: string,\n ): Record<string, DtoValue> {\n const params: Record<string, DtoValue> = { progressToken, progress };\n if (total !== undefined) params['total'] = total;\n if (message !== undefined) params['message'] = message;\n return params;\n }\n\n private requestId(): string {\n return RequestContext.getUntrusted(WebpiecesCoreHeaders.REQUEST_ID) ?? 'missing-request-id';\n }\n\n private sdkScope(context: ServerContext, method: string, toolName?: string): McpFailureScope {\n return new McpFailureScope(this.requestId(), context.mcpReq.id, method, toolName);\n }\n\n private httpScope(req: Request): McpFailureScope {\n const body = this.bodyObject(req.body);\n const id = body?.['id'];\n const method = body?.['method'];\n const params = this.bodyObject(body?.['params']);\n const name = params?.['name'];\n return new McpFailureScope(\n this.requestId(),\n typeof id === 'string' || typeof id === 'number' ? id : null,\n typeof method === 'string' ? method : 'unknown',\n typeof name === 'string' ? name : undefined,\n );\n }\n\n private challenge(): string {\n return `Bearer resource_metadata=\"${this.config.resourceMetadataUrl}\"`;\n }\n\n private bodyObject(value: DtoValue | undefined): Record<string, DtoValue> | undefined {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return undefined;\n return value as Record<string, DtoValue>;\n }\n\n private hasProgressToken(body: DtoValue): boolean {\n const params = this.bodyObject(this.bodyObject(body)?.['params']);\n return this.bodyObject(params?.['_meta'])?.['progressToken'] !== undefined;\n }\n\n private toHttpRequest(req: Request): HttpRequest {\n const headers = new Map<string, string[]>();\n for (const [name, value] of Object.entries(req.headers)) {\n if (typeof value === 'string') headers.set(name, [value]);\n else if (Array.isArray(value)) headers.set(name, value);\n }\n return new HttpRequest(req.method, req.originalUrl || req.path, headers);\n }\n\n private calculateRegistryRevision(registry: McpToolRegistry): string {\n const surface = registry.tools.map((tool: RegisteredMcpTool) => [\n tool.name,\n tool.description,\n tool.inputSchema,\n tool.outputSchema,\n tool.annotations,\n ]);\n return createHash('sha256').update(JSON.stringify(surface)).digest('hex').slice(0, 12);\n }\n\n private requireRegistry(): McpToolRegistry {\n if (!this.registry) throw new Error('Call WpMcpServer.bind(...) before serving requests.');\n return this.registry;\n }\n\n private record(value: DtoValue): Record<string, DtoValue> {\n const jsonValue = JSON.stringify(value);\n if (jsonValue === undefined) {\n throw new ApiImplementationError('MCP structured content is not JSON serializable.');\n }\n const parsed = JSON.parse(jsonValue) as DtoValue;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n return { value: parsed };\n }\n return parsed as Record<string, DtoValue>;\n }\n}\n"]}
package/src/index.d.ts CHANGED
@@ -11,3 +11,4 @@ export type { McpDeploymentMode } from './McpDeployment';
11
11
  export { McpApiDispatcher } from './McpApiDispatcher';
12
12
  export { WpMcpServer } from './WpMcpServer';
13
13
  export { MCP_REQUEST_ID_META_KEY, McpErrorData, McpFailureScope, McpHttpErrorBody, McpHttpErrorDetail, ModelVisibleToolError, WpMcpErrorTranslator, } from './WpMcpErrorTranslator';
14
+ export type { McpErrorTranslators } from './WpMcpErrorTranslator';
package/src/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/index.ts"],"names":[],"mappings":";;;AAAA,qCASmB;AARf,gIAAA,qCAAqC,OAAA;AACrC,iIAAA,sCAAsC,OAAA;AACtC,gIAAA,qCAAqC,OAAA;AACrC,gHAAA,qBAAqB,OAAA;AACrB,+GAAA,oBAAoB,OAAA;AACpB,uHAAA,4BAA4B,OAAA;AAC5B,gHAAA,qBAAqB,OAAA;AACrB,4GAAA,iBAAiB,OAAA;AAGrB,qDAAuE;AAA9D,kHAAA,eAAe,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAC3C,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AAEtB,+DAAsF;AAA7E,8HAAA,sBAAsB,OAAA;AAAE,4HAAA,oBAAoB,OAAA;AAErD,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AACvB,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AAEtB,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AACpB,+DAQgC;AAP5B,+HAAA,uBAAuB,OAAA;AACvB,oHAAA,YAAY,OAAA;AACZ,uHAAA,eAAe,OAAA;AACf,wHAAA,gBAAgB,OAAA;AAChB,0HAAA,kBAAkB,OAAA;AAClB,6HAAA,qBAAqB,OAAA;AACrB,4HAAA,oBAAoB,OAAA","sourcesContent":["export {\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS,\n MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS,\n MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS,\n McpEndpointDescriptor,\n MintedMcpAccessToken,\n McpProtectedResourceMetadata,\n VerifiedMcpCredential,\n WpMcpServerConfig,\n} from './McpAuth';\nexport type { McpAccessTokenAuthority, McpEndpointMintRequestFactory } from './McpAuth';\nexport { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';\nexport { McpApiBinding } from './McpApiBinding';\nexport type { McpBindingTopology } from './McpApiBinding';\nexport { MCP_INVOCATION_CONTEXT, McpInvocationContext } from './McpInvocationContext';\nexport type { McpProgressReporter } from './McpInvocationContext';\nexport { McpBindOptions } from './McpBindOptions';\nexport { McpDeployment } from './McpDeployment';\nexport type { McpDeploymentMode } from './McpDeployment';\nexport { McpApiDispatcher } from './McpApiDispatcher';\nexport { WpMcpServer } from './WpMcpServer';\nexport {\n MCP_REQUEST_ID_META_KEY,\n McpErrorData,\n McpFailureScope,\n McpHttpErrorBody,\n McpHttpErrorDetail,\n ModelVisibleToolError,\n WpMcpErrorTranslator,\n} from './WpMcpErrorTranslator';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/index.ts"],"names":[],"mappings":";;;AAAA,qCASmB;AARf,gIAAA,qCAAqC,OAAA;AACrC,iIAAA,sCAAsC,OAAA;AACtC,gIAAA,qCAAqC,OAAA;AACrC,gHAAA,qBAAqB,OAAA;AACrB,+GAAA,oBAAoB,OAAA;AACpB,uHAAA,4BAA4B,OAAA;AAC5B,gHAAA,qBAAqB,OAAA;AACrB,4GAAA,iBAAiB,OAAA;AAGrB,qDAAuE;AAA9D,kHAAA,eAAe,OAAA;AAAE,oHAAA,iBAAiB,OAAA;AAC3C,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AAEtB,+DAAsF;AAA7E,8HAAA,sBAAsB,OAAA;AAAE,4HAAA,oBAAoB,OAAA;AAErD,mDAAkD;AAAzC,gHAAA,cAAc,OAAA;AACvB,iDAAgD;AAAvC,8GAAA,aAAa,OAAA;AAEtB,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,6CAA4C;AAAnC,0GAAA,WAAW,OAAA;AACpB,+DAQgC;AAP5B,+HAAA,uBAAuB,OAAA;AACvB,oHAAA,YAAY,OAAA;AACZ,uHAAA,eAAe,OAAA;AACf,wHAAA,gBAAgB,OAAA;AAChB,0HAAA,kBAAkB,OAAA;AAClB,6HAAA,qBAAqB,OAAA;AACrB,4HAAA,oBAAoB,OAAA","sourcesContent":["export {\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS,\n MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS,\n MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS,\n McpEndpointDescriptor,\n MintedMcpAccessToken,\n McpProtectedResourceMetadata,\n VerifiedMcpCredential,\n WpMcpServerConfig,\n} from './McpAuth';\nexport type { McpAccessTokenAuthority, McpEndpointMintRequestFactory } from './McpAuth';\nexport { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';\nexport { McpApiBinding } from './McpApiBinding';\nexport type { McpBindingTopology } from './McpApiBinding';\nexport { MCP_INVOCATION_CONTEXT, McpInvocationContext } from './McpInvocationContext';\nexport type { McpProgressReporter } from './McpInvocationContext';\nexport { McpBindOptions } from './McpBindOptions';\nexport { McpDeployment } from './McpDeployment';\nexport type { McpDeploymentMode } from './McpDeployment';\nexport { McpApiDispatcher } from './McpApiDispatcher';\nexport { WpMcpServer } from './WpMcpServer';\nexport {\n MCP_REQUEST_ID_META_KEY,\n McpErrorData,\n McpFailureScope,\n McpHttpErrorBody,\n McpHttpErrorDetail,\n ModelVisibleToolError,\n WpMcpErrorTranslator,\n} from './WpMcpErrorTranslator';\nexport type { McpErrorTranslators } from './WpMcpErrorTranslator';\n"]}