@webpieces/mcp-server 0.4.934 → 0.4.936
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 +55 -20
- package/package.json +6 -6
- package/src/McpAuth.d.ts +37 -0
- package/src/McpAuth.js +73 -1
- package/src/McpAuth.js.map +1 -1
- package/src/McpToolRegistry.d.ts +13 -3
- package/src/McpToolRegistry.js +8 -8
- package/src/McpToolRegistry.js.map +1 -1
- package/src/WpMcpServer.js +5 -1
- package/src/WpMcpServer.js.map +1 -1
- package/src/__tests__/McpProfileFixtures.d.ts +20 -4
- package/src/__tests__/McpProfileFixtures.js +25 -9
- package/src/__tests__/McpProfileFixtures.js.map +1 -1
- package/src/__tests__/McpRemoteFixtures.d.ts +35 -7
- package/src/__tests__/McpRemoteFixtures.js +42 -14
- package/src/__tests__/McpRemoteFixtures.js.map +1 -1
- package/src/__tests__/WpMcpServerTestFixtures.d.ts +15 -3
- package/src/__tests__/WpMcpServerTestFixtures.js +18 -6
- package/src/__tests__/WpMcpServerTestFixtures.js.map +1 -1
- package/src/index.d.ts +1 -1
- package/src/index.js +3 -1
- package/src/index.js.map +1 -1
package/README.md
CHANGED
|
@@ -24,31 +24,42 @@ interface FindOrderResponse {
|
|
|
24
24
|
@ApiPath('/orders')
|
|
25
25
|
@ApiType(SVC_TO_SVC, MCP)
|
|
26
26
|
abstract class OrdersApi {
|
|
27
|
-
/**
|
|
27
|
+
/**
|
|
28
|
+
* Find one order owned by the signed-in user.
|
|
29
|
+
*
|
|
30
|
+
* @summary Find orders
|
|
31
|
+
* @returns The order, with its current state.
|
|
32
|
+
*/
|
|
28
33
|
@WpAuth([jwt()])
|
|
29
34
|
@WpAuthorization({ authType: AuthorizationType.ALL_USERS })
|
|
30
35
|
@Endpoint(POST, '/find', READ, RPC)
|
|
31
|
-
@WpMcpTool('orders_find'
|
|
36
|
+
@WpMcpTool('orders_find')
|
|
32
37
|
find(request: FindOrderRequest): Promise<FindOrderResponse> {
|
|
33
38
|
throw new Error('contract only');
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
```
|
|
37
42
|
|
|
38
|
-
## Everything but the tool NAME
|
|
43
|
+
## Everything but the tool NAME comes from the source
|
|
39
44
|
|
|
40
|
-
`@WpMcpTool(name
|
|
41
|
-
|
|
42
|
-
Directory
|
|
43
|
-
`
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
`@WpMcpTool(name)` takes ONE argument (plus the optional profiles object), because the stable protocol
|
|
46
|
+
name is the only fact the source cannot state. It must be 1-64 characters of `[A-Za-z0-9_-]` — the
|
|
47
|
+
Claude Connectors Directory refuses a server with a longer name — and `McpToolName` enforces that rule
|
|
48
|
+
at decoration time, in `wp-openapi` (every offender named in one run), in `api-rules-for-mcp`, and when a
|
|
49
|
+
catalog is loaded.
|
|
50
|
+
|
|
51
|
+
The TITLE is the method's `@summary` JSDoc tag (#1296): one line, at most 100 characters, REQUIRED on
|
|
52
|
+
every published operation. It is the same line the OpenAPI documents publish as the operation's
|
|
53
|
+
`summary`, so a partner's docs page and an agent's tool list name the endpoint identically, and
|
|
54
|
+
`tools/list` publishes it as BOTH the top-level `Tool.title` and `annotations.title` (#1180). The title
|
|
55
|
+
argument the decorator used to take is gone — passing one is a compile error — and a catalog entry
|
|
56
|
+
with no title is refused when it is loaded, so a stale catalog fails the boot instead of publishing
|
|
57
|
+
untitled tools. Write the title a person would read ("Find orders"); never derive it from the name.
|
|
58
|
+
Everything else is read off the contract by `@webpieces/api-doc-model` at BUILD time:
|
|
48
59
|
|
|
49
60
|
| fact | source |
|
|
50
61
|
|---|---|
|
|
51
|
-
| `title` / `annotations.title` | `@
|
|
62
|
+
| `title` / `annotations.title` | the method's `@summary` JSDoc tag — the OpenAPI operation's `summary` |
|
|
52
63
|
| `description` | the method's JSDoc body, or its `@mcp` tag — the same words the partner reads in OpenAPI |
|
|
53
64
|
| `inputSchema` / `outputSchema` | the declared request and response types, with each field's JSDoc as its description; `inputSchema.description` is the request DTO's JSDoc and `outputSchema.description` is the method's `@returns` — the sentence OpenAPI publishes as the success response's description (#1292) |
|
|
54
65
|
| `readOnlyHint` / `destructiveHint` / `idempotentHint` | `@Endpoint`'s `operation` — `READ`, `WRITE_IDEMPOTENT` or `WRITE` |
|
|
@@ -117,6 +128,9 @@ change. Expect to supply the type arguments explicitly:
|
|
|
117
128
|
```ts
|
|
118
129
|
const config = new WpMcpServerConfig<MyGrant>()
|
|
119
130
|
.setName('my-server')
|
|
131
|
+
.setTitle('My Product')
|
|
132
|
+
.setDescription('Search and manage My Product accounts from any MCP client.')
|
|
133
|
+
.setWebsiteUrl('https://www.example.com/my-product')
|
|
120
134
|
.setVersion('1.0.0')
|
|
121
135
|
.setResource('https://api.example.com/mcp')
|
|
122
136
|
.setAccessTokenAuthority(authority)
|
|
@@ -131,9 +145,26 @@ McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());
|
|
|
131
145
|
|
|
132
146
|
Each setter validates its own value immediately and names itself in the failure: `setResource(...)`
|
|
133
147
|
and every entry of `setAuthorizationServers(...)` must be an absolute URL, because they are compared
|
|
134
|
-
against a token's audience and issuer. `setMaxAccountValidationAgeSeconds` and
|
|
135
|
-
|
|
136
|
-
`
|
|
148
|
+
against a token's audience and issuer. `setMaxAccountValidationAgeSeconds` defaults to one hour and is capped there.
|
|
149
|
+
|
|
150
|
+
`setTitle`, `setDescription` and `setWebsiteUrl` are REQUIRED and are sent on `initialize`'s
|
|
151
|
+
`serverInfo` (and on every modern-era result's `io.modelcontextprotocol/serverInfo` meta) as
|
|
152
|
+
`title`, `description` and `websiteUrl`:
|
|
153
|
+
|
|
154
|
+
| setter | `serverInfo` field | validation |
|
|
155
|
+
|---|---|---|
|
|
156
|
+
| `setTitle(title)` | `title` (MCP 2025-06-18) | one line, at most 100 characters |
|
|
157
|
+
| `setDescription(description)` | `description` (MCP 2025-11-25) | non-empty, at most 2000 characters |
|
|
158
|
+
| `setWebsiteUrl(url)` | `websiteUrl` (MCP 2025-11-25) | absolute `https://` URL |
|
|
159
|
+
|
|
160
|
+
The limits are the Claude Connectors Directory's listing Name and Description limits. The app sets all
|
|
161
|
+
three because the server cannot derive them: it does not read the OpenAPI manifest at runtime, and one
|
|
162
|
+
server may bind contracts from several api libraries. `websiteUrl` is a page a human reads (the product
|
|
163
|
+
homepage or docs site), so it is never derived from `setResource` — that is the MCP endpoint, not a
|
|
164
|
+
website. There are no defaults; a missing one fails `bind(...)` naming the setter, like `setName`.
|
|
165
|
+
|
|
166
|
+
`setIcons(...)` and `setToolProfiles(...)` are the only optional non-ceiling settings (the error
|
|
167
|
+
translator is process-global, on `McpRegistry`); everything else is required and
|
|
137
168
|
`WpMcpServer.bind(...)` fails at startup listing **every** missing setter
|
|
138
169
|
(`WpMcpServerConfig is missing setResource(...)`). That is deliberate: a misconfigured audience or
|
|
139
170
|
issuer otherwise surfaces as a first-request `401 + WWW-Authenticate`, which is the OAuth discovery
|
|
@@ -413,13 +444,14 @@ Profile membership partitions tool exposure; account roles still belong exclusiv
|
|
|
413
444
|
`WpMcpTool` from the browser-safe `@webpieces/core-util` package:
|
|
414
445
|
|
|
415
446
|
```ts
|
|
416
|
-
@WpMcpTool('learner_get_passages'
|
|
417
|
-
@WpMcpTool('admin_list_users',
|
|
418
|
-
@WpMcpTool('shared_tool',
|
|
447
|
+
@WpMcpTool('learner_get_passages') // only Mcp.DEFAULT
|
|
448
|
+
@WpMcpTool('admin_list_users', { profiles: ['admin'] }) // only admin
|
|
449
|
+
@WpMcpTool('shared_tool', { profiles: [Mcp.DEFAULT, 'admin'] })
|
|
419
450
|
```
|
|
420
451
|
|
|
421
|
-
Each declaration also needs the usual endpoint, DTO, authentication, and authorization declarations
|
|
422
|
-
|
|
452
|
+
Each declaration also needs the usual endpoint, DTO, authentication, and authorization declarations,
|
|
453
|
+
and a `@summary` line in the method's JSDoc for its title.
|
|
454
|
+
One argument, an omitted `profiles` property, and explicit `[Mcp.DEFAULT]` all select only the base
|
|
423
455
|
group. An explicit list replaces that membership; it never implicitly adds the base group.
|
|
424
456
|
Identifiers match `[a-z][a-z0-9-]{0,63}` (1–64 lowercase ASCII letters, digits or hyphens, starting
|
|
425
457
|
with a letter). `Mcp.DEFAULT` reserves `default` for the base group. Empty lists, duplicates, unknown
|
|
@@ -460,6 +492,9 @@ import { McpIcon, WpMcpServerConfig } from '@webpieces/mcp-server';
|
|
|
460
492
|
|
|
461
493
|
const config = new WpMcpServerConfig<MyGrant>()
|
|
462
494
|
.setName('my-product')
|
|
495
|
+
.setTitle('My Product')
|
|
496
|
+
.setDescription('Search and manage My Product accounts from any MCP client.')
|
|
497
|
+
.setWebsiteUrl('https://www.example.com/my-product')
|
|
463
498
|
.setVersion('1.0.0')
|
|
464
499
|
.setResource('https://api.example.com/mcp')
|
|
465
500
|
.setIcons([
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.936",
|
|
4
4
|
"description": "Secure MCP tool generation from annotated Webpieces API contracts",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -24,15 +24,15 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@modelcontextprotocol/node": "2.0.0",
|
|
26
26
|
"@modelcontextprotocol/server": "2.0.0",
|
|
27
|
-
"@webpieces/core-context": "0.4.
|
|
28
|
-
"@webpieces/core-util": "0.4.
|
|
29
|
-
"@webpieces/http-routing": "0.4.
|
|
30
|
-
"@webpieces/http-server": "0.4.
|
|
27
|
+
"@webpieces/core-context": "0.4.936",
|
|
28
|
+
"@webpieces/core-util": "0.4.936",
|
|
29
|
+
"@webpieces/http-routing": "0.4.936",
|
|
30
|
+
"@webpieces/http-server": "0.4.936",
|
|
31
31
|
"express": "5.1.0",
|
|
32
32
|
"reflect-metadata": "0.2.2",
|
|
33
33
|
"tslib": "2.8.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@webpieces/http-client-node": "0.4.
|
|
36
|
+
"@webpieces/http-client-node": "0.4.936"
|
|
37
37
|
}
|
|
38
38
|
}
|
package/src/McpAuth.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ import { Icon } from '@modelcontextprotocol/server';
|
|
|
4
4
|
import { McpIcon } from './McpIcon';
|
|
5
5
|
export declare const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS: number;
|
|
6
6
|
export declare const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS: number;
|
|
7
|
+
/** `setTitle` ceiling: the Claude Connectors Directory's listing Name limit. */
|
|
8
|
+
export declare const MAX_MCP_SERVER_TITLE_LENGTH = 100;
|
|
9
|
+
/** `setDescription` ceiling: the Claude Connectors Directory's listing Description limit. */
|
|
10
|
+
export declare const MAX_MCP_SERVER_DESCRIPTION_LENGTH = 2000;
|
|
7
11
|
/** Framework-normalized access-token result. The token representation remains application-owned. */
|
|
8
12
|
export declare class MintedMcpAccessToken {
|
|
9
13
|
readonly token: string;
|
|
@@ -73,6 +77,9 @@ export declare class McpProtectedResourceMetadata {
|
|
|
73
77
|
* ```ts
|
|
74
78
|
* const config = new WpMcpServerConfig<MyGrant>()
|
|
75
79
|
* .setName('my-server')
|
|
80
|
+
* .setTitle('My Product')
|
|
81
|
+
* .setDescription('Search and manage My Product accounts from any MCP client.')
|
|
82
|
+
* .setWebsiteUrl('https://www.example.com/my-product')
|
|
76
83
|
* .setVersion('1.0.0')
|
|
77
84
|
* .setResource('https://api.example.com/mcp')
|
|
78
85
|
* .setAccessTokenAuthority(authority)
|
|
@@ -87,6 +94,9 @@ export declare class McpProtectedResourceMetadata {
|
|
|
87
94
|
export declare class WpMcpServerConfig<TGrant> {
|
|
88
95
|
private toolProfilesValue;
|
|
89
96
|
private nameValue?;
|
|
97
|
+
private titleValue?;
|
|
98
|
+
private descriptionValue?;
|
|
99
|
+
private websiteUrlValue?;
|
|
90
100
|
private versionValue?;
|
|
91
101
|
private iconsValue?;
|
|
92
102
|
private resourceValue?;
|
|
@@ -100,6 +110,29 @@ export declare class WpMcpServerConfig<TGrant> {
|
|
|
100
110
|
get toolProfiles(): readonly string[];
|
|
101
111
|
/** REQUIRED. The MCP server name reported by `initialize`. */
|
|
102
112
|
setName(name: string): this;
|
|
113
|
+
/**
|
|
114
|
+
* REQUIRED. The human-readable display name sent as `serverInfo.title` (MCP 2025-06-18), e.g.
|
|
115
|
+
* `'Lang Learning'` where {@link setName} is the programmatic `'lang-learning'`. One line, at most
|
|
116
|
+
* 100 characters — the Claude Connectors Directory's listing Name limit.
|
|
117
|
+
*
|
|
118
|
+
* The app sets it because the server cannot derive it: it does not read the OpenAPI manifest at
|
|
119
|
+
* runtime, and one MCP server may bind contracts from several api libraries.
|
|
120
|
+
*/
|
|
121
|
+
setTitle(title: string): this;
|
|
122
|
+
/**
|
|
123
|
+
* REQUIRED. What this server does, sent as `serverInfo.description` (MCP 2025-11-25). At most
|
|
124
|
+
* 2000 characters — the Claude Connectors Directory's listing Description limit.
|
|
125
|
+
*/
|
|
126
|
+
setDescription(description: string): this;
|
|
127
|
+
/**
|
|
128
|
+
* REQUIRED. An absolute `https://` URL of a page a HUMAN reads about this server — the product
|
|
129
|
+
* homepage or its docs site — sent as `serverInfo.websiteUrl` (MCP 2025-11-25).
|
|
130
|
+
*
|
|
131
|
+
* Deliberately NOT derived from {@link setResource}: that is the MCP endpoint, an API host, not a
|
|
132
|
+
* website, and the resource is never derived from request input either. No default, because the
|
|
133
|
+
* page a user is sent to is the app's decision.
|
|
134
|
+
*/
|
|
135
|
+
setWebsiteUrl(websiteUrl: string): this;
|
|
103
136
|
/** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */
|
|
104
137
|
setVersion(version: string): this;
|
|
105
138
|
/** OPTIONAL. Standard MCP icons; copied and frozen, with no network access. */
|
|
@@ -143,6 +176,9 @@ export declare class WpMcpServerConfig<TGrant> {
|
|
|
143
176
|
*/
|
|
144
177
|
validate(): void;
|
|
145
178
|
get name(): string;
|
|
179
|
+
get title(): string;
|
|
180
|
+
get description(): string;
|
|
181
|
+
get websiteUrl(): string;
|
|
146
182
|
get version(): string;
|
|
147
183
|
get resource(): string;
|
|
148
184
|
get accessTokenAuthority(): McpAccessTokenAuthority<TGrant>;
|
|
@@ -165,6 +201,7 @@ export declare class WpMcpServerConfig<TGrant> {
|
|
|
165
201
|
private snapshotIcons;
|
|
166
202
|
private requireText;
|
|
167
203
|
private requireUrl;
|
|
204
|
+
private requireMaxLength;
|
|
168
205
|
private requireArray;
|
|
169
206
|
private requireSeconds;
|
|
170
207
|
private requirePresent;
|
package/src/McpAuth.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WpMcpServerConfig = exports.McpProtectedResourceMetadata = exports.VerifiedMcpCredential = exports.MintedMcpAccessToken = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = void 0;
|
|
3
|
+
exports.WpMcpServerConfig = exports.McpProtectedResourceMetadata = exports.VerifiedMcpCredential = exports.MintedMcpAccessToken = exports.MAX_MCP_SERVER_DESCRIPTION_LENGTH = exports.MAX_MCP_SERVER_TITLE_LENGTH = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = void 0;
|
|
4
4
|
const core_util_1 = require("@webpieces/core-util");
|
|
5
5
|
const McpIcon_1 = require("./McpIcon");
|
|
6
6
|
exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;
|
|
7
7
|
exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = 60 * 60;
|
|
8
|
+
/** `setTitle` ceiling: the Claude Connectors Directory's listing Name limit. */
|
|
9
|
+
exports.MAX_MCP_SERVER_TITLE_LENGTH = 100;
|
|
10
|
+
/** `setDescription` ceiling: the Claude Connectors Directory's listing Description limit. */
|
|
11
|
+
exports.MAX_MCP_SERVER_DESCRIPTION_LENGTH = 2000;
|
|
8
12
|
/** Framework-normalized access-token result. The token representation remains application-owned. */
|
|
9
13
|
class MintedMcpAccessToken {
|
|
10
14
|
token;
|
|
@@ -98,6 +102,9 @@ exports.McpProtectedResourceMetadata = McpProtectedResourceMetadata;
|
|
|
98
102
|
* ```ts
|
|
99
103
|
* const config = new WpMcpServerConfig<MyGrant>()
|
|
100
104
|
* .setName('my-server')
|
|
105
|
+
* .setTitle('My Product')
|
|
106
|
+
* .setDescription('Search and manage My Product accounts from any MCP client.')
|
|
107
|
+
* .setWebsiteUrl('https://www.example.com/my-product')
|
|
101
108
|
* .setVersion('1.0.0')
|
|
102
109
|
* .setResource('https://api.example.com/mcp')
|
|
103
110
|
* .setAccessTokenAuthority(authority)
|
|
@@ -112,6 +119,9 @@ exports.McpProtectedResourceMetadata = McpProtectedResourceMetadata;
|
|
|
112
119
|
class WpMcpServerConfig {
|
|
113
120
|
toolProfilesValue = Object.freeze([core_util_1.Mcp.DEFAULT]);
|
|
114
121
|
nameValue;
|
|
122
|
+
titleValue;
|
|
123
|
+
descriptionValue;
|
|
124
|
+
websiteUrlValue;
|
|
115
125
|
versionValue;
|
|
116
126
|
iconsValue;
|
|
117
127
|
resourceValue;
|
|
@@ -133,6 +143,47 @@ class WpMcpServerConfig {
|
|
|
133
143
|
this.nameValue = this.requireText(name, 'setName');
|
|
134
144
|
return this;
|
|
135
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* REQUIRED. The human-readable display name sent as `serverInfo.title` (MCP 2025-06-18), e.g.
|
|
148
|
+
* `'Lang Learning'` where {@link setName} is the programmatic `'lang-learning'`. One line, at most
|
|
149
|
+
* 100 characters — the Claude Connectors Directory's listing Name limit.
|
|
150
|
+
*
|
|
151
|
+
* The app sets it because the server cannot derive it: it does not read the OpenAPI manifest at
|
|
152
|
+
* runtime, and one MCP server may bind contracts from several api libraries.
|
|
153
|
+
*/
|
|
154
|
+
setTitle(title) {
|
|
155
|
+
const value = this.requireText(title, 'setTitle');
|
|
156
|
+
if (/[\r\n]/.test(value)) {
|
|
157
|
+
throw new Error('WpMcpServerConfig.setTitle(...) requires a single line of text.');
|
|
158
|
+
}
|
|
159
|
+
this.titleValue = this.requireMaxLength(value, exports.MAX_MCP_SERVER_TITLE_LENGTH, 'setTitle');
|
|
160
|
+
return this;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* REQUIRED. What this server does, sent as `serverInfo.description` (MCP 2025-11-25). At most
|
|
164
|
+
* 2000 characters — the Claude Connectors Directory's listing Description limit.
|
|
165
|
+
*/
|
|
166
|
+
setDescription(description) {
|
|
167
|
+
const value = this.requireText(description, 'setDescription');
|
|
168
|
+
this.descriptionValue = this.requireMaxLength(value, exports.MAX_MCP_SERVER_DESCRIPTION_LENGTH, 'setDescription');
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* REQUIRED. An absolute `https://` URL of a page a HUMAN reads about this server — the product
|
|
173
|
+
* homepage or its docs site — sent as `serverInfo.websiteUrl` (MCP 2025-11-25).
|
|
174
|
+
*
|
|
175
|
+
* Deliberately NOT derived from {@link setResource}: that is the MCP endpoint, an API host, not a
|
|
176
|
+
* website, and the resource is never derived from request input either. No default, because the
|
|
177
|
+
* page a user is sent to is the app's decision.
|
|
178
|
+
*/
|
|
179
|
+
setWebsiteUrl(websiteUrl) {
|
|
180
|
+
const value = this.requireUrl(websiteUrl, 'setWebsiteUrl');
|
|
181
|
+
if (new URL(value).protocol !== 'https:') {
|
|
182
|
+
throw new Error(`WpMcpServerConfig.setWebsiteUrl(...) requires an https:// URL, got '${value}'.`);
|
|
183
|
+
}
|
|
184
|
+
this.websiteUrlValue = value;
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
136
187
|
/** REQUIRED. The MCP server version; the registry revision is appended to it at bind time. */
|
|
137
188
|
setVersion(version) {
|
|
138
189
|
this.versionValue = this.requireText(version, 'setVersion');
|
|
@@ -213,6 +264,12 @@ class WpMcpServerConfig {
|
|
|
213
264
|
const missing = [];
|
|
214
265
|
if (this.nameValue === undefined)
|
|
215
266
|
missing.push('setName(...)');
|
|
267
|
+
if (this.titleValue === undefined)
|
|
268
|
+
missing.push('setTitle(...)');
|
|
269
|
+
if (this.descriptionValue === undefined)
|
|
270
|
+
missing.push('setDescription(...)');
|
|
271
|
+
if (this.websiteUrlValue === undefined)
|
|
272
|
+
missing.push('setWebsiteUrl(...)');
|
|
216
273
|
if (this.versionValue === undefined)
|
|
217
274
|
missing.push('setVersion(...)');
|
|
218
275
|
if (this.resourceValue === undefined)
|
|
@@ -241,6 +298,15 @@ class WpMcpServerConfig {
|
|
|
241
298
|
get name() {
|
|
242
299
|
return this.read(this.nameValue, 'setName');
|
|
243
300
|
}
|
|
301
|
+
get title() {
|
|
302
|
+
return this.read(this.titleValue, 'setTitle');
|
|
303
|
+
}
|
|
304
|
+
get description() {
|
|
305
|
+
return this.read(this.descriptionValue, 'setDescription');
|
|
306
|
+
}
|
|
307
|
+
get websiteUrl() {
|
|
308
|
+
return this.read(this.websiteUrlValue, 'setWebsiteUrl');
|
|
309
|
+
}
|
|
244
310
|
get version() {
|
|
245
311
|
return this.read(this.versionValue, 'setVersion');
|
|
246
312
|
}
|
|
@@ -304,6 +370,12 @@ class WpMcpServerConfig {
|
|
|
304
370
|
}
|
|
305
371
|
return value;
|
|
306
372
|
}
|
|
373
|
+
requireMaxLength(value, max, setter) {
|
|
374
|
+
if (value.length > max) {
|
|
375
|
+
throw new Error(`WpMcpServerConfig.${setter}(...) allows at most ${max} characters, got ${value.length}.`);
|
|
376
|
+
}
|
|
377
|
+
return value;
|
|
378
|
+
}
|
|
307
379
|
requireArray(value, setter) {
|
|
308
380
|
if (!Array.isArray(value)) {
|
|
309
381
|
throw new Error(`WpMcpServerConfig.${setter}(...) requires an array of strings.`);
|
package/src/McpAuth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"McpAuth.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuth.ts"],"names":[],"mappings":"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","sourcesContent":["import { Mcp, McpToolProfiles } from '@webpieces/core-util';\nimport { AuthenticatedCaller, AuthorizationService } from '@webpieces/http-routing';\nimport { Icon } from '@modelcontextprotocol/server';\nimport { McpIcon } from './McpIcon';\n\nexport const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;\nexport const MAX_MCP_ACCOUNT_VALIDATION_AGE_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 and an explicit application-owned caller mapping 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 /** Application mapping of freshly verified account facts into canonical trusted context. */\n public readonly caller: AuthenticatedCaller,\n ) {}\n\n /** The canonical application's roles; there is no independent listing-role snapshot. */\n get principalRoles(): readonly string[] {\n return this.caller.roles;\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/** 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. The access-token authority verifies MCP credentials; the authorization service\n * is shared with receiving endpoints and document projection.\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>()\n * .setName('my-server')\n * .setVersion('1.0.0')\n * .setResource('https://api.example.com/mcp')\n * .setAccessTokenAuthority(authority)\n * .setAuthorizationService(router.authorizationService())\n * .setAuthorizationServers(['https://login.example.com'])\n * .setRequiredScopes(['tools']);\n *\n * // the tools/call error translator is a PROCESS-GLOBAL, not a member of this config:\n * McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());\n * ```\n */\nexport class WpMcpServerConfig<TGrant> {\n private toolProfilesValue: readonly string[] = Object.freeze([Mcp.DEFAULT]);\n private nameValue?: string;\n private versionValue?: string;\n private iconsValue?: readonly McpIcon[];\n private resourceValue?: string;\n private accessTokenAuthorityValue?: McpAccessTokenAuthority<TGrant>;\n private authorizationServiceValue?: AuthorizationService;\n private authorizationServersValue?: readonly string[];\n private requiredScopesValue?: readonly string[];\n private maxAccountValidationAgeSecondsValue = MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS;\n\n /** OPTIONAL. Union of groups exposed by this instance; defaults to [Mcp.DEFAULT]. */\n setToolProfiles(profiles: McpToolProfiles): this {\n this.toolProfilesValue = Mcp.profiles(profiles, 'WpMcpServerConfig.setToolProfiles');\n return this;\n }\n\n get toolProfiles(): readonly string[] {\n return this.toolProfilesValue;\n }\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 /** OPTIONAL. Standard MCP icons; copied and frozen, with no network access. */\n setIcons(icons: readonly Icon[]): this {\n this.iconsValue = this.snapshotIcons(icons);\n return this;\n }\n\n /** Immutable snapshot; omitted icons remain absent from SDK implementation metadata. */\n get icons(): readonly McpIcon[] | undefined {\n return this.iconsValue;\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 common authorization service for tool projection and invocation. */\n setAuthorizationService(authority: AuthorizationService): this {\n this.authorizationServiceValue = this.requirePresent(authority, 'setAuthorizationService');\n return this;\n }\n\n /**\n * REQUIRED. The trusted token issuers. Every entry must be an absolute URL, because each is\n * compared against a token's `issuer` claim and published as an `authorization_servers` entry —\n * which is what makes a scope list landed here fail at boot rather than on the first request.\n */\n setAuthorizationServers(authorizationServers: readonly string[]): this {\n const servers = this.requireArray(authorizationServers, 'setAuthorizationServers');\n if (servers.length === 0) {\n throw new Error(\n 'WpMcpServerConfig.setAuthorizationServers(...) requires at least one issuer URL.',\n );\n }\n for (const server of servers) this.requireUrl(server, 'setAuthorizationServers');\n this.authorizationServersValue = servers;\n return this;\n }\n\n /**\n * REQUIRED, and empty only by saying so explicitly. Scopes every access token must carry before\n * any tool is dispatched.\n */\n setRequiredScopes(requiredScopes: readonly string[]): this {\n const scopes = this.requireArray(requiredScopes, 'setRequiredScopes');\n for (const scope of scopes) this.requireText(scope, 'setRequiredScopes');\n this.requiredScopesValue = scopes;\n return this;\n }\n\n /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */\n setMaxAccountValidationAgeSeconds(seconds: number): this {\n this.maxAccountValidationAgeSecondsValue = this.requireSeconds(\n seconds,\n MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS,\n 'setMaxAccountValidationAgeSeconds',\n );\n return this;\n }\n\n /**\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.authorizationServiceValue === undefined) {\n missing.push('setAuthorizationService(...)');\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 for (const icon of this.iconsValue ?? []) {\n const url = new URL(icon.src);\n if (url.protocol === 'https:' && url.origin !== new URL(this.resource).origin) {\n throw new Error(\n 'WpMcpServerConfig.setIcons(...) requires HTTPS icons on the resource origin.',\n );\n }\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 authorizationService(): AuthorizationService {\n return this.read(this.authorizationServiceValue, 'setAuthorizationService');\n }\n\n get authorizationServers(): readonly string[] {\n return this.read(this.authorizationServersValue, 'setAuthorizationServers');\n }\n\n get requiredScopes(): readonly string[] {\n return this.read(this.requiredScopesValue, 'setRequiredScopes');\n }\n\n get maxAccountValidationAgeSeconds(): number {\n return this.maxAccountValidationAgeSecondsValue;\n }\n\n /**\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 /** Copies caller-owned descriptors and nested size arrays before freezing the snapshot. */\n private snapshotIcons(icons: readonly Icon[]): readonly McpIcon[] {\n if (!Array.isArray(icons)) {\n throw new Error(\n 'WpMcpServerConfig.setIcons(...) requires an array of icon descriptors.',\n );\n }\n return Object.freeze(\n icons.map((icon: Icon) => {\n if (!icon || typeof icon !== 'object') {\n throw new Error('WpMcpServerConfig.setIcons(...) requires icon descriptors.');\n }\n return new McpIcon(icon.src, icon.mimeType, icon.sizes, icon.theme);\n }),\n );\n }\n\n private requireText(value: string, setter: string): string {\n if (typeof value !== 'string' || value.trim() === '') {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires a non-empty string.`);\n }\n return value;\n }\n\n private requireUrl(value: string, setter: string): string {\n this.requireText(value, setter);\n if (!URL.canParse(value)) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) requires an absolute URL, got '${value}'.`,\n );\n }\n return value;\n }\n\n private requireArray(value: readonly string[], setter: string): readonly string[] {\n if (!Array.isArray(value)) {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires an array of strings.`);\n }\n return value;\n }\n\n private requireSeconds(value: number, ceiling: number, setter: string): number {\n if (!Number.isFinite(value) || value <= 0 || value > ceiling) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) requires 1..${ceiling} seconds, got ${String(value)}.`,\n );\n }\n return value;\n }\n\n private requirePresent<T>(value: T, setter: string): T {\n if (value === undefined || value === null) {\n throw new Error(`WpMcpServerConfig.${setter}(...) requires a value.`);\n }\n return value;\n }\n\n private read<T>(value: T | undefined, setter: string): T {\n if (value === undefined) {\n throw new Error(`WpMcpServerConfig is missing ${setter}(...)`);\n }\n return value;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"McpAuth.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuth.ts"],"names":[],"mappings":"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","sourcesContent":["import { Mcp, McpToolProfiles } from '@webpieces/core-util';\nimport { AuthenticatedCaller, AuthorizationService } from '@webpieces/http-routing';\nimport { Icon } from '@modelcontextprotocol/server';\nimport { McpIcon } from './McpIcon';\n\nexport const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;\nexport const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = 60 * 60;\n/** `setTitle` ceiling: the Claude Connectors Directory's listing Name limit. */\nexport const MAX_MCP_SERVER_TITLE_LENGTH = 100;\n/** `setDescription` ceiling: the Claude Connectors Directory's listing Description limit. */\nexport const MAX_MCP_SERVER_DESCRIPTION_LENGTH = 2000;\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 and an explicit application-owned caller mapping 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 /** Application mapping of freshly verified account facts into canonical trusted context. */\n public readonly caller: AuthenticatedCaller,\n ) {}\n\n /** The canonical application's roles; there is no independent listing-role snapshot. */\n get principalRoles(): readonly string[] {\n return this.caller.roles;\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/** 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. The access-token authority verifies MCP credentials; the authorization service\n * is shared with receiving endpoints and document projection.\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>()\n * .setName('my-server')\n * .setTitle('My Product')\n * .setDescription('Search and manage My Product accounts from any MCP client.')\n * .setWebsiteUrl('https://www.example.com/my-product')\n * .setVersion('1.0.0')\n * .setResource('https://api.example.com/mcp')\n * .setAccessTokenAuthority(authority)\n * .setAuthorizationService(router.authorizationService())\n * .setAuthorizationServers(['https://login.example.com'])\n * .setRequiredScopes(['tools']);\n *\n * // the tools/call error translator is a PROCESS-GLOBAL, not a member of this config:\n * McpRegistry.setErrorTranslator(new MyMcpErrorTranslator());\n * ```\n */\nexport class WpMcpServerConfig<TGrant> {\n private toolProfilesValue: readonly string[] = Object.freeze([Mcp.DEFAULT]);\n private nameValue?: string;\n private titleValue?: string;\n private descriptionValue?: string;\n private websiteUrlValue?: string;\n private versionValue?: string;\n private iconsValue?: readonly McpIcon[];\n private resourceValue?: string;\n private accessTokenAuthorityValue?: McpAccessTokenAuthority<TGrant>;\n private authorizationServiceValue?: AuthorizationService;\n private authorizationServersValue?: readonly string[];\n private requiredScopesValue?: readonly string[];\n private maxAccountValidationAgeSecondsValue = MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS;\n\n /** OPTIONAL. Union of groups exposed by this instance; defaults to [Mcp.DEFAULT]. */\n setToolProfiles(profiles: McpToolProfiles): this {\n this.toolProfilesValue = Mcp.profiles(profiles, 'WpMcpServerConfig.setToolProfiles');\n return this;\n }\n\n get toolProfiles(): readonly string[] {\n return this.toolProfilesValue;\n }\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 /**\n * REQUIRED. The human-readable display name sent as `serverInfo.title` (MCP 2025-06-18), e.g.\n * `'Lang Learning'` where {@link setName} is the programmatic `'lang-learning'`. One line, at most\n * 100 characters — the Claude Connectors Directory's listing Name limit.\n *\n * The app sets it because the server cannot derive it: it does not read the OpenAPI manifest at\n * runtime, and one MCP server may bind contracts from several api libraries.\n */\n setTitle(title: string): this {\n const value = this.requireText(title, 'setTitle');\n if (/[\\r\\n]/.test(value)) {\n throw new Error('WpMcpServerConfig.setTitle(...) requires a single line of text.');\n }\n this.titleValue = this.requireMaxLength(value, MAX_MCP_SERVER_TITLE_LENGTH, 'setTitle');\n return this;\n }\n\n /**\n * REQUIRED. What this server does, sent as `serverInfo.description` (MCP 2025-11-25). At most\n * 2000 characters — the Claude Connectors Directory's listing Description limit.\n */\n setDescription(description: string): this {\n const value = this.requireText(description, 'setDescription');\n this.descriptionValue = this.requireMaxLength(\n value,\n MAX_MCP_SERVER_DESCRIPTION_LENGTH,\n 'setDescription',\n );\n return this;\n }\n\n /**\n * REQUIRED. An absolute `https://` URL of a page a HUMAN reads about this server — the product\n * homepage or its docs site — sent as `serverInfo.websiteUrl` (MCP 2025-11-25).\n *\n * Deliberately NOT derived from {@link setResource}: that is the MCP endpoint, an API host, not a\n * website, and the resource is never derived from request input either. No default, because the\n * page a user is sent to is the app's decision.\n */\n setWebsiteUrl(websiteUrl: string): this {\n const value = this.requireUrl(websiteUrl, 'setWebsiteUrl');\n if (new URL(value).protocol !== 'https:') {\n throw new Error(\n `WpMcpServerConfig.setWebsiteUrl(...) requires an https:// URL, got '${value}'.`,\n );\n }\n this.websiteUrlValue = value;\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 /** OPTIONAL. Standard MCP icons; copied and frozen, with no network access. */\n setIcons(icons: readonly Icon[]): this {\n this.iconsValue = this.snapshotIcons(icons);\n return this;\n }\n\n /** Immutable snapshot; omitted icons remain absent from SDK implementation metadata. */\n get icons(): readonly McpIcon[] | undefined {\n return this.iconsValue;\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 common authorization service for tool projection and invocation. */\n setAuthorizationService(authority: AuthorizationService): this {\n this.authorizationServiceValue = this.requirePresent(authority, 'setAuthorizationService');\n return this;\n }\n\n /**\n * REQUIRED. The trusted token issuers. Every entry must be an absolute URL, because each is\n * compared against a token's `issuer` claim and published as an `authorization_servers` entry —\n * which is what makes a scope list landed here fail at boot rather than on the first request.\n */\n setAuthorizationServers(authorizationServers: readonly string[]): this {\n const servers = this.requireArray(authorizationServers, 'setAuthorizationServers');\n if (servers.length === 0) {\n throw new Error(\n 'WpMcpServerConfig.setAuthorizationServers(...) requires at least one issuer URL.',\n );\n }\n for (const server of servers) this.requireUrl(server, 'setAuthorizationServers');\n this.authorizationServersValue = servers;\n return this;\n }\n\n /**\n * REQUIRED, and empty only by saying so explicitly. Scopes every access token must carry before\n * any tool is dispatched.\n */\n setRequiredScopes(requiredScopes: readonly string[]): this {\n const scopes = this.requireArray(requiredScopes, 'setRequiredScopes');\n for (const scope of scopes) this.requireText(scope, 'setRequiredScopes');\n this.requiredScopesValue = scopes;\n return this;\n }\n\n /** OPTIONAL ceiling, 1 second to 1 hour. Defaults to 1 hour. */\n setMaxAccountValidationAgeSeconds(seconds: number): this {\n this.maxAccountValidationAgeSecondsValue = this.requireSeconds(\n seconds,\n MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS,\n 'setMaxAccountValidationAgeSeconds',\n );\n return this;\n }\n\n /**\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.titleValue === undefined) missing.push('setTitle(...)');\n if (this.descriptionValue === undefined) missing.push('setDescription(...)');\n if (this.websiteUrlValue === undefined) missing.push('setWebsiteUrl(...)');\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.authorizationServiceValue === undefined) {\n missing.push('setAuthorizationService(...)');\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 for (const icon of this.iconsValue ?? []) {\n const url = new URL(icon.src);\n if (url.protocol === 'https:' && url.origin !== new URL(this.resource).origin) {\n throw new Error(\n 'WpMcpServerConfig.setIcons(...) requires HTTPS icons on the resource origin.',\n );\n }\n }\n }\n\n get name(): string {\n return this.read(this.nameValue, 'setName');\n }\n\n get title(): string {\n return this.read(this.titleValue, 'setTitle');\n }\n\n get description(): string {\n return this.read(this.descriptionValue, 'setDescription');\n }\n\n get websiteUrl(): string {\n return this.read(this.websiteUrlValue, 'setWebsiteUrl');\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 authorizationService(): AuthorizationService {\n return this.read(this.authorizationServiceValue, 'setAuthorizationService');\n }\n\n get authorizationServers(): readonly string[] {\n return this.read(this.authorizationServersValue, 'setAuthorizationServers');\n }\n\n get requiredScopes(): readonly string[] {\n return this.read(this.requiredScopesValue, 'setRequiredScopes');\n }\n\n get maxAccountValidationAgeSeconds(): number {\n return this.maxAccountValidationAgeSecondsValue;\n }\n\n /**\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 /** Copies caller-owned descriptors and nested size arrays before freezing the snapshot. */\n private snapshotIcons(icons: readonly Icon[]): readonly McpIcon[] {\n if (!Array.isArray(icons)) {\n throw new Error(\n 'WpMcpServerConfig.setIcons(...) requires an array of icon descriptors.',\n );\n }\n return Object.freeze(\n icons.map((icon: Icon) => {\n if (!icon || typeof icon !== 'object') {\n throw new Error('WpMcpServerConfig.setIcons(...) requires icon descriptors.');\n }\n return new McpIcon(icon.src, icon.mimeType, icon.sizes, icon.theme);\n }),\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 requireMaxLength(value: string, max: number, setter: string): string {\n if (value.length > max) {\n throw new Error(\n `WpMcpServerConfig.${setter}(...) allows at most ${max} characters, got ${value.length}.`,\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"]}
|
package/src/McpToolRegistry.d.ts
CHANGED
|
@@ -7,7 +7,11 @@ export declare class RegisteredMcpTool {
|
|
|
7
7
|
readonly apiClass: ClassType;
|
|
8
8
|
readonly methodName: string;
|
|
9
9
|
readonly name: string;
|
|
10
|
-
/**
|
|
10
|
+
/**
|
|
11
|
+
* The human-readable display name, read from the CATALOG — which the generator wrote from the
|
|
12
|
+
* method's `@summary` JSDoc tag (#1296). Published as BOTH `Tool.title` and `annotations.title`.
|
|
13
|
+
* The contract carries no title at runtime, so there is nothing to compare it with: one source.
|
|
14
|
+
*/
|
|
11
15
|
readonly title: string;
|
|
12
16
|
readonly description: string;
|
|
13
17
|
readonly annotations: WpMcpToolHints;
|
|
@@ -19,12 +23,18 @@ export declare class RegisteredMcpTool {
|
|
|
19
23
|
readonly outputSchema: ApiJsonSchema;
|
|
20
24
|
readonly profiles: readonly string[];
|
|
21
25
|
constructor(apiClass: ClassType, methodName: string, name: string,
|
|
22
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* The human-readable display name, read from the CATALOG — which the generator wrote from the
|
|
28
|
+
* method's `@summary` JSDoc tag (#1296). Published as BOTH `Tool.title` and `annotations.title`.
|
|
29
|
+
* The contract carries no title at runtime, so there is nothing to compare it with: one source.
|
|
30
|
+
*/
|
|
23
31
|
title: string, description: string, annotations: WpMcpToolHints, operation: EndpointOperation, authMeta: AuthMeta, authorization: AuthorizationRequirement<unknown>, binding: McpApiBinding, inputSchema: ApiJsonSchema, outputSchema: ApiJsonSchema, profiles: readonly string[]);
|
|
24
32
|
}
|
|
25
33
|
/**
|
|
26
34
|
* Fail-fast registry: the CONTRACT says which methods are tools and how they are authorized, and the
|
|
27
|
-
* build's generated catalog OF THAT CONTRACT says what each tool's documentation and schemas
|
|
35
|
+
* build's generated catalog OF THAT CONTRACT says what each tool's title, documentation and schemas
|
|
36
|
+
* are. The title and the description are JSDoc (`@summary` and the body), which only the compiler can
|
|
37
|
+
* read, so the catalog is their one runtime source (#1296).
|
|
28
38
|
*
|
|
29
39
|
* ## Why the schemas come from the catalog and not from here
|
|
30
40
|
*
|
package/src/McpToolRegistry.js
CHANGED
|
@@ -19,7 +19,11 @@ class RegisteredMcpTool {
|
|
|
19
19
|
outputSchema;
|
|
20
20
|
profiles;
|
|
21
21
|
constructor(apiClass, methodName, name,
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* The human-readable display name, read from the CATALOG — which the generator wrote from the
|
|
24
|
+
* method's `@summary` JSDoc tag (#1296). Published as BOTH `Tool.title` and `annotations.title`.
|
|
25
|
+
* The contract carries no title at runtime, so there is nothing to compare it with: one source.
|
|
26
|
+
*/
|
|
23
27
|
title, description, annotations, operation, authMeta,
|
|
24
28
|
// webpieces-disable no-any-unknown -- custom policy is validated by AuthorizationService
|
|
25
29
|
authorization, binding, inputSchema, outputSchema, profiles) {
|
|
@@ -41,7 +45,9 @@ class RegisteredMcpTool {
|
|
|
41
45
|
exports.RegisteredMcpTool = RegisteredMcpTool;
|
|
42
46
|
/**
|
|
43
47
|
* Fail-fast registry: the CONTRACT says which methods are tools and how they are authorized, and the
|
|
44
|
-
* build's generated catalog OF THAT CONTRACT says what each tool's documentation and schemas
|
|
48
|
+
* build's generated catalog OF THAT CONTRACT says what each tool's title, documentation and schemas
|
|
49
|
+
* are. The title and the description are JSDoc (`@summary` and the body), which only the compiler can
|
|
50
|
+
* read, so the catalog is their one runtime source (#1296).
|
|
45
51
|
*
|
|
46
52
|
* ## Why the schemas come from the catalog and not from here
|
|
47
53
|
*
|
|
@@ -147,12 +153,6 @@ class McpToolRegistry {
|
|
|
147
153
|
pairing.problem(`MCP tool '${metadata.name}' has stale method/profile metadata in ${catalog.file.fileName}; rebuild the api library.`);
|
|
148
154
|
return undefined;
|
|
149
155
|
}
|
|
150
|
-
if (published.title !== metadata.title) {
|
|
151
|
-
pairing.problem(`@WpMcpTool ${apiClass.name}.${metadata.methodName} is titled '${metadata.title}', but ` +
|
|
152
|
-
`${catalog.file.fileName} (${catalog.directory}) publishes '${metadata.name}' as ` +
|
|
153
|
-
`'${published.title}'. The catalog is older than the contract — rebuild the api library.`);
|
|
154
|
-
return undefined;
|
|
155
|
-
}
|
|
156
156
|
return published;
|
|
157
157
|
}
|
|
158
158
|
}
|