@webpieces/mcp-server 0.4.990 → 0.4.991

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +199 -42
  2. package/package.json +7 -6
  3. package/src/McpApiDispatcher.d.ts +6 -3
  4. package/src/McpApiDispatcher.js +6 -2
  5. package/src/McpApiDispatcher.js.map +1 -1
  6. package/src/McpAuth.d.ts +115 -56
  7. package/src/McpAuth.js +180 -111
  8. package/src/McpAuth.js.map +1 -1
  9. package/src/McpAuthorityCompileAssertions.d.ts +12 -2
  10. package/src/McpAuthorityCompileAssertions.js +32 -4
  11. package/src/McpAuthorityCompileAssertions.js.map +1 -1
  12. package/src/McpBindOptions.d.ts +5 -20
  13. package/src/McpBindOptions.js +5 -15
  14. package/src/McpBindOptions.js.map +1 -1
  15. package/src/McpClientSet.d.ts +35 -0
  16. package/src/McpClientSet.js +86 -0
  17. package/src/McpClientSet.js.map +1 -0
  18. package/src/McpDoorAuth.d.ts +117 -0
  19. package/src/McpDoorAuth.js +455 -0
  20. package/src/McpDoorAuth.js.map +1 -0
  21. package/src/McpToolCallRendering.d.ts +5 -4
  22. package/src/McpToolCallRendering.js.map +1 -1
  23. package/src/McpToolCatalog.d.ts +1 -1
  24. package/src/McpToolCatalog.js +1 -1
  25. package/src/McpToolCatalog.js.map +1 -1
  26. package/src/McpToolRegistry.js +8 -2
  27. package/src/McpToolRegistry.js.map +1 -1
  28. package/src/McpToolset.d.ts +115 -0
  29. package/src/McpToolset.js +323 -0
  30. package/src/McpToolset.js.map +1 -0
  31. package/src/WpMcpErrorTranslator.d.ts +5 -7
  32. package/src/WpMcpErrorTranslator.js +7 -13
  33. package/src/WpMcpErrorTranslator.js.map +1 -1
  34. package/src/WpMcpInMemory.d.ts +30 -0
  35. package/src/WpMcpInMemory.js +97 -0
  36. package/src/WpMcpInMemory.js.map +1 -0
  37. package/src/WpMcpRemote.d.ts +23 -0
  38. package/src/WpMcpRemote.js +89 -0
  39. package/src/WpMcpRemote.js.map +1 -0
  40. package/src/WpMcpServer.d.ts +45 -76
  41. package/src/WpMcpServer.js +101 -327
  42. package/src/WpMcpServer.js.map +1 -1
  43. package/src/__tests__/McpTransportFixtures.d.ts +107 -0
  44. package/src/__tests__/McpTransportFixtures.js +266 -0
  45. package/src/__tests__/McpTransportFixtures.js.map +1 -0
  46. package/src/index.d.ts +9 -2
  47. package/src/index.js +17 -1
  48. package/src/index.js.map +1 -1
package/src/McpAuth.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { McpToolProfiles } from '@webpieces/core-util';
2
- import { AuthenticatedCaller, AuthorizationService } from '@webpieces/http-routing';
1
+ import { AuthMethod } from '@webpieces/core-util';
2
+ import { AuthenticatedCaller, CredentialVerifiers } from '@webpieces/http-routing';
3
3
  import { Icon } from '@modelcontextprotocol/server';
4
4
  import { McpIcon } from './McpIcon';
5
5
  export declare const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS: number;
@@ -41,8 +41,8 @@ export declare class VerifiedMcpCredential {
41
41
  get principalRoles(): readonly string[];
42
42
  }
43
43
  /**
44
- * Application-owned paired mint/verify authority for resource-bound MCP access tokens.
45
- * `verifyAccessToken` must throw `ApiUnauthorizedError` for a token it rejects (the bind boundary
44
+ * Application-owned paired mint/verify authority for resource-bound MCP access tokens, handed to
45
+ * `mcpOAuth(...)`. `verifyAccessToken` must throw `ApiUnauthorizedError` for a token it rejects (the bind boundary
46
46
  * answers 401 + `WWW-Authenticate`); any other throw is treated as an implementation failure (500).
47
47
  */
48
48
  export interface McpAccessTokenAuthority<TGrant> {
@@ -58,41 +58,51 @@ export declare class McpProtectedResourceMetadata {
58
58
  constructor(resource: string, authorizationServers: readonly string[], scopesSupported?: readonly string[]);
59
59
  }
60
60
  /**
61
- * Node MCP server configuration, built with fluent setters: every name sits next to its own value, so
62
- * no two settings can be swapped, and a future setting is an additive setter rather than a breaking
63
- * signature change. The access-token authority verifies MCP credentials; the authorization service
64
- * is shared with receiving endpoints and document projection.
61
+ * One member of a server's `setAuth([...])` set: an `@WpAuth` builder (`oidcSameProject()`,
62
+ * `oidcSvcs([...])`, `jwt()`, `apiKey(...)`, `sharedSecret(...)`) or `mcpOAuth(...)`.
63
+ */
64
+ export type McpHttpAuth = AuthMethod | McpOAuthMode;
65
+ /** A server's `setAuth([...])` set: at least one mode. */
66
+ export type McpHttpAuths = readonly [McpHttpAuth, ...McpHttpAuth[]];
67
+ /**
68
+ * Node MCP HTTP server configuration, built with fluent setters: every name sits next to its own
69
+ * value, so no two settings can be swapped, and a future setting is an additive setter rather than a
70
+ * breaking signature change.
65
71
  *
66
- * SINGLE CANONICAL ORIGIN: one server serves exactly one protected resource. {@link setResource} is
67
- * fixed for the lifetime of the process, is the audience of every access token, and is never derived
68
- * from the request `Host` — see that setter for why a host-derived audience is a confused deputy.
72
+ * WHICH tools the server serves is not here: that is the `McpToolset` handed to
73
+ * `new WpMcpServer(config, toolset)`. This config says only who the server is and HOW A CALLER GETS
74
+ * IN — {@link setAuth}, an OR-set of the same `@WpAuth` builders an endpoint uses plus `mcpOAuth(...)`.
75
+ *
76
+ * SINGLE CANONICAL ORIGIN: one server serves exactly one resource. {@link setResource} is fixed for
77
+ * the lifetime of the process, is the audience of every `mcpOAuth(...)` access token, and is never
78
+ * derived from the request `Host` — see that setter for why a host-derived audience is a confused
79
+ * deputy.
69
80
  *
70
81
  * Each setter validates its own value immediately and names itself in the failure. Setters cannot
71
82
  * make a field impossible to forget the way a positional constructor could, so
72
83
  * {@link WpMcpServerConfig.validate} runs from `WpMcpServer.bind(...)` and fails at startup listing
73
84
  * EVERY missing required setter at once. A misconfiguration is therefore a boot failure naming the
74
- * setter, instead of a first-request 401 + `WWW-Authenticate` — which is the OAuth discovery signal,
75
- * so the client's correct response is to re-authenticate and fail again, forever.
85
+ * setter, instead of a first-request 401 — which, with `mcpOAuth(...)`, is the OAuth discovery
86
+ * signal, so the client's correct response is to re-authenticate and fail again, forever.
76
87
  *
77
88
  * ```ts
78
- * const config = new WpMcpServerConfig<MyGrant>()
89
+ * const config = new WpMcpServerConfig()
79
90
  * .setName('my-server')
80
91
  * .setTitle('My Product')
81
92
  * .setDescription('Search and manage My Product accounts from any MCP client.')
82
93
  * .setWebsiteUrl('https://www.example.com/my-product')
83
94
  * .setVersion('1.0.0')
84
- * .setResource('https://api.example.com/mcp')
85
- * .setAccessTokenAuthority(authority)
86
- * .setAuthorizationService(router.authorizationService())
87
- * .setAuthorizationServers(['https://login.example.com'])
88
- * .setRequiredScopes(['tools']);
95
+ * .setResource('https://api.example.com/mcp/user')
96
+ * .setAuth([oidcSameProject()]) // internal svc-to-svc MCP
97
+ * .setCredentialVerifiers(router.credentialVerifiers());
98
+ * // .setAuth([oidcSameProject(), jwt(), apiKey('partner', creds),
99
+ * // mcpOAuth(authority, ['https://login.example.com'], ['tools'], McpAccessTokenFormat.opaquePrefix('wpmcp_'))])
89
100
  *
90
101
  * // the tools/call error translator is a PROCESS-GLOBAL, not a member of this config:
91
102
  * McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());
92
103
  * ```
93
104
  */
94
- export declare class WpMcpServerConfig<TGrant> {
95
- private toolProfilesValue;
105
+ export declare class WpMcpServerConfig {
96
106
  private nameValue?;
97
107
  private titleValue?;
98
108
  private descriptionValue?;
@@ -100,14 +110,8 @@ export declare class WpMcpServerConfig<TGrant> {
100
110
  private versionValue?;
101
111
  private iconsValue?;
102
112
  private resourceValue?;
103
- private accessTokenAuthorityValue?;
104
- private authorizationServiceValue?;
105
- private authorizationServersValue?;
106
- private requiredScopesValue?;
107
- private maxAccountValidationAgeSecondsValue;
108
- /** OPTIONAL. Union of groups exposed by this instance; defaults to [Mcp.DEFAULT]. */
109
- setToolProfiles(profiles: McpToolProfiles): this;
110
- get toolProfiles(): readonly string[];
113
+ private authValue?;
114
+ private credentialVerifiersValue?;
111
115
  /** REQUIRED. The MCP server name reported by `initialize`. */
112
116
  setName(name: string): this;
113
117
  /**
@@ -140,9 +144,9 @@ export declare class WpMcpServerConfig<TGrant> {
140
144
  /** Immutable snapshot; omitted icons remain absent from SDK implementation metadata. */
141
145
  get icons(): readonly McpIcon[] | undefined;
142
146
  /**
143
- * REQUIRED. The ONE canonical protected-resource URI, which must be an absolute URL: it is the
144
- * exact audience every access token is checked against, and {@link resourceMetadataUrl} is
145
- * derived from it for the 401 challenge, so a non-URL here is always a mistake — including a
147
+ * REQUIRED. The ONE canonical resource URI, which must be an absolute URL: it is the exact
148
+ * audience every `mcpOAuth(...)` access token is checked against, and {@link resourceMetadataUrl}
149
+ * is derived from it for the 401 challenge, so a non-URL here is always a mistake — including a
146
150
  * name or version landed on it.
147
151
  *
148
152
  * It is fixed for the lifetime of the process and must NEVER be derived from the request `Host`
@@ -153,23 +157,23 @@ export declare class WpMcpServerConfig<TGrant> {
153
157
  * audience.
154
158
  */
155
159
  setResource(resource: string): this;
156
- /** REQUIRED. Application-owned paired mint/verify authority for MCP access tokens. */
157
- setAccessTokenAuthority(authority: McpAccessTokenAuthority<TGrant>): this;
158
- /** REQUIRED. The common authorization service for tool projection and invocation. */
159
- setAuthorizationService(authority: AuthorizationService): this;
160
160
  /**
161
- * REQUIRED. The trusted token issuers. Every entry must be an absolute URL, because each is
162
- * compared against a token's `issuer` claim and published as an `authorization_servers` entry —
163
- * which is what makes a scope list landed here fail at boot rather than on the first request.
161
+ * REQUIRED. HOW a caller gets in: an OR-set of the `@WpAuth` builders endpoints already use —
162
+ * `oidcSameProject()`, `oidcSvcs([...])`, `jwt()`, `apiKey(regime, creds)`, `sharedSecret(key)` —
163
+ * plus `mcpOAuth(...)` for off-the-shelf connectors. Each request is CLASSIFIED from unverified
164
+ * surface facts (which header; a JWT's unverified `iss`) and handed to exactly ONE verifier, whose
165
+ * rejection is the answer — it never falls through to another mode.
166
+ *
167
+ * `vendorAuth(...)` and `@WpAuthPublic` identify no caller to authorize tools against, so they are
168
+ * refused at bind; so is a set whose modes could both claim one request.
164
169
  */
165
- setAuthorizationServers(authorizationServers: readonly string[]): this;
170
+ setAuth(auth: McpHttpAuths): this;
166
171
  /**
167
- * REQUIRED, and empty only by saying so explicitly. Scopes every access token must carry before
168
- * any tool is dispatched.
172
+ * REQUIRED when {@link setAuth} holds any `@WpAuth` mode (everything but `mcpOAuth(...)`): the
173
+ * verifiers this app bound, from `router.credentialVerifiers()` — the SAME JwtHook, OidcHook /
174
+ * DefaultOidcVerifier, AuthConfig and ApiKeyHook the endpoint `AuthFilter` uses.
169
175
  */
170
- setRequiredScopes(requiredScopes: readonly string[]): this;
171
- /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */
172
- setMaxAccountValidationAgeSeconds(seconds: number): this;
176
+ setCredentialVerifiers(verifiers: CredentialVerifiers): this;
173
177
  /**
174
178
  * Called by `WpMcpServer.bind(...)`. Throws naming EVERY missing required setter at once, so a
175
179
  * half-built config is repaired in one pass rather than one boot failure at a time.
@@ -181,29 +185,84 @@ export declare class WpMcpServerConfig<TGrant> {
181
185
  get websiteUrl(): string;
182
186
  get version(): string;
183
187
  get resource(): string;
184
- get accessTokenAuthority(): McpAccessTokenAuthority<TGrant>;
185
- get authorizationService(): AuthorizationService;
186
- get authorizationServers(): readonly string[];
187
- get requiredScopes(): readonly string[];
188
- get maxAccountValidationAgeSeconds(): number;
188
+ get auth(): McpHttpAuths;
189
+ /** The bound verifiers, or `undefined` for an `mcpOAuth(...)`-only server, which needs none. */
190
+ get credentialVerifiers(): CredentialVerifiers | undefined;
189
191
  /**
190
192
  * RFC 9728 §3.1: the URL of this resource's protected-resource METADATA DOCUMENT, derived from
191
193
  * {@link resource} by inserting `/.well-known/oauth-protected-resource` between origin and path
192
194
  * (`https://host/mcp` → `https://host/.well-known/oauth-protected-resource/mcp`).
193
195
  *
194
196
  * This — never the resource identifier itself — is what RFC 9728 §5.1 defines `resource_metadata`
195
- * to be, so it is the value webpieces puts in the 401 `WWW-Authenticate` challenge. webpieces
196
- * mounts no `.well-known` route; publish {@link protectedResourceMetadata} at this URL.
197
+ * to be, so it is the value webpieces puts in the `mcpOAuth(...)` 401 `WWW-Authenticate`
198
+ * challenge. webpieces mounts no `.well-known` route; publish
199
+ * `WpMcpServer.protectedResourceMetadata()` at this URL.
197
200
  */
198
201
  get resourceMetadataUrl(): string;
199
- protectedResourceMetadata(): McpProtectedResourceMetadata;
200
202
  /** Copies caller-owned descriptors and nested size arrays before freezing the snapshot. */
201
203
  private snapshotIcons;
202
204
  private requireText;
203
205
  private requireUrl;
204
206
  private requireMaxLength;
205
- private requireArray;
206
- private requireSeconds;
207
- private requirePresent;
208
207
  private read;
209
208
  }
209
+ /** Google's OIDC issuers. An `mcpOAuth(...)` issuer may never be one: it would claim Google tokens. */
210
+ export declare const GOOGLE_OIDC_ISSUERS: readonly string[];
211
+ /** The unsigned off-GCP OIDC token prefix `@webpieces/gcp-identity` mints and verifies. */
212
+ export declare const DEV_OIDC_TOKEN_PREFIX = "dev-oidc.";
213
+ /** Every JWT starts with the base64url of `{"`. An opaque prefix starting so would claim JWTs. */
214
+ export declare const JWT_PREFIX = "eyJ";
215
+ export type McpAccessTokenFormatKind = 'opaque-prefix' | 'jwt-issuer' | 'every-bearer';
216
+ /** The trusted token issuers of an `mcpOAuth(...)` mode: at least one absolute URL. */
217
+ export type McpAuthorizationServers = readonly [string, ...string[]];
218
+ /**
219
+ * How an `mcpOAuth(...)` access token LOOKS, decided by the authority that mints it. The MCP door
220
+ * classifies every request from unverified surface facts before handing it to exactly ONE verifier, so
221
+ * the OAuth mode must say which bearers are its kind without any crypto or I/O:
222
+ *
223
+ * - {@link opaquePrefix} — the authority mints opaque tokens that all start with `prefix`
224
+ * (e.g. `'wpmcp_'`). Disjoint from every JWT and from off-GCP OIDC tokens by construction.
225
+ * - {@link jwtIssuer} — the authority mints JWTs whose `iss` is one of the mode's
226
+ * `authorizationServers`. A JWT with any other issuer is not this mode's.
227
+ * - {@link everyBearer} — every `Authorization: Bearer` credential is this mode's. Legal ONLY when no
228
+ * other bearer mode (`oidcSameProject()`, `oidcSvcs(...)`, `jwt()`, a bearer `apiKey(...)`) is in the
229
+ * same `setAuth([...])`; startup refuses the combination, because two modes claiming one token is
230
+ * exactly the overlap classify-then-verify forbids.
231
+ */
232
+ export declare class McpAccessTokenFormat {
233
+ readonly kind: McpAccessTokenFormatKind;
234
+ readonly prefix: string;
235
+ private constructor();
236
+ static opaquePrefix(prefix: string): McpAccessTokenFormat;
237
+ static jwtIssuer(): McpAccessTokenFormat;
238
+ static everyBearer(): McpAccessTokenFormat;
239
+ }
240
+ /**
241
+ * The MCP OAuth mode: today's connector behavior (Claude / ChatGPT) as ONE member of a server's
242
+ * `setAuth([...])` set. It is the only mode that publishes RFC 9728 protected-resource metadata and
243
+ * answers a request carrying no credential with the `401 WWW-Authenticate` discovery challenge, and
244
+ * its access tokens are audience-bound to the server's canonical resource. Build it with
245
+ * {@link mcpOAuth}.
246
+ */
247
+ export declare class McpOAuthMode {
248
+ readonly authority: McpAccessTokenAuthority<never>;
249
+ readonly authorizationServers: McpAuthorizationServers;
250
+ readonly requiredScopes: readonly string[];
251
+ readonly tokenFormat: McpAccessTokenFormat;
252
+ readonly maxAccountValidationAgeSeconds: number;
253
+ readonly kind = "mcp-oauth";
254
+ constructor(authority: McpAccessTokenAuthority<never>, authorizationServers: McpAuthorizationServers, requiredScopes: readonly string[], tokenFormat: McpAccessTokenFormat, maxAccountValidationAgeSeconds: number);
255
+ /** RFC 9728 metadata for `resource`, published by the app at the server's `resourceMetadataUrl`. */
256
+ protectedResourceMetadata(resource: string): McpProtectedResourceMetadata;
257
+ }
258
+ /**
259
+ * The MCP OAuth door mode, for off-the-shelf connectors. Everything OAuth-only lives here and is
260
+ * required only when this mode is in the server's `setAuth([...])`:
261
+ *
262
+ * @param authority the application-owned paired mint/verify authority
263
+ * @param authorizationServers the trusted token issuers, published as `authorization_servers`
264
+ * @param requiredScopes scopes every access token must carry before any tool is dispatched
265
+ * @param tokenFormat how the authority's tokens look, so the door can classify them
266
+ * @param maxAccountValidationAgeSeconds how fresh the account facts must be, 1 second to 1 hour
267
+ */
268
+ export declare function mcpOAuth<TGrant>(authority: McpAccessTokenAuthority<TGrant>, authorizationServers: McpAuthorizationServers, requiredScopes: readonly string[], tokenFormat: McpAccessTokenFormat, maxAccountValidationAgeSeconds?: number): McpOAuthMode;