@webpieces/mcp-server 0.4.766 → 0.4.767
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 +11 -3
- package/package.json +4 -4
- package/src/McpAuth.d.ts +48 -16
- package/src/McpAuth.js +78 -16
- package/src/McpAuth.js.map +1 -1
- package/src/McpAuthorityCompileAssertions.d.ts +19 -0
- package/src/McpAuthorityCompileAssertions.js +38 -0
- package/src/McpAuthorityCompileAssertions.js.map +1 -0
- package/src/WpMcpServer.d.ts +7 -5
- package/src/WpMcpServer.js +91 -21
- package/src/WpMcpServer.js.map +1 -1
- package/src/index.d.ts +2 -1
- package/src/index.js +6 -2
- package/src/index.js.map +1 -1
package/README.md
CHANGED
|
@@ -43,9 +43,17 @@ response classes generate `inputSchema` and `outputSchema`; `@WpDtoField` suppli
|
|
|
43
43
|
descriptions and the facts TypeScript erases, such as optionality and array element types.
|
|
44
44
|
|
|
45
45
|
Applications construct `WpMcpServer` with their built `ApiFactory`, the API classes they want scanned,
|
|
46
|
-
and
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
a paired `McpAccessTokenAuthority`, and their application `JwtHook`. The authority verifies issuer,
|
|
47
|
+
signature/token state, expiry, scopes, the exact resource URI, and current account state on every MCP
|
|
48
|
+
operation. Before every tool call the bridge asks the same application `JwtHook` to mint a distinct,
|
|
49
|
+
short-lived endpoint JWT, then invokes the ordinary `AuthFilter`. Literal MCP-token passthrough is
|
|
50
|
+
rejected, endpoint JWTs are capped at one hour, and MCP access tokens are capped at 30 days.
|
|
51
|
+
|
|
52
|
+
The verifier's `accountValidatedAtEpochSeconds` must represent an authoritative enabled/revoked and
|
|
53
|
+
role/scope read. The bridge enforces a maximum one-hour decision age; per-request reads are preferred so
|
|
54
|
+
offboarding and role changes take effect on the next call. JWT access-token implementations must enforce
|
|
55
|
+
an explicit algorithm allowlist plus issuer, exact audience/resource, expiry, type/version, and key
|
|
56
|
+
rotation metadata. Opaque tokens remain valid implementations of the same authority contract.
|
|
49
57
|
|
|
50
58
|
`protectedResourceMetadata()` returns the resource metadata an HTTP adapter can publish at the
|
|
51
59
|
well-known OAuth protected-resource endpoint. OAuth token issuance remains pluggable.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.767",
|
|
4
4
|
"description": "Secure MCP tool generation from annotated Webpieces API contracts",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -23,9 +23,9 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
26
|
-
"@webpieces/core-context": "0.4.
|
|
27
|
-
"@webpieces/core-util": "0.4.
|
|
28
|
-
"@webpieces/http-routing": "0.4.
|
|
26
|
+
"@webpieces/core-context": "0.4.767",
|
|
27
|
+
"@webpieces/core-util": "0.4.767",
|
|
28
|
+
"@webpieces/http-routing": "0.4.767",
|
|
29
29
|
"reflect-metadata": "0.2.2",
|
|
30
30
|
"tslib": "2.8.1"
|
|
31
31
|
}
|
package/src/McpAuth.d.ts
CHANGED
|
@@ -1,26 +1,51 @@
|
|
|
1
|
-
|
|
1
|
+
import { JwtHook } from '@webpieces/http-routing';
|
|
2
|
+
export declare const MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS: number;
|
|
3
|
+
export declare const MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS: number;
|
|
4
|
+
export declare const MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS: number;
|
|
5
|
+
/** Framework-normalized access-token result. The token representation remains application-owned. */
|
|
6
|
+
export declare class MintedMcpAccessToken {
|
|
7
|
+
readonly token: string;
|
|
8
|
+
readonly resource: string;
|
|
9
|
+
readonly issuedAtEpochSeconds: number;
|
|
10
|
+
readonly expiresAtEpochSeconds: number;
|
|
11
|
+
constructor(token: string, resource: string, issuedAtEpochSeconds: number, expiresAtEpochSeconds: number);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Fresh, authoritative security facts resolved at the MCP resource boundary. Implementations must
|
|
15
|
+
* verify issuer, exact resource/audience, expiry, token type/version, key/algorithm policy, and
|
|
16
|
+
* current account state. Opaque access tokens are fully supported.
|
|
17
|
+
*/
|
|
2
18
|
export declare class VerifiedMcpCredential {
|
|
3
|
-
|
|
4
|
-
readonly endpointBearerToken: string;
|
|
19
|
+
readonly subject: string;
|
|
5
20
|
readonly issuer: string;
|
|
6
|
-
/** The RFC 8707 resource/audience the verifier proved from the access token. */
|
|
7
21
|
readonly resource: string;
|
|
22
|
+
readonly issuedAtEpochSeconds: number;
|
|
8
23
|
readonly expiresAtEpochSeconds: number;
|
|
9
24
|
readonly scopes: readonly string[];
|
|
25
|
+
/** When current enabled/revoked state and roles were read from the authoritative source. */
|
|
26
|
+
readonly accountValidatedAtEpochSeconds: number;
|
|
10
27
|
/** Advisory tools/list filtering only. Endpoint authorization always runs again. */
|
|
11
28
|
readonly listingRoles: readonly string[];
|
|
12
|
-
constructor(
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/** The RFC 8707 resource/audience the verifier proved from the access token. */
|
|
16
|
-
resource: string, expiresAtEpochSeconds: number, scopes: readonly string[],
|
|
29
|
+
constructor(subject: string, issuer: string, resource: string, issuedAtEpochSeconds: number, expiresAtEpochSeconds: number, scopes: readonly string[],
|
|
30
|
+
/** When current enabled/revoked state and roles were read from the authoritative source. */
|
|
31
|
+
accountValidatedAtEpochSeconds: number,
|
|
17
32
|
/** Advisory tools/list filtering only. Endpoint authorization always runs again. */
|
|
18
33
|
listingRoles?: readonly string[]);
|
|
19
34
|
}
|
|
20
|
-
/** Application-owned
|
|
21
|
-
export
|
|
22
|
-
|
|
35
|
+
/** Application-owned paired mint/verify authority for resource-bound MCP access tokens. */
|
|
36
|
+
export interface McpAccessTokenAuthority<TGrant> {
|
|
37
|
+
mintAccessToken(grant: TGrant): Promise<MintedMcpAccessToken>;
|
|
38
|
+
verifyAccessToken(accessToken: string, expectedResource: string): Promise<VerifiedMcpCredential>;
|
|
39
|
+
}
|
|
40
|
+
/** Minimal tool identity supplied when the app creates its own endpoint-JWT mint request. */
|
|
41
|
+
export declare class McpEndpointDescriptor {
|
|
42
|
+
readonly toolName: string;
|
|
43
|
+
readonly apiClassName: string;
|
|
44
|
+
readonly methodName: string;
|
|
45
|
+
constructor(toolName: string, apiClassName: string, methodName: string);
|
|
23
46
|
}
|
|
47
|
+
/** Application mapping from verified MCP identity to its unconstrained JwtHook mint request. */
|
|
48
|
+
export type McpEndpointMintRequestFactory<TMintRequest> = (credential: VerifiedMcpCredential, endpoint: McpEndpointDescriptor) => TMintRequest;
|
|
24
49
|
/** RFC 9728-style metadata exposed by the MCP protected resource. */
|
|
25
50
|
export declare class McpProtectedResourceMetadata {
|
|
26
51
|
readonly resource: string;
|
|
@@ -29,14 +54,21 @@ export declare class McpProtectedResourceMetadata {
|
|
|
29
54
|
readonly scopes_supported?: readonly string[];
|
|
30
55
|
constructor(resource: string, authorizationServers: readonly string[], scopesSupported?: readonly string[]);
|
|
31
56
|
}
|
|
32
|
-
/**
|
|
33
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Node MCP server configuration. Pass the same concrete application authority as both
|
|
59
|
+
* accessTokenAuthority and endpointJwtAuthority when it implements both security seams.
|
|
60
|
+
*/
|
|
61
|
+
export declare class WpMcpServerConfig<TGrant, TMintRequest> {
|
|
34
62
|
readonly name: string;
|
|
35
63
|
readonly version: string;
|
|
36
64
|
readonly resource: string;
|
|
37
|
-
readonly
|
|
65
|
+
readonly accessTokenAuthority: McpAccessTokenAuthority<TGrant>;
|
|
66
|
+
readonly endpointJwtAuthority: JwtHook<TMintRequest>;
|
|
67
|
+
readonly endpointMintRequest: McpEndpointMintRequestFactory<TMintRequest>;
|
|
38
68
|
readonly authorizationServers: readonly string[];
|
|
39
69
|
readonly requiredScopes: readonly string[];
|
|
40
|
-
|
|
70
|
+
readonly maxAccountValidationAgeSeconds: number;
|
|
71
|
+
readonly maxEndpointJwtLifetimeSeconds: number;
|
|
72
|
+
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);
|
|
41
73
|
protectedResourceMetadata(): McpProtectedResourceMetadata;
|
|
42
74
|
}
|
package/src/McpAuth.js
CHANGED
|
@@ -1,34 +1,77 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WpMcpServerConfig = exports.McpProtectedResourceMetadata = exports.
|
|
4
|
-
|
|
3
|
+
exports.WpMcpServerConfig = exports.McpProtectedResourceMetadata = exports.McpEndpointDescriptor = exports.VerifiedMcpCredential = exports.MintedMcpAccessToken = exports.MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = void 0;
|
|
4
|
+
exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60;
|
|
5
|
+
exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = 60 * 60;
|
|
6
|
+
exports.MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS = 60 * 60;
|
|
7
|
+
/** Framework-normalized access-token result. The token representation remains application-owned. */
|
|
8
|
+
class MintedMcpAccessToken {
|
|
9
|
+
token;
|
|
10
|
+
resource;
|
|
11
|
+
issuedAtEpochSeconds;
|
|
12
|
+
expiresAtEpochSeconds;
|
|
13
|
+
constructor(token, resource, issuedAtEpochSeconds, expiresAtEpochSeconds) {
|
|
14
|
+
this.token = token;
|
|
15
|
+
this.resource = resource;
|
|
16
|
+
this.issuedAtEpochSeconds = issuedAtEpochSeconds;
|
|
17
|
+
this.expiresAtEpochSeconds = expiresAtEpochSeconds;
|
|
18
|
+
if (token.trim() === '' || resource.trim() === '') {
|
|
19
|
+
throw new Error('Minted MCP access token and resource must be non-empty.');
|
|
20
|
+
}
|
|
21
|
+
if (!Number.isFinite(issuedAtEpochSeconds) || !Number.isFinite(expiresAtEpochSeconds)) {
|
|
22
|
+
throw new Error('Minted MCP access token timestamps must be finite epoch-second values.');
|
|
23
|
+
}
|
|
24
|
+
if (expiresAtEpochSeconds <= issuedAtEpochSeconds) {
|
|
25
|
+
throw new Error('MCP access token expiry must be after issuance.');
|
|
26
|
+
}
|
|
27
|
+
if (expiresAtEpochSeconds - issuedAtEpochSeconds > exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) {
|
|
28
|
+
throw new Error('MCP access token lifetime must not exceed 30 days.');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
exports.MintedMcpAccessToken = MintedMcpAccessToken;
|
|
33
|
+
/**
|
|
34
|
+
* Fresh, authoritative security facts resolved at the MCP resource boundary. Implementations must
|
|
35
|
+
* verify issuer, exact resource/audience, expiry, token type/version, key/algorithm policy, and
|
|
36
|
+
* current account state. Opaque access tokens are fully supported.
|
|
37
|
+
*/
|
|
5
38
|
class VerifiedMcpCredential {
|
|
6
|
-
|
|
39
|
+
subject;
|
|
7
40
|
issuer;
|
|
8
41
|
resource;
|
|
42
|
+
issuedAtEpochSeconds;
|
|
9
43
|
expiresAtEpochSeconds;
|
|
10
44
|
scopes;
|
|
45
|
+
accountValidatedAtEpochSeconds;
|
|
11
46
|
listingRoles;
|
|
12
|
-
constructor(
|
|
13
|
-
/**
|
|
14
|
-
|
|
15
|
-
/** The RFC 8707 resource/audience the verifier proved from the access token. */
|
|
16
|
-
resource, expiresAtEpochSeconds, scopes,
|
|
47
|
+
constructor(subject, issuer, resource, issuedAtEpochSeconds, expiresAtEpochSeconds, scopes,
|
|
48
|
+
/** When current enabled/revoked state and roles were read from the authoritative source. */
|
|
49
|
+
accountValidatedAtEpochSeconds,
|
|
17
50
|
/** Advisory tools/list filtering only. Endpoint authorization always runs again. */
|
|
18
51
|
listingRoles = []) {
|
|
19
|
-
this.
|
|
52
|
+
this.subject = subject;
|
|
20
53
|
this.issuer = issuer;
|
|
21
54
|
this.resource = resource;
|
|
55
|
+
this.issuedAtEpochSeconds = issuedAtEpochSeconds;
|
|
22
56
|
this.expiresAtEpochSeconds = expiresAtEpochSeconds;
|
|
23
57
|
this.scopes = scopes;
|
|
58
|
+
this.accountValidatedAtEpochSeconds = accountValidatedAtEpochSeconds;
|
|
24
59
|
this.listingRoles = listingRoles;
|
|
25
60
|
}
|
|
26
61
|
}
|
|
27
62
|
exports.VerifiedMcpCredential = VerifiedMcpCredential;
|
|
28
|
-
/**
|
|
29
|
-
class
|
|
63
|
+
/** Minimal tool identity supplied when the app creates its own endpoint-JWT mint request. */
|
|
64
|
+
class McpEndpointDescriptor {
|
|
65
|
+
toolName;
|
|
66
|
+
apiClassName;
|
|
67
|
+
methodName;
|
|
68
|
+
constructor(toolName, apiClassName, methodName) {
|
|
69
|
+
this.toolName = toolName;
|
|
70
|
+
this.apiClassName = apiClassName;
|
|
71
|
+
this.methodName = methodName;
|
|
72
|
+
}
|
|
30
73
|
}
|
|
31
|
-
exports.
|
|
74
|
+
exports.McpEndpointDescriptor = McpEndpointDescriptor;
|
|
32
75
|
/** RFC 9728-style metadata exposed by the MCP protected resource. */
|
|
33
76
|
class McpProtectedResourceMetadata {
|
|
34
77
|
resource;
|
|
@@ -42,24 +85,43 @@ class McpProtectedResourceMetadata {
|
|
|
42
85
|
}
|
|
43
86
|
}
|
|
44
87
|
exports.McpProtectedResourceMetadata = McpProtectedResourceMetadata;
|
|
45
|
-
/**
|
|
88
|
+
/**
|
|
89
|
+
* Node MCP server configuration. Pass the same concrete application authority as both
|
|
90
|
+
* accessTokenAuthority and endpointJwtAuthority when it implements both security seams.
|
|
91
|
+
*/
|
|
46
92
|
class WpMcpServerConfig {
|
|
47
93
|
name;
|
|
48
94
|
version;
|
|
49
95
|
resource;
|
|
50
|
-
|
|
96
|
+
accessTokenAuthority;
|
|
97
|
+
endpointJwtAuthority;
|
|
98
|
+
endpointMintRequest;
|
|
51
99
|
authorizationServers;
|
|
52
100
|
requiredScopes;
|
|
53
|
-
|
|
101
|
+
maxAccountValidationAgeSeconds;
|
|
102
|
+
maxEndpointJwtLifetimeSeconds;
|
|
103
|
+
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) {
|
|
54
104
|
this.name = name;
|
|
55
105
|
this.version = version;
|
|
56
106
|
this.resource = resource;
|
|
57
|
-
this.
|
|
107
|
+
this.accessTokenAuthority = accessTokenAuthority;
|
|
108
|
+
this.endpointJwtAuthority = endpointJwtAuthority;
|
|
109
|
+
this.endpointMintRequest = endpointMintRequest;
|
|
58
110
|
this.authorizationServers = authorizationServers;
|
|
59
111
|
this.requiredScopes = requiredScopes;
|
|
112
|
+
this.maxAccountValidationAgeSeconds = maxAccountValidationAgeSeconds;
|
|
113
|
+
this.maxEndpointJwtLifetimeSeconds = maxEndpointJwtLifetimeSeconds;
|
|
60
114
|
if (name.trim() === '' || version.trim() === '' || resource.trim() === '') {
|
|
61
115
|
throw new Error('MCP name, version, and resource must be non-empty.');
|
|
62
116
|
}
|
|
117
|
+
if (maxAccountValidationAgeSeconds <= 0 ||
|
|
118
|
+
maxAccountValidationAgeSeconds > exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS) {
|
|
119
|
+
throw new Error('MCP account validation cache ceiling must be between 1 second and 1 hour.');
|
|
120
|
+
}
|
|
121
|
+
if (maxEndpointJwtLifetimeSeconds <= 0 ||
|
|
122
|
+
maxEndpointJwtLifetimeSeconds > exports.MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS) {
|
|
123
|
+
throw new Error('MCP endpoint JWT lifetime must be between 1 second and 1 hour.');
|
|
124
|
+
}
|
|
63
125
|
}
|
|
64
126
|
protectedResourceMetadata() {
|
|
65
127
|
return new McpProtectedResourceMetadata(this.resource, this.authorizationServers, this.requiredScopes);
|
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":";;;
|
|
1
|
+
{"version":3,"file":"McpAuth.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuth.ts"],"names":[],"mappings":";;;AAEa,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,CAAC,wEAAwE,CAAC,CAAC;QAC9F,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;AApBD,oDAoBC;AAED;;;;GAIG;AACH,MAAa,qBAAqB;IAEV;IACA;IACA;IACA;IACA;IACA;IAEA;IAEA;IAVpB,YACoB,OAAe,EACf,MAAc,EACd,QAAgB,EAChB,oBAA4B,EAC5B,qBAA6B,EAC7B,MAAyB;IACzC,4FAA4F;IAC5E,8BAAsC;IACtD,oFAAoF;IACpE,eAAkC,EAAE;QATpC,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;IACrD,CAAC;CACP;AAbD,sDAaC;AAWD,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,CAAC,2EAA2E,CAAC,CAAC;QACjG,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;AArCD,8CAqCC","sourcesContent":["import { JwtHook } from '@webpieces/http-routing';\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('Minted MCP access token timestamps must be finite epoch-second values.');\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 ) {}\n}\n\n/** Application-owned paired mint/verify authority for resource-bound MCP access tokens. */\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('MCP account validation cache ceiling must be between 1 second and 1 hour.');\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"]}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AuthenticatedCaller, JwtHook, MintedJwt } from '@webpieces/http-routing';
|
|
2
|
+
import { McpAccessTokenAuthority, MintedMcpAccessToken, VerifiedMcpCredential } from './McpAuth';
|
|
3
|
+
declare class ExampleJwtMintRequest {
|
|
4
|
+
readonly subject: string;
|
|
5
|
+
constructor(subject: string);
|
|
6
|
+
}
|
|
7
|
+
declare class ExampleMcpGrant {
|
|
8
|
+
readonly subject: string;
|
|
9
|
+
readonly resource: string;
|
|
10
|
+
constructor(subject: string, resource: string);
|
|
11
|
+
}
|
|
12
|
+
/** Compile assertion: one application authority can own both endpoint JWT and MCP token lifecycles. */
|
|
13
|
+
export declare class McpAuthorityCompileAssertions extends JwtHook<ExampleJwtMintRequest> implements McpAccessTokenAuthority<ExampleMcpGrant> {
|
|
14
|
+
mint(request: ExampleJwtMintRequest): Promise<MintedJwt>;
|
|
15
|
+
parseJwt(token: string): Promise<AuthenticatedCaller>;
|
|
16
|
+
mintAccessToken(grant: ExampleMcpGrant): Promise<MintedMcpAccessToken>;
|
|
17
|
+
verifyAccessToken(_accessToken: string, expectedResource: string): Promise<VerifiedMcpCredential>;
|
|
18
|
+
}
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.McpAuthorityCompileAssertions = void 0;
|
|
4
|
+
const http_routing_1 = require("@webpieces/http-routing");
|
|
5
|
+
const McpAuth_1 = require("./McpAuth");
|
|
6
|
+
class ExampleJwtMintRequest {
|
|
7
|
+
subject;
|
|
8
|
+
constructor(subject) {
|
|
9
|
+
this.subject = subject;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
class ExampleMcpGrant {
|
|
13
|
+
subject;
|
|
14
|
+
resource;
|
|
15
|
+
constructor(subject, resource) {
|
|
16
|
+
this.subject = subject;
|
|
17
|
+
this.resource = resource;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Compile assertion: one application authority can own both endpoint JWT and MCP token lifecycles. */
|
|
21
|
+
class McpAuthorityCompileAssertions extends http_routing_1.JwtHook {
|
|
22
|
+
async mint(request) {
|
|
23
|
+
return new http_routing_1.MintedJwt(`endpoint:${request.subject}`, Date.now() / 1000 + 60);
|
|
24
|
+
}
|
|
25
|
+
async parseJwt(token) {
|
|
26
|
+
return new http_routing_1.AuthenticatedCaller(token);
|
|
27
|
+
}
|
|
28
|
+
async mintAccessToken(grant) {
|
|
29
|
+
const now = Math.floor(Date.now() / 1000);
|
|
30
|
+
return new McpAuth_1.MintedMcpAccessToken(`mcp:${grant.subject}`, grant.resource, now, now + 60);
|
|
31
|
+
}
|
|
32
|
+
async verifyAccessToken(_accessToken, expectedResource) {
|
|
33
|
+
const now = Math.floor(Date.now() / 1000);
|
|
34
|
+
return new McpAuth_1.VerifiedMcpCredential('example-user', 'https://issuer.example.test', expectedResource, now, now + 60, ['tools'], now);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
exports.McpAuthorityCompileAssertions = McpAuthorityCompileAssertions;
|
|
38
|
+
//# sourceMappingURL=McpAuthorityCompileAssertions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpAuthorityCompileAssertions.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpAuthorityCompileAssertions.ts"],"names":[],"mappings":";;;AAAA,0DAIiC;AACjC,uCAImB;AAEnB,MAAM,qBAAqB;IACK;IAA5B,YAA4B,OAAe;QAAf,YAAO,GAAP,OAAO,CAAQ;IAAG,CAAC;CAClD;AAED,MAAM,eAAe;IACW;IAAiC;IAA7D,YAA4B,OAAe,EAAkB,QAAgB;QAAjD,YAAO,GAAP,OAAO,CAAQ;QAAkB,aAAQ,GAAR,QAAQ,CAAQ;IAAG,CAAC;CACpF;AAED,uGAAuG;AACvG,MAAa,6BAA8B,SAAQ,sBAA8B;IAGpE,KAAK,CAAC,IAAI,CAAC,OAA8B;QAC9C,OAAO,IAAI,wBAAS,CAAC,YAAY,OAAO,CAAC,OAAO,EAAE,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,GAAG,EAAE,CAAC,CAAC;IAChF,CAAC;IAEQ,KAAK,CAAC,QAAQ,CAAC,KAAa;QACjC,OAAO,IAAI,kCAAmB,CAAC,KAAK,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,eAAe,CAAC,KAAsB;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,OAAO,IAAI,8BAAoB,CAAC,OAAO,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC;IAC3F,CAAC;IAED,KAAK,CAAC,iBAAiB,CACnB,YAAoB,EACpB,gBAAwB;QAExB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;QAC1C,OAAO,IAAI,+BAAqB,CAC5B,cAAc,EACd,6BAA6B,EAC7B,gBAAgB,EAChB,GAAG,EACH,GAAG,GAAG,EAAE,EACR,CAAC,OAAO,CAAC,EACT,GAAG,CACN,CAAC;IACN,CAAC;CACJ;AA/BD,sEA+BC","sourcesContent":["import {\n AuthenticatedCaller,\n JwtHook,\n MintedJwt,\n} from '@webpieces/http-routing';\nimport {\n McpAccessTokenAuthority,\n MintedMcpAccessToken,\n VerifiedMcpCredential,\n} from './McpAuth';\n\nclass ExampleJwtMintRequest {\n constructor(public readonly subject: string) {}\n}\n\nclass ExampleMcpGrant {\n constructor(public readonly subject: string, public readonly resource: string) {}\n}\n\n/** Compile assertion: one application authority can own both endpoint JWT and MCP token lifecycles. */\nexport class McpAuthorityCompileAssertions extends JwtHook<ExampleJwtMintRequest>\n implements McpAccessTokenAuthority<ExampleMcpGrant>\n{\n override async mint(request: ExampleJwtMintRequest): Promise<MintedJwt> {\n return new MintedJwt(`endpoint:${request.subject}`, Date.now() / 1000 + 60);\n }\n\n override async parseJwt(token: string): Promise<AuthenticatedCaller> {\n return new AuthenticatedCaller(token);\n }\n\n async mintAccessToken(grant: ExampleMcpGrant): Promise<MintedMcpAccessToken> {\n const now = Math.floor(Date.now() / 1000);\n return new MintedMcpAccessToken(`mcp:${grant.subject}`, grant.resource, now, now + 60);\n }\n\n async verifyAccessToken(\n _accessToken: string,\n expectedResource: string,\n ): Promise<VerifiedMcpCredential> {\n const now = Math.floor(Date.now() / 1000);\n return new VerifiedMcpCredential(\n 'example-user',\n 'https://issuer.example.test',\n expectedResource,\n now,\n now + 60,\n ['tools'],\n now,\n );\n }\n}\n"]}
|
package/src/WpMcpServer.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
2
|
import { ApiFactory, ClassType } from '@webpieces/http-routing';
|
|
3
|
-
import { WpMcpServerConfig } from './McpAuth';
|
|
3
|
+
import { McpProtectedResourceMetadata, WpMcpServerConfig } from './McpAuth';
|
|
4
4
|
/** The only error shape rendered into model-visible MCP content. */
|
|
5
5
|
export declare class ModelVisibleToolError {
|
|
6
6
|
readonly kind: string;
|
|
@@ -16,16 +16,18 @@ export declare class ModelVisibleToolError {
|
|
|
16
16
|
* Node-only MCP adapter. Build one Server per authenticated transport/session; every tool call still
|
|
17
17
|
* re-enters the endpoint's ordinary AuthFilter, so tools/list visibility never grants access.
|
|
18
18
|
*/
|
|
19
|
-
export declare class WpMcpServer {
|
|
19
|
+
export declare class WpMcpServer<TGrant, TMintRequest> {
|
|
20
20
|
private readonly config;
|
|
21
21
|
private readonly registry;
|
|
22
22
|
private readonly dispatcher;
|
|
23
|
-
constructor(config: WpMcpServerConfig, apiFactory: ApiFactory, apiClasses: readonly ClassType[]);
|
|
23
|
+
constructor(config: WpMcpServerConfig<TGrant, TMintRequest>, apiFactory: ApiFactory, apiClasses: readonly ClassType[]);
|
|
24
24
|
build(accessToken: string): Promise<Server>;
|
|
25
|
-
protectedResourceMetadata():
|
|
26
|
-
private
|
|
25
|
+
protectedResourceMetadata(): McpProtectedResourceMetadata;
|
|
26
|
+
private buildForAccessToken;
|
|
27
|
+
private verifyCredential;
|
|
27
28
|
private validateCredential;
|
|
28
29
|
private call;
|
|
30
|
+
private validateEndpointJwt;
|
|
29
31
|
private apiError;
|
|
30
32
|
private errorResult;
|
|
31
33
|
private record;
|
package/src/WpMcpServer.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.WpMcpServer = exports.ModelVisibleToolError = void 0;
|
|
|
4
4
|
const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
|
|
5
5
|
const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
|
|
6
6
|
const core_util_1 = require("@webpieces/core-util");
|
|
7
|
+
const McpAuth_1 = require("./McpAuth");
|
|
7
8
|
const McpApiDispatcher_1 = require("./McpApiDispatcher");
|
|
8
9
|
const McpToolRegistry_1 = require("./McpToolRegistry");
|
|
9
10
|
const log = core_util_1.LogManager.getLogger('WpMcpServer');
|
|
@@ -41,60 +42,116 @@ class WpMcpServer {
|
|
|
41
42
|
this.dispatcher = new McpApiDispatcher_1.McpApiDispatcher(apiFactory);
|
|
42
43
|
}
|
|
43
44
|
async build(accessToken) {
|
|
44
|
-
|
|
45
|
-
this.
|
|
46
|
-
return this.buildForCredential(credential);
|
|
45
|
+
await this.verifyCredential(accessToken);
|
|
46
|
+
return this.buildForAccessToken(accessToken);
|
|
47
47
|
}
|
|
48
48
|
protectedResourceMetadata() {
|
|
49
49
|
return this.config.protectedResourceMetadata();
|
|
50
50
|
}
|
|
51
|
-
|
|
51
|
+
buildForAccessToken(accessToken) {
|
|
52
52
|
const server = new index_js_1.Server(
|
|
53
53
|
// webpieces-disable no-anonymous-object-literals -- external MCP SDK request structure
|
|
54
54
|
{ name: this.config.name, version: this.config.version },
|
|
55
55
|
// webpieces-disable no-anonymous-object-literals -- external MCP SDK capability structure
|
|
56
56
|
{ capabilities: { tools: {} } });
|
|
57
|
-
server.setRequestHandler(types_js_1.ListToolsRequestSchema, () =>
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
57
|
+
server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
|
|
58
|
+
const credential = await this.verifyCredential(accessToken);
|
|
59
|
+
return {
|
|
60
|
+
tools: this.registry.tools
|
|
61
|
+
.filter((tool) => tool.isVisibleTo(credential.listingRoles))
|
|
62
|
+
.map((tool) => ({
|
|
63
|
+
name: tool.name,
|
|
64
|
+
description: tool.description,
|
|
65
|
+
inputSchema: tool.inputSchema,
|
|
66
|
+
outputSchema: tool.outputSchema,
|
|
67
|
+
annotations: tool.annotations,
|
|
68
|
+
})),
|
|
69
|
+
};
|
|
70
|
+
});
|
|
68
71
|
server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
|
|
69
72
|
const tool = this.registry.find(request.params.name);
|
|
70
73
|
if (!tool) {
|
|
71
74
|
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);
|
|
72
75
|
}
|
|
73
|
-
return this.call(tool, request.params.arguments ?? {},
|
|
76
|
+
return this.call(tool, request.params.arguments ?? {}, accessToken);
|
|
74
77
|
});
|
|
75
78
|
return server;
|
|
76
79
|
}
|
|
80
|
+
async verifyCredential(accessToken) {
|
|
81
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- security boundary logs owner detail and exposes only a generic authentication failure
|
|
82
|
+
try {
|
|
83
|
+
const credential = await this.config.accessTokenAuthority.verifyAccessToken(accessToken, this.config.resource);
|
|
84
|
+
this.validateCredential(credential);
|
|
85
|
+
return credential;
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
const error = (0, core_util_1.toError)(err);
|
|
89
|
+
log.warn('MCP access-token verification rejected a request', error);
|
|
90
|
+
throw new core_util_1.ApiUnauthorizedError('MCP access token rejected.', undefined, error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
77
93
|
validateCredential(credential) {
|
|
94
|
+
const now = Math.floor(Date.now() / 1000);
|
|
95
|
+
if (credential.subject.trim() === '') {
|
|
96
|
+
throw new Error('MCP access token has no subject.');
|
|
97
|
+
}
|
|
98
|
+
if (!Number.isFinite(credential.issuedAtEpochSeconds) ||
|
|
99
|
+
!Number.isFinite(credential.expiresAtEpochSeconds) ||
|
|
100
|
+
!Number.isFinite(credential.accountValidatedAtEpochSeconds)) {
|
|
101
|
+
throw new Error('MCP access token security timestamps must be finite.');
|
|
102
|
+
}
|
|
78
103
|
if (credential.resource !== this.config.resource) {
|
|
79
104
|
throw new Error('MCP access token was not issued for this protected resource.');
|
|
80
105
|
}
|
|
81
106
|
if (!this.config.authorizationServers.includes(credential.issuer)) {
|
|
82
107
|
throw new Error('MCP access token issuer is not trusted by this protected resource.');
|
|
83
108
|
}
|
|
84
|
-
if (credential.
|
|
109
|
+
if (credential.issuedAtEpochSeconds > now) {
|
|
110
|
+
throw new Error('MCP access token was issued in the future.');
|
|
111
|
+
}
|
|
112
|
+
if (credential.expiresAtEpochSeconds <= now) {
|
|
85
113
|
throw new Error('MCP access token has expired.');
|
|
86
114
|
}
|
|
115
|
+
if (credential.expiresAtEpochSeconds - credential.issuedAtEpochSeconds >
|
|
116
|
+
McpAuth_1.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS) {
|
|
117
|
+
throw new Error('MCP access token lifetime exceeds 30 days.');
|
|
118
|
+
}
|
|
87
119
|
for (const scope of this.config.requiredScopes) {
|
|
88
120
|
if (!credential.scopes.includes(scope)) {
|
|
89
121
|
throw new Error(`MCP access token is missing required scope '${scope}'.`);
|
|
90
122
|
}
|
|
91
123
|
}
|
|
92
|
-
if (credential.
|
|
93
|
-
|
|
124
|
+
if (credential.accountValidatedAtEpochSeconds > now ||
|
|
125
|
+
now - credential.accountValidatedAtEpochSeconds > this.config.maxAccountValidationAgeSeconds) {
|
|
126
|
+
throw new Error('MCP account authorization state is not fresh enough for dispatch.');
|
|
94
127
|
}
|
|
95
128
|
}
|
|
96
|
-
async call(tool, args,
|
|
97
|
-
|
|
129
|
+
async call(tool, args, accessToken) {
|
|
130
|
+
let credential;
|
|
131
|
+
// webpieces-disable no-unmanaged-exceptions -- MCP transport boundary normalizes authentication failures before they reach the model
|
|
132
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP transport boundary
|
|
133
|
+
try {
|
|
134
|
+
credential = await this.verifyCredential(accessToken);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
//const error = toError(err);
|
|
138
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InvalidRequest, 'Unauthorized');
|
|
139
|
+
}
|
|
140
|
+
let endpointJwt;
|
|
141
|
+
// webpieces-disable no-unmanaged-exceptions -- MCP transport boundary logs mint failures and exposes only a generic protocol error
|
|
142
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP transport boundary
|
|
143
|
+
try {
|
|
144
|
+
const descriptor = new McpAuth_1.McpEndpointDescriptor(tool.name, tool.apiClass.name, tool.methodName);
|
|
145
|
+
const mintRequest = this.config.endpointMintRequest(credential, descriptor);
|
|
146
|
+
endpointJwt = await this.config.endpointJwtAuthority.mint(mintRequest);
|
|
147
|
+
this.validateEndpointJwt(endpointJwt, accessToken);
|
|
148
|
+
}
|
|
149
|
+
catch (err) {
|
|
150
|
+
const error = (0, core_util_1.toError)(err);
|
|
151
|
+
log.error(`MCP endpoint credential mint failed for ${tool.apiClass.name}.${tool.methodName}`, error);
|
|
152
|
+
throw new types_js_1.McpError(types_js_1.ErrorCode.InternalError, 'Internal Error');
|
|
153
|
+
}
|
|
154
|
+
const result = await this.dispatcher.call(tool, args ?? {}, endpointJwt.token);
|
|
98
155
|
if (!result.success)
|
|
99
156
|
return this.apiError(result.error, result.requestId);
|
|
100
157
|
const outputFailure = this.registry.schemaBuilder.validate(tool.responseClass, result.value);
|
|
@@ -117,6 +174,19 @@ class WpMcpServer {
|
|
|
117
174
|
structuredContent: structured,
|
|
118
175
|
};
|
|
119
176
|
}
|
|
177
|
+
validateEndpointJwt(endpointJwt, accessToken) {
|
|
178
|
+
const now = Math.floor(Date.now() / 1000);
|
|
179
|
+
if (endpointJwt.token === accessToken) {
|
|
180
|
+
throw new Error('MCP access-token passthrough to endpoint dispatch is forbidden.');
|
|
181
|
+
}
|
|
182
|
+
if (endpointJwt.expiresAtEpochSeconds <= now) {
|
|
183
|
+
throw new Error('MCP endpoint JWT has already expired.');
|
|
184
|
+
}
|
|
185
|
+
if (endpointJwt.expiresAtEpochSeconds - now >
|
|
186
|
+
this.config.maxEndpointJwtLifetimeSeconds) {
|
|
187
|
+
throw new Error('MCP endpoint JWT lifetime exceeds the configured one-hour ceiling.');
|
|
188
|
+
}
|
|
189
|
+
}
|
|
120
190
|
apiError(payload, requestId) {
|
|
121
191
|
return this.errorResult(new ModelVisibleToolError(payload.kind, payload.message, requestId, payload.field, payload.callerMessage, payload.errorCode, payload.retryAfterSeconds));
|
|
122
192
|
}
|
package/src/WpMcpServer.js.map
CHANGED
|
@@ -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 { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequest,\n CallToolRequestSchema,\n CallToolResult,\n ErrorCode,\n ListToolsRequestSchema,\n McpError,\n} from '@modelcontextprotocol/sdk/types.js';\nimport {\n ApiErrorPayload,\n DtoValue,\n LogManager,\n toError,\n} from '@webpieces/core-util';\nimport { ApiFactory, ClassType } from '@webpieces/http-routing';\nimport { VerifiedMcpCredential, WpMcpServerConfig } from './McpAuth';\nimport { McpApiDispatcher } from './McpApiDispatcher';\nimport { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';\n\nconst log = LogManager.getLogger('WpMcpServer');\n\n/** The only error shape rendered into model-visible MCP 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 ) {}\n}\n\n/**\n * Node-only MCP adapter. Build one Server per authenticated transport/session; every tool call still\n * re-enters the endpoint's ordinary AuthFilter, so tools/list visibility never grants access.\n */\nexport class WpMcpServer {\n private readonly registry: McpToolRegistry;\n private readonly dispatcher: McpApiDispatcher;\n\n constructor(\n private readonly config: WpMcpServerConfig,\n apiFactory: ApiFactory,\n apiClasses: readonly ClassType[],\n ) {\n this.registry = new McpToolRegistry(apiClasses);\n this.dispatcher = new McpApiDispatcher(apiFactory);\n }\n\n async build(accessToken: string): Promise<Server> {\n const credential = await this.config.tokenVerifier.verify(\n accessToken,\n this.config.resource,\n );\n this.validateCredential(credential);\n return this.buildForCredential(credential);\n }\n\n protectedResourceMetadata(): ReturnType<WpMcpServerConfig['protectedResourceMetadata']> {\n return this.config.protectedResourceMetadata();\n }\n\n private buildForCredential(credential: VerifiedMcpCredential): Server {\n const server = new Server(\n // webpieces-disable no-anonymous-object-literals -- external MCP SDK request structure\n { name: this.config.name, version: this.config.version },\n // webpieces-disable no-anonymous-object-literals -- external MCP SDK capability structure\n { capabilities: { tools: {} } },\n );\n server.setRequestHandler(ListToolsRequestSchema, () => ({\n tools: this.registry.tools\n .filter((tool: RegisteredMcpTool) => tool.isVisibleTo(credential.listingRoles))\n .map((tool: RegisteredMcpTool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n annotations: tool.annotations,\n })),\n }));\n server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {\n const tool = this.registry.find(request.params.name);\n if (!tool) {\n throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);\n }\n return this.call(tool, request.params.arguments ?? {}, credential);\n });\n return server;\n }\n\n private validateCredential(credential: VerifiedMcpCredential): void {\n if (credential.resource !== this.config.resource) {\n throw new Error('MCP access token was not issued for this protected resource.');\n }\n if (!this.config.authorizationServers.includes(credential.issuer)) {\n throw new Error('MCP access token issuer is not trusted by this protected resource.');\n }\n if (credential.expiresAtEpochSeconds <= Date.now() / 1000) {\n throw new Error('MCP access token has expired.');\n }\n for (const scope of this.config.requiredScopes) {\n if (!credential.scopes.includes(scope)) {\n throw new Error(`MCP access token is missing required scope '${scope}'.`);\n }\n }\n if (credential.endpointBearerToken.trim() === '') {\n throw new Error('MCP verifier returned an empty endpoint bearer credential.');\n }\n }\n\n private async call(\n tool: RegisteredMcpTool,\n args: DtoValue,\n credential: VerifiedMcpCredential,\n ): Promise<CallToolResult> {\n const result = await this.dispatcher.call(tool, args ?? {}, credential.endpointBearerToken);\n if (!result.success) return this.apiError(result.error, result.requestId);\n\n const outputFailure = this.registry.schemaBuilder.validate(tool.responseClass, result.value);\n if (outputFailure) {\n log.error(\n `MCP output schema violation for ${tool.apiClass.name}.${tool.methodName}: ${outputFailure.message}`,\n );\n return this.errorResult(\n new ModelVisibleToolError('implementation', 'Internal Error', result.requestId),\n );\n }\n let structured: Record<string, DtoValue>;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- transport boundary sanitizes serialization failures\n try {\n structured = this.record(result.value);\n } catch (err: unknown) {\n const error = toError(err);\n log.error(\n `MCP response serialization failed for ${tool.apiClass.name}.${tool.methodName}`,\n error,\n );\n return this.errorResult(\n new ModelVisibleToolError('implementation', 'Internal Error', result.requestId),\n );\n }\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n structuredContent: structured,\n };\n }\n\n private apiError(payload: ApiErrorPayload, requestId: string): CallToolResult {\n return this.errorResult(\n new ModelVisibleToolError(\n payload.kind,\n payload.message,\n requestId,\n payload.field,\n payload.callerMessage,\n payload.errorCode,\n payload.retryAfterSeconds,\n ),\n );\n }\n\n private errorResult(error: ModelVisibleToolError): CallToolResult {\n const structured = this.record(error);\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n structuredContent: structured,\n isError: true,\n };\n }\n\n private record(value: DtoValue): Record<string, DtoValue> {\n const json = JSON.stringify(value);\n if (json === undefined) throw new Error('MCP structured content is not JSON serializable.');\n const parsed = JSON.parse(json) as DtoValue;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n // webpieces-disable no-anonymous-object-literals -- external MCP structured-content wrapper\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 { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport {\n CallToolRequest,\n CallToolRequestSchema,\n CallToolResult,\n ErrorCode,\n ListToolsRequestSchema,\n McpError,\n} from '@modelcontextprotocol/sdk/types.js';\nimport {\n ApiErrorPayload,\n ApiUnauthorizedError,\n DtoValue,\n LogManager,\n toError,\n} from '@webpieces/core-util';\nimport { ApiFactory, ClassType, 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 { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';\n\nconst log = LogManager.getLogger('WpMcpServer');\n\n/** The only error shape rendered into model-visible MCP 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 ) {}\n}\n\n/**\n * Node-only MCP adapter. Build one Server per authenticated transport/session; every tool call still\n * re-enters the endpoint's ordinary AuthFilter, so tools/list visibility never grants access.\n */\nexport class WpMcpServer<TGrant, TMintRequest> {\n private readonly registry: McpToolRegistry;\n private readonly dispatcher: McpApiDispatcher;\n\n constructor(\n private readonly config: WpMcpServerConfig<TGrant, TMintRequest>,\n apiFactory: ApiFactory,\n apiClasses: readonly ClassType[],\n ) {\n this.registry = new McpToolRegistry(apiClasses);\n this.dispatcher = new McpApiDispatcher(apiFactory);\n }\n\n async build(accessToken: string): Promise<Server> {\n await this.verifyCredential(accessToken);\n return this.buildForAccessToken(accessToken);\n }\n\n protectedResourceMetadata(): McpProtectedResourceMetadata {\n return this.config.protectedResourceMetadata();\n }\n\n private buildForAccessToken(accessToken: string): Server {\n const server = new Server(\n // webpieces-disable no-anonymous-object-literals -- external MCP SDK request structure\n { name: this.config.name, version: this.config.version },\n // webpieces-disable no-anonymous-object-literals -- external MCP SDK capability structure\n { capabilities: { tools: {} } },\n );\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const credential = await this.verifyCredential(accessToken);\n return {\n tools: this.registry.tools\n .filter((tool: RegisteredMcpTool) => tool.isVisibleTo(credential.listingRoles))\n .map((tool: RegisteredMcpTool) => ({\n name: tool.name,\n description: tool.description,\n inputSchema: tool.inputSchema,\n outputSchema: tool.outputSchema,\n annotations: tool.annotations,\n })),\n };\n });\n server.setRequestHandler(CallToolRequestSchema, async (request: CallToolRequest) => {\n const tool = this.registry.find(request.params.name);\n if (!tool) {\n throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${request.params.name}`);\n }\n return this.call(tool, request.params.arguments ?? {}, accessToken);\n });\n return server;\n }\n\n private async verifyCredential(accessToken: string): Promise<VerifiedMcpCredential> {\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- security boundary logs owner detail and exposes only a generic authentication failure\n try {\n const credential = await this.config.accessTokenAuthority.verifyAccessToken(\n accessToken,\n this.config.resource,\n );\n this.validateCredential(credential);\n return credential;\n } catch (err: unknown) {\n const error = toError(err);\n log.warn('MCP access-token verification rejected a request', error);\n throw new ApiUnauthorizedError('MCP access token rejected.', undefined, error);\n }\n }\n\n private validateCredential(credential: VerifiedMcpCredential): void {\n const now = Math.floor(Date.now() / 1000);\n if (credential.subject.trim() === '') {\n throw new Error('MCP access token has no subject.');\n }\n if (\n !Number.isFinite(credential.issuedAtEpochSeconds) ||\n !Number.isFinite(credential.expiresAtEpochSeconds) ||\n !Number.isFinite(credential.accountValidatedAtEpochSeconds)\n ) {\n throw new Error('MCP access token security timestamps must be finite.');\n }\n if (credential.resource !== this.config.resource) {\n throw new Error('MCP access token was not issued for this protected resource.');\n }\n if (!this.config.authorizationServers.includes(credential.issuer)) {\n throw new Error('MCP access token issuer is not trusted by this protected resource.');\n }\n if (credential.issuedAtEpochSeconds > now) {\n throw new Error('MCP access token was issued in the future.');\n }\n if (credential.expiresAtEpochSeconds <= now) {\n throw new Error('MCP access token has expired.');\n }\n if (\n credential.expiresAtEpochSeconds - credential.issuedAtEpochSeconds >\n MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS\n ) {\n throw new Error('MCP access token lifetime exceeds 30 days.');\n }\n for (const scope of this.config.requiredScopes) {\n if (!credential.scopes.includes(scope)) {\n throw new Error(`MCP access token is missing required scope '${scope}'.`);\n }\n }\n if (\n credential.accountValidatedAtEpochSeconds > now ||\n now - credential.accountValidatedAtEpochSeconds > this.config.maxAccountValidationAgeSeconds\n ) {\n throw new Error('MCP account authorization state is not fresh enough for dispatch.');\n }\n }\n\n private async call(\n tool: RegisteredMcpTool,\n args: DtoValue,\n accessToken: string,\n ): Promise<CallToolResult> {\n let credential: VerifiedMcpCredential;\n // webpieces-disable no-unmanaged-exceptions -- MCP transport boundary normalizes authentication failures before they reach the model\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP transport boundary\n try {\n credential = await this.verifyCredential(accessToken);\n } catch (err: unknown) {\n //const error = toError(err);\n throw new McpError(ErrorCode.InvalidRequest, 'Unauthorized');\n }\n let endpointJwt: MintedJwt;\n // webpieces-disable no-unmanaged-exceptions -- MCP transport boundary logs mint failures and exposes only a generic protocol error\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- MCP transport boundary\n try {\n const descriptor = new McpEndpointDescriptor(\n tool.name,\n tool.apiClass.name,\n tool.methodName,\n );\n const mintRequest = this.config.endpointMintRequest(credential, descriptor);\n endpointJwt = await this.config.endpointJwtAuthority.mint(mintRequest);\n this.validateEndpointJwt(endpointJwt, accessToken);\n } catch (err: unknown) {\n const error = toError(err);\n log.error(\n `MCP endpoint credential mint failed for ${tool.apiClass.name}.${tool.methodName}`,\n error,\n );\n throw new McpError(ErrorCode.InternalError, 'Internal Error');\n }\n const result = await this.dispatcher.call(tool, args ?? {}, endpointJwt.token);\n if (!result.success) return this.apiError(result.error, result.requestId);\n\n const outputFailure = this.registry.schemaBuilder.validate(tool.responseClass, result.value);\n if (outputFailure) {\n log.error(\n `MCP output schema violation for ${tool.apiClass.name}.${tool.methodName}: ${outputFailure.message}`,\n );\n return this.errorResult(\n new ModelVisibleToolError('implementation', 'Internal Error', result.requestId),\n );\n }\n let structured: Record<string, DtoValue>;\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions -- transport boundary sanitizes serialization failures\n try {\n structured = this.record(result.value);\n } catch (err: unknown) {\n const error = toError(err);\n log.error(\n `MCP response serialization failed for ${tool.apiClass.name}.${tool.methodName}`,\n error,\n );\n return this.errorResult(\n new ModelVisibleToolError('implementation', 'Internal Error', result.requestId),\n );\n }\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n structuredContent: structured,\n };\n }\n\n private validateEndpointJwt(endpointJwt: MintedJwt, accessToken: string): void {\n const now = Math.floor(Date.now() / 1000);\n if (endpointJwt.token === accessToken) {\n throw new Error('MCP access-token passthrough to endpoint dispatch is forbidden.');\n }\n if (endpointJwt.expiresAtEpochSeconds <= now) {\n throw new Error('MCP endpoint JWT has already expired.');\n }\n if (\n endpointJwt.expiresAtEpochSeconds - now >\n this.config.maxEndpointJwtLifetimeSeconds\n ) {\n throw new Error('MCP endpoint JWT lifetime exceeds the configured one-hour ceiling.');\n }\n }\n\n private apiError(payload: ApiErrorPayload, requestId: string): CallToolResult {\n return this.errorResult(\n new ModelVisibleToolError(\n payload.kind,\n payload.message,\n requestId,\n payload.field,\n payload.callerMessage,\n payload.errorCode,\n payload.retryAfterSeconds,\n ),\n );\n }\n\n private errorResult(error: ModelVisibleToolError): CallToolResult {\n const structured = this.record(error);\n return {\n content: [{ type: 'text', text: JSON.stringify(structured) }],\n structuredContent: structured,\n isError: true,\n };\n }\n\n private record(value: DtoValue): Record<string, DtoValue> {\n const json = JSON.stringify(value);\n if (json === undefined) throw new Error('MCP structured content is not JSON serializable.');\n const parsed = JSON.parse(json) as DtoValue;\n if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n // webpieces-disable no-anonymous-object-literals -- external MCP structured-content wrapper\n return { value: parsed };\n }\n return parsed as Record<string, DtoValue>;\n }\n}\n"]}
|
package/src/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS, MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS, MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS, McpEndpointDescriptor, MintedMcpAccessToken, McpProtectedResourceMetadata, VerifiedMcpCredential, WpMcpServerConfig, } from './McpAuth';
|
|
2
|
+
export type { McpAccessTokenAuthority, McpEndpointMintRequestFactory } from './McpAuth';
|
|
2
3
|
export { McpToolRegistry, RegisteredMcpTool } from './McpToolRegistry';
|
|
3
4
|
export { McpApiDispatcher, McpDispatchFailure, McpDispatchSuccess, } from './McpApiDispatcher';
|
|
4
5
|
export type { McpDispatchResult } from './McpApiDispatcher';
|
package/src/index.js
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WpMcpServer = exports.ModelVisibleToolError = exports.McpDispatchSuccess = exports.McpDispatchFailure = exports.McpApiDispatcher = exports.RegisteredMcpTool = exports.McpToolRegistry = exports.WpMcpServerConfig = exports.VerifiedMcpCredential = exports.McpProtectedResourceMetadata = exports.
|
|
3
|
+
exports.WpMcpServer = exports.ModelVisibleToolError = exports.McpDispatchSuccess = exports.McpDispatchFailure = exports.McpApiDispatcher = exports.RegisteredMcpTool = exports.McpToolRegistry = exports.WpMcpServerConfig = exports.VerifiedMcpCredential = exports.McpProtectedResourceMetadata = exports.MintedMcpAccessToken = exports.McpEndpointDescriptor = exports.MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS = exports.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS = exports.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS = void 0;
|
|
4
4
|
var McpAuth_1 = require("./McpAuth");
|
|
5
|
-
Object.defineProperty(exports, "
|
|
5
|
+
Object.defineProperty(exports, "MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS", { enumerable: true, get: function () { return McpAuth_1.MAX_MCP_ACCESS_TOKEN_LIFETIME_SECONDS; } });
|
|
6
|
+
Object.defineProperty(exports, "MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS", { enumerable: true, get: function () { return McpAuth_1.MAX_MCP_ACCOUNT_VALIDATION_AGE_SECONDS; } });
|
|
7
|
+
Object.defineProperty(exports, "MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS", { enumerable: true, get: function () { return McpAuth_1.MAX_MCP_ENDPOINT_JWT_LIFETIME_SECONDS; } });
|
|
8
|
+
Object.defineProperty(exports, "McpEndpointDescriptor", { enumerable: true, get: function () { return McpAuth_1.McpEndpointDescriptor; } });
|
|
9
|
+
Object.defineProperty(exports, "MintedMcpAccessToken", { enumerable: true, get: function () { return McpAuth_1.MintedMcpAccessToken; } });
|
|
6
10
|
Object.defineProperty(exports, "McpProtectedResourceMetadata", { enumerable: true, get: function () { return McpAuth_1.McpProtectedResourceMetadata; } });
|
|
7
11
|
Object.defineProperty(exports, "VerifiedMcpCredential", { enumerable: true, get: function () { return McpAuth_1.VerifiedMcpCredential; } });
|
|
8
12
|
Object.defineProperty(exports, "WpMcpServerConfig", { enumerable: true, get: function () { return McpAuth_1.WpMcpServerConfig; } });
|
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,
|
|
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,uDAI4B;AAHxB,oHAAA,gBAAgB,OAAA;AAChB,sHAAA,kBAAkB,OAAA;AAClB,sHAAA,kBAAkB,OAAA;AAGtB,6CAAmE;AAA1D,oHAAA,qBAAqB,OAAA;AAAE,0GAAA,WAAW,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 {\n McpApiDispatcher,\n McpDispatchFailure,\n McpDispatchSuccess,\n} from './McpApiDispatcher';\nexport type { McpDispatchResult } from './McpApiDispatcher';\nexport { ModelVisibleToolError, WpMcpServer } from './WpMcpServer';\n"]}
|