@webpieces/mcp-server 0.4.772 → 0.4.774
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 +114 -8
- package/package.json +11 -5
- package/src/McpApiBinding.d.ts +14 -0
- package/src/McpApiBinding.js +42 -0
- package/src/McpApiBinding.js.map +1 -0
- package/src/McpApiDispatcher.d.ts +10 -19
- package/src/McpApiDispatcher.js +35 -44
- package/src/McpApiDispatcher.js.map +1 -1
- package/src/McpAuth.d.ts +11 -2
- package/src/McpAuth.js +5 -1
- package/src/McpAuth.js.map +1 -1
- package/src/McpAuthorityCompileAssertions.js.map +1 -1
- package/src/McpBindOptions.d.ts +17 -0
- package/src/McpBindOptions.js +36 -0
- package/src/McpBindOptions.js.map +1 -0
- package/src/McpDeployment.d.ts +11 -0
- package/src/McpDeployment.js +32 -0
- package/src/McpDeployment.js.map +1 -0
- package/src/McpInvocationContext.d.ts +14 -0
- package/src/McpInvocationContext.js +25 -0
- package/src/McpInvocationContext.js.map +1 -0
- package/src/McpToolRegistry.d.ts +7 -4
- package/src/McpToolRegistry.js +20 -16
- package/src/McpToolRegistry.js.map +1 -1
- package/src/WpMcpErrorTranslator.d.ts +69 -0
- package/src/WpMcpErrorTranslator.js +168 -0
- package/src/WpMcpErrorTranslator.js.map +1 -0
- package/src/WpMcpServer.d.ts +55 -24
- package/src/WpMcpServer.js +331 -154
- package/src/WpMcpServer.js.map +1 -1
- package/src/__tests__/McpHttpTestHarness.d.ts +40 -0
- package/src/__tests__/McpHttpTestHarness.js +158 -0
- package/src/__tests__/McpHttpTestHarness.js.map +1 -0
- package/src/__tests__/McpRemoteFixtures.d.ts +93 -0
- package/src/__tests__/McpRemoteFixtures.js +409 -0
- package/src/__tests__/McpRemoteFixtures.js.map +1 -0
- package/src/__tests__/WpMcpServerTestFixtures.d.ts +60 -0
- package/src/__tests__/WpMcpServerTestFixtures.js +269 -0
- package/src/__tests__/WpMcpServerTestFixtures.js.map +1 -0
- package/src/index.d.ts +10 -3
- package/src/index.js +18 -4
- package/src/index.js.map +1 -1
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RelayWebpiecesPeerErrors = exports.BoundaryProbeFilter = exports.RecordingOidcVerifier = exports.RecordingOidcMinter = exports.RemoteThrowController = exports.LocalThrowController = exports.RemoteMcpController = exports.THROW_CASES = exports.ThrowCase = exports.RemoteThrowApi = exports.LocalThrowApi = exports.OidcFailRemoteApi = exports.GarbageRemoteApi = exports.RefusedRemoteApi = exports.MissingRemoteMcpApi = exports.RemoteMcpApi = exports.RemoteResponse = exports.RemoteRequest = exports.GATEWAY_PATH = exports.LOCAL_ENDPOINT_JWT = exports.EXTERNAL_MCP_BEARER = exports.OIDC_TOKEN = exports.GARBAGE_SERVICE = exports.REFUSED_SERVICE = exports.REMOTE_SERVICE = exports.REMOTE_ROLES = exports.REMOTE_USER = void 0;
|
|
4
|
+
exports.raiseFor = raiseFor;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
require("reflect-metadata");
|
|
7
|
+
const inversify_1 = require("inversify");
|
|
8
|
+
const core_util_1 = require("@webpieces/core-util");
|
|
9
|
+
const core_context_1 = require("@webpieces/core-context");
|
|
10
|
+
const http_routing_1 = require("@webpieces/http-routing");
|
|
11
|
+
/** Contracts, controllers and test doubles for McpRemoteBinding.integration.spec.ts. */
|
|
12
|
+
exports.REMOTE_USER = core_util_1.ContextKey.trusted('mcpRemoteUser', 'verified by the MCP access-token authority before delegation', 'x-mcp-remote-user');
|
|
13
|
+
exports.REMOTE_ROLES = core_util_1.ContextKey.trusted('mcpRemoteRoles', 'verified by the MCP access-token authority before delegation', 'x-mcp-remote-roles');
|
|
14
|
+
exports.REMOTE_SERVICE = 'mcp-remote-integration';
|
|
15
|
+
exports.REFUSED_SERVICE = 'mcp-remote-refused';
|
|
16
|
+
exports.GARBAGE_SERVICE = 'mcp-remote-garbage';
|
|
17
|
+
exports.OIDC_TOKEN = 'oidc-for-mcp-remote-integration';
|
|
18
|
+
exports.EXTERNAL_MCP_BEARER = 'external-mcp-access-token';
|
|
19
|
+
exports.LOCAL_ENDPOINT_JWT = 'local-endpoint-jwt-that-must-not-cross-the-remote-hop';
|
|
20
|
+
exports.GATEWAY_PATH = '/gateway/mcp';
|
|
21
|
+
let RemoteRequest = class RemoteRequest {
|
|
22
|
+
query;
|
|
23
|
+
constructor(query) {
|
|
24
|
+
if (query !== undefined)
|
|
25
|
+
this.query = query;
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
exports.RemoteRequest = RemoteRequest;
|
|
29
|
+
tslib_1.__decorate([
|
|
30
|
+
(0, core_util_1.WpDtoField)(new core_util_1.WpDtoFieldOptions('Search text', true)),
|
|
31
|
+
tslib_1.__metadata("design:type", String)
|
|
32
|
+
], RemoteRequest.prototype, "query", void 0);
|
|
33
|
+
exports.RemoteRequest = RemoteRequest = tslib_1.__decorate([
|
|
34
|
+
(0, core_util_1.WpDto)(),
|
|
35
|
+
tslib_1.__metadata("design:paramtypes", [String])
|
|
36
|
+
], RemoteRequest);
|
|
37
|
+
let RemoteResponse = class RemoteResponse {
|
|
38
|
+
user;
|
|
39
|
+
roles;
|
|
40
|
+
result;
|
|
41
|
+
constructor(user, roles, result) {
|
|
42
|
+
if (user !== undefined)
|
|
43
|
+
this.user = user;
|
|
44
|
+
if (roles !== undefined)
|
|
45
|
+
this.roles = roles;
|
|
46
|
+
if (result !== undefined)
|
|
47
|
+
this.result = result;
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
exports.RemoteResponse = RemoteResponse;
|
|
51
|
+
tslib_1.__decorate([
|
|
52
|
+
(0, core_util_1.WpDtoField)(new core_util_1.WpDtoFieldOptions('Delegated user', true)),
|
|
53
|
+
tslib_1.__metadata("design:type", String)
|
|
54
|
+
], RemoteResponse.prototype, "user", void 0);
|
|
55
|
+
tslib_1.__decorate([
|
|
56
|
+
(0, core_util_1.WpDtoField)(new core_util_1.WpDtoFieldOptions('Delegated roles', true)),
|
|
57
|
+
tslib_1.__metadata("design:type", String)
|
|
58
|
+
], RemoteResponse.prototype, "roles", void 0);
|
|
59
|
+
tslib_1.__decorate([
|
|
60
|
+
(0, core_util_1.WpDtoField)(new core_util_1.WpDtoFieldOptions('Result', true)),
|
|
61
|
+
tslib_1.__metadata("design:type", String)
|
|
62
|
+
], RemoteResponse.prototype, "result", void 0);
|
|
63
|
+
exports.RemoteResponse = RemoteResponse = tslib_1.__decorate([
|
|
64
|
+
(0, core_util_1.WpDto)(),
|
|
65
|
+
tslib_1.__metadata("design:paramtypes", [String, String, String])
|
|
66
|
+
], RemoteResponse);
|
|
67
|
+
let RemoteMcpApi = class RemoteMcpApi {
|
|
68
|
+
search(_request) {
|
|
69
|
+
throw new Error('contract only');
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
exports.RemoteMcpApi = RemoteMcpApi;
|
|
73
|
+
tslib_1.__decorate([
|
|
74
|
+
(0, core_util_1.WpMcpAuthJwt)({ allRolesAllowed: true }),
|
|
75
|
+
(0, core_util_1.WpAuthOidc)('mcp-gateway'),
|
|
76
|
+
(0, core_util_1.Endpoint)('/search', 'rpc'),
|
|
77
|
+
(0, core_util_1.WpResponseDto)(() => RemoteResponse),
|
|
78
|
+
(0, core_util_1.WpMcpTool)({
|
|
79
|
+
name: 'remote_integration_search',
|
|
80
|
+
description: 'Calls a remote Webpieces API.',
|
|
81
|
+
readOnlyHint: true,
|
|
82
|
+
idempotentHint: true,
|
|
83
|
+
openWorldHint: false,
|
|
84
|
+
}),
|
|
85
|
+
tslib_1.__metadata("design:type", Function),
|
|
86
|
+
tslib_1.__metadata("design:paramtypes", [RemoteRequest]),
|
|
87
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
88
|
+
], RemoteMcpApi.prototype, "search", null);
|
|
89
|
+
exports.RemoteMcpApi = RemoteMcpApi = tslib_1.__decorate([
|
|
90
|
+
(0, core_util_1.ApiPath)('/remote-mcp')
|
|
91
|
+
], RemoteMcpApi);
|
|
92
|
+
let MissingRemoteMcpApi = class MissingRemoteMcpApi {
|
|
93
|
+
search(_request) {
|
|
94
|
+
throw new Error('contract only');
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
exports.MissingRemoteMcpApi = MissingRemoteMcpApi;
|
|
98
|
+
tslib_1.__decorate([
|
|
99
|
+
(0, core_util_1.WpMcpAuthJwt)({ allRolesAllowed: true }),
|
|
100
|
+
(0, core_util_1.WpAuthOidc)('mcp-gateway'),
|
|
101
|
+
(0, core_util_1.Endpoint)('/search', 'rpc'),
|
|
102
|
+
(0, core_util_1.WpResponseDto)(() => RemoteResponse),
|
|
103
|
+
(0, core_util_1.WpMcpTool)({
|
|
104
|
+
name: 'missing_remote_integration_search',
|
|
105
|
+
description: 'Intentionally absent route.',
|
|
106
|
+
readOnlyHint: true,
|
|
107
|
+
idempotentHint: true,
|
|
108
|
+
openWorldHint: false,
|
|
109
|
+
}),
|
|
110
|
+
tslib_1.__metadata("design:type", Function),
|
|
111
|
+
tslib_1.__metadata("design:paramtypes", [RemoteRequest]),
|
|
112
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
113
|
+
], MissingRemoteMcpApi.prototype, "search", null);
|
|
114
|
+
exports.MissingRemoteMcpApi = MissingRemoteMcpApi = tslib_1.__decorate([
|
|
115
|
+
(0, core_util_1.ApiPath)('/missing-remote-mcp')
|
|
116
|
+
], MissingRemoteMcpApi);
|
|
117
|
+
let RefusedRemoteApi = class RefusedRemoteApi {
|
|
118
|
+
search(_request) {
|
|
119
|
+
throw new Error('contract only');
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
exports.RefusedRemoteApi = RefusedRemoteApi;
|
|
123
|
+
tslib_1.__decorate([
|
|
124
|
+
(0, core_util_1.WpMcpAuthJwt)({ allRolesAllowed: true }),
|
|
125
|
+
(0, core_util_1.WpAuthOidc)('mcp-gateway'),
|
|
126
|
+
(0, core_util_1.Endpoint)('/search', 'rpc'),
|
|
127
|
+
(0, core_util_1.WpResponseDto)(() => RemoteResponse),
|
|
128
|
+
(0, core_util_1.WpMcpTool)({
|
|
129
|
+
name: 'refused_remote',
|
|
130
|
+
description: 'Nothing listens on this port.',
|
|
131
|
+
readOnlyHint: true,
|
|
132
|
+
idempotentHint: true,
|
|
133
|
+
openWorldHint: false,
|
|
134
|
+
}),
|
|
135
|
+
tslib_1.__metadata("design:type", Function),
|
|
136
|
+
tslib_1.__metadata("design:paramtypes", [RemoteRequest]),
|
|
137
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
138
|
+
], RefusedRemoteApi.prototype, "search", null);
|
|
139
|
+
exports.RefusedRemoteApi = RefusedRemoteApi = tslib_1.__decorate([
|
|
140
|
+
(0, core_util_1.ApiPath)('/refused-remote-mcp')
|
|
141
|
+
], RefusedRemoteApi);
|
|
142
|
+
let GarbageRemoteApi = class GarbageRemoteApi {
|
|
143
|
+
search(_request) {
|
|
144
|
+
throw new Error('contract only');
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
exports.GarbageRemoteApi = GarbageRemoteApi;
|
|
148
|
+
tslib_1.__decorate([
|
|
149
|
+
(0, core_util_1.WpMcpAuthJwt)({ allRolesAllowed: true }),
|
|
150
|
+
(0, core_util_1.WpAuthOidc)('mcp-gateway'),
|
|
151
|
+
(0, core_util_1.Endpoint)('/search', 'rpc'),
|
|
152
|
+
(0, core_util_1.WpResponseDto)(() => RemoteResponse),
|
|
153
|
+
(0, core_util_1.WpMcpTool)({
|
|
154
|
+
name: 'garbage_remote',
|
|
155
|
+
description: 'Answers an undecodable body.',
|
|
156
|
+
readOnlyHint: true,
|
|
157
|
+
idempotentHint: true,
|
|
158
|
+
openWorldHint: false,
|
|
159
|
+
}),
|
|
160
|
+
tslib_1.__metadata("design:type", Function),
|
|
161
|
+
tslib_1.__metadata("design:paramtypes", [RemoteRequest]),
|
|
162
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
163
|
+
], GarbageRemoteApi.prototype, "search", null);
|
|
164
|
+
exports.GarbageRemoteApi = GarbageRemoteApi = tslib_1.__decorate([
|
|
165
|
+
(0, core_util_1.ApiPath)('/garbage-remote-mcp')
|
|
166
|
+
], GarbageRemoteApi);
|
|
167
|
+
let OidcFailRemoteApi = class OidcFailRemoteApi {
|
|
168
|
+
search(_request) {
|
|
169
|
+
throw new Error('contract only');
|
|
170
|
+
}
|
|
171
|
+
};
|
|
172
|
+
exports.OidcFailRemoteApi = OidcFailRemoteApi;
|
|
173
|
+
tslib_1.__decorate([
|
|
174
|
+
(0, core_util_1.WpMcpAuthJwt)({ allRolesAllowed: true }),
|
|
175
|
+
(0, core_util_1.WpAuthOidc)('mcp-gateway'),
|
|
176
|
+
(0, core_util_1.Endpoint)('/search', 'rpc'),
|
|
177
|
+
(0, core_util_1.WpResponseDto)(() => RemoteResponse),
|
|
178
|
+
(0, core_util_1.WpMcpTool)({
|
|
179
|
+
name: 'oidc_fail_remote',
|
|
180
|
+
description: 'Its OIDC token cannot be minted.',
|
|
181
|
+
readOnlyHint: true,
|
|
182
|
+
idempotentHint: true,
|
|
183
|
+
openWorldHint: false,
|
|
184
|
+
}),
|
|
185
|
+
tslib_1.__metadata("design:type", Function),
|
|
186
|
+
tslib_1.__metadata("design:paramtypes", [RemoteRequest]),
|
|
187
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
188
|
+
], OidcFailRemoteApi.prototype, "search", null);
|
|
189
|
+
exports.OidcFailRemoteApi = OidcFailRemoteApi = tslib_1.__decorate([
|
|
190
|
+
(0, core_util_1.ApiPath)('/oidc-fail-remote-mcp')
|
|
191
|
+
], OidcFailRemoteApi);
|
|
192
|
+
let LocalThrowApi = class LocalThrowApi {
|
|
193
|
+
fail(_request) {
|
|
194
|
+
throw new Error('contract only');
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
exports.LocalThrowApi = LocalThrowApi;
|
|
198
|
+
tslib_1.__decorate([
|
|
199
|
+
(0, core_util_1.WpMcpAuthJwt)({ allRolesAllowed: true }),
|
|
200
|
+
(0, core_util_1.WpAuthJwt)({ allRolesAllowed: true }),
|
|
201
|
+
(0, core_util_1.Endpoint)('/throw', 'rpc'),
|
|
202
|
+
(0, core_util_1.WpResponseDto)(() => RemoteResponse),
|
|
203
|
+
(0, core_util_1.WpMcpTool)({
|
|
204
|
+
name: 'local_throw',
|
|
205
|
+
description: 'Throws the requested failure in-process.',
|
|
206
|
+
readOnlyHint: true,
|
|
207
|
+
idempotentHint: true,
|
|
208
|
+
openWorldHint: false,
|
|
209
|
+
}),
|
|
210
|
+
tslib_1.__metadata("design:type", Function),
|
|
211
|
+
tslib_1.__metadata("design:paramtypes", [RemoteRequest]),
|
|
212
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
213
|
+
], LocalThrowApi.prototype, "fail", null);
|
|
214
|
+
exports.LocalThrowApi = LocalThrowApi = tslib_1.__decorate([
|
|
215
|
+
(0, core_util_1.ApiPath)('/local-throw')
|
|
216
|
+
], LocalThrowApi);
|
|
217
|
+
let RemoteThrowApi = class RemoteThrowApi {
|
|
218
|
+
fail(_request) {
|
|
219
|
+
throw new Error('contract only');
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
exports.RemoteThrowApi = RemoteThrowApi;
|
|
223
|
+
tslib_1.__decorate([
|
|
224
|
+
(0, core_util_1.WpMcpAuthJwt)({ allRolesAllowed: true }),
|
|
225
|
+
(0, core_util_1.WpAuthOidc)('mcp-gateway'),
|
|
226
|
+
(0, core_util_1.Endpoint)('/throw', 'rpc'),
|
|
227
|
+
(0, core_util_1.WpResponseDto)(() => RemoteResponse),
|
|
228
|
+
(0, core_util_1.WpMcpTool)({
|
|
229
|
+
name: 'remote_throw',
|
|
230
|
+
description: 'Throws the requested failure remotely.',
|
|
231
|
+
readOnlyHint: true,
|
|
232
|
+
idempotentHint: true,
|
|
233
|
+
openWorldHint: false,
|
|
234
|
+
}),
|
|
235
|
+
tslib_1.__metadata("design:type", Function),
|
|
236
|
+
tslib_1.__metadata("design:paramtypes", [RemoteRequest]),
|
|
237
|
+
tslib_1.__metadata("design:returntype", Promise)
|
|
238
|
+
], RemoteThrowApi.prototype, "fail", null);
|
|
239
|
+
exports.RemoteThrowApi = RemoteThrowApi = tslib_1.__decorate([
|
|
240
|
+
(0, core_util_1.ApiPath)('/remote-throw')
|
|
241
|
+
], RemoteThrowApi);
|
|
242
|
+
/** One named throw, run identically behind the local and the remote binding. */
|
|
243
|
+
class ThrowCase {
|
|
244
|
+
name;
|
|
245
|
+
expectedKind;
|
|
246
|
+
expectedLevel;
|
|
247
|
+
raise;
|
|
248
|
+
constructor(name, expectedKind, expectedLevel, raise) {
|
|
249
|
+
this.name = name;
|
|
250
|
+
this.expectedKind = expectedKind;
|
|
251
|
+
this.expectedLevel = expectedLevel;
|
|
252
|
+
this.raise = raise;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
exports.ThrowCase = ThrowCase;
|
|
256
|
+
exports.THROW_CASES = [
|
|
257
|
+
new ThrowCase('end-user', 'end-user', 'info', () => {
|
|
258
|
+
throw new core_util_1.ApiEndUserError('The two passwords you entered do not match.', 'PW_MISMATCH');
|
|
259
|
+
}),
|
|
260
|
+
new ThrowCase('bad-request', 'bad-request', 'info', () => {
|
|
261
|
+
throw new core_util_1.ApiBadRequestError('internal column name', '$.query', 'query must be a word');
|
|
262
|
+
}),
|
|
263
|
+
new ThrowCase('unauthorized', 'unauthorized', 'info', () => {
|
|
264
|
+
throw new core_util_1.ApiUnauthorizedError('session internals');
|
|
265
|
+
}),
|
|
266
|
+
new ThrowCase('forbidden', 'forbidden', 'info', () => {
|
|
267
|
+
throw new core_util_1.ApiForbiddenError('acl internals');
|
|
268
|
+
}),
|
|
269
|
+
new ThrowCase('not-found', 'not-found', 'info', () => {
|
|
270
|
+
throw new core_util_1.ApiNotFoundError('row 7 missing');
|
|
271
|
+
}),
|
|
272
|
+
new ThrowCase('endpoint-not-found', 'endpoint-not-found', 'info', () => {
|
|
273
|
+
throw new core_util_1.ApiEndpointNotFoundError('route internals');
|
|
274
|
+
}),
|
|
275
|
+
new ThrowCase('request-timeout', 'request-timeout', 'error', () => {
|
|
276
|
+
throw new core_util_1.ApiRequestTimeoutError('slow internals');
|
|
277
|
+
}),
|
|
278
|
+
new ThrowCase('rate-limited', 'rate-limited', 'error', () => {
|
|
279
|
+
throw new core_util_1.ApiRateLimitedError('quota internals');
|
|
280
|
+
}),
|
|
281
|
+
new ThrowCase('implementation', 'implementation', 'error', () => {
|
|
282
|
+
throw new core_util_1.ApiImplementationError('bug internals');
|
|
283
|
+
}),
|
|
284
|
+
new ThrowCase('dependency', 'dependency', 'error', () => {
|
|
285
|
+
throw new core_util_1.ApiDependencyError('upstream internals');
|
|
286
|
+
}),
|
|
287
|
+
new ThrowCase('unavailable', 'unavailable', 'error', () => {
|
|
288
|
+
throw new core_util_1.ApiUnavailableError('maintenance internals');
|
|
289
|
+
}),
|
|
290
|
+
new ThrowCase('dependency-timeout', 'dependency-timeout', 'error', () => {
|
|
291
|
+
throw new core_util_1.ApiDependencyTimeoutError('upstream slow internals');
|
|
292
|
+
}),
|
|
293
|
+
new ThrowCase('dependency-backoff', 'dependency-backoff', 'error', () => {
|
|
294
|
+
throw new core_util_1.ApiDependencyBackoffError('backoff internals', 42);
|
|
295
|
+
}),
|
|
296
|
+
new ThrowCase('connection', 'implementation', 'error', () => {
|
|
297
|
+
throw new core_util_1.ApiConnectionError('ECONNRESET internals');
|
|
298
|
+
}),
|
|
299
|
+
new ThrowCase('raw-error', 'implementation', 'error', () => {
|
|
300
|
+
throw new Error('SECRET-raw-internals');
|
|
301
|
+
}),
|
|
302
|
+
new ThrowCase('thrown-string', 'implementation', 'error', () => {
|
|
303
|
+
throw 'SECRET-string-internals';
|
|
304
|
+
}),
|
|
305
|
+
];
|
|
306
|
+
function raiseFor(query) {
|
|
307
|
+
const found = exports.THROW_CASES.find((candidate) => candidate.name === query);
|
|
308
|
+
if (!found)
|
|
309
|
+
throw new Error(`unknown throw case ${query}`);
|
|
310
|
+
return found.raise();
|
|
311
|
+
}
|
|
312
|
+
let RemoteMcpController = class RemoteMcpController extends RemoteMcpApi {
|
|
313
|
+
calls = 0;
|
|
314
|
+
authorization;
|
|
315
|
+
search(request) {
|
|
316
|
+
this.calls += 1;
|
|
317
|
+
this.authorization = core_context_1.RequestContext.getRequest()?.getHeader('authorization');
|
|
318
|
+
return Promise.resolve(new RemoteResponse(core_context_1.RequestContext.getTrusted(exports.REMOTE_USER) ?? 'missing-user', core_context_1.RequestContext.getTrusted(exports.REMOTE_ROLES) ?? 'missing-roles', `remote:${request.query}`));
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
exports.RemoteMcpController = RemoteMcpController;
|
|
322
|
+
exports.RemoteMcpController = RemoteMcpController = tslib_1.__decorate([
|
|
323
|
+
(0, inversify_1.injectable)()
|
|
324
|
+
], RemoteMcpController);
|
|
325
|
+
let LocalThrowController = class LocalThrowController extends LocalThrowApi {
|
|
326
|
+
async fail(request) {
|
|
327
|
+
return raiseFor(request.query);
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
exports.LocalThrowController = LocalThrowController;
|
|
331
|
+
exports.LocalThrowController = LocalThrowController = tslib_1.__decorate([
|
|
332
|
+
(0, inversify_1.injectable)()
|
|
333
|
+
], LocalThrowController);
|
|
334
|
+
let RemoteThrowController = class RemoteThrowController extends RemoteThrowApi {
|
|
335
|
+
async fail(request) {
|
|
336
|
+
return raiseFor(request.query);
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
exports.RemoteThrowController = RemoteThrowController;
|
|
340
|
+
exports.RemoteThrowController = RemoteThrowController = tslib_1.__decorate([
|
|
341
|
+
(0, inversify_1.injectable)()
|
|
342
|
+
], RemoteThrowController);
|
|
343
|
+
class RecordingOidcMinter {
|
|
344
|
+
failure;
|
|
345
|
+
token;
|
|
346
|
+
audiences = [];
|
|
347
|
+
constructor(failure, token = exports.OIDC_TOKEN) {
|
|
348
|
+
this.failure = failure;
|
|
349
|
+
this.token = token;
|
|
350
|
+
}
|
|
351
|
+
mintIdToken(audience) {
|
|
352
|
+
this.audiences.push(audience);
|
|
353
|
+
if (this.failure)
|
|
354
|
+
return Promise.reject(this.failure);
|
|
355
|
+
return Promise.resolve(this.token);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
exports.RecordingOidcMinter = RecordingOidcMinter;
|
|
359
|
+
class RecordingOidcVerifier extends http_routing_1.OidcHook {
|
|
360
|
+
tokens = [];
|
|
361
|
+
callerLists = [];
|
|
362
|
+
verifyOidc(token, callers) {
|
|
363
|
+
this.tokens.push(token);
|
|
364
|
+
this.callerLists.push(callers);
|
|
365
|
+
if (token !== exports.OIDC_TOKEN)
|
|
366
|
+
throw new Error(`unexpected OIDC token '${token}'`);
|
|
367
|
+
return Promise.resolve();
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
exports.RecordingOidcVerifier = RecordingOidcVerifier;
|
|
371
|
+
let BoundaryProbeFilter = class BoundaryProbeFilter extends core_util_1.Filter {
|
|
372
|
+
calls = 0;
|
|
373
|
+
sawLogIdentity = false;
|
|
374
|
+
sawTrustedUser;
|
|
375
|
+
filter(meta, next) {
|
|
376
|
+
this.calls += 1;
|
|
377
|
+
this.sawLogIdentity =
|
|
378
|
+
core_context_1.RequestContext.getUntrusted(core_util_1.WebpiecesCoreHeaders.CONTROLLER) ===
|
|
379
|
+
'RemoteMcpController' &&
|
|
380
|
+
core_context_1.RequestContext.getUntrusted(core_util_1.WebpiecesCoreHeaders.METHOD) === 'search';
|
|
381
|
+
this.sawTrustedUser = core_context_1.RequestContext.getTrusted(exports.REMOTE_USER);
|
|
382
|
+
return next.invoke(meta);
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
exports.BoundaryProbeFilter = BoundaryProbeFilter;
|
|
386
|
+
exports.BoundaryProbeFilter = BoundaryProbeFilter = tslib_1.__decorate([
|
|
387
|
+
(0, inversify_1.injectable)()
|
|
388
|
+
], BoundaryProbeFilter);
|
|
389
|
+
/**
|
|
390
|
+
* The documented gateway opt-out from http-client-node's "a dependency's 4xx is OUR 500" invariant:
|
|
391
|
+
* an MCP gateway relays a Webpieces peer's typed failure so the model sees the same kind it would see
|
|
392
|
+
* for a local binding. `relay = false` answers "not mine", which is the framework default.
|
|
393
|
+
*/
|
|
394
|
+
class RelayWebpiecesPeerErrors {
|
|
395
|
+
relay = true;
|
|
396
|
+
toWire(_error) {
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
399
|
+
fromWire(response) {
|
|
400
|
+
if (!this.relay || !core_util_1.ApiErrorCodec.isPayload(response.body))
|
|
401
|
+
return undefined;
|
|
402
|
+
const decoded = core_util_1.ApiErrorCodec.decode(response.body);
|
|
403
|
+
if (!core_util_1.ApiErrorHttpStatus.hasCode(decoded))
|
|
404
|
+
return undefined;
|
|
405
|
+
return core_util_1.ApiErrorHttpStatus.code(decoded) === response.status.code ? decoded : undefined;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
exports.RelayWebpiecesPeerErrors = RelayWebpiecesPeerErrors;
|
|
409
|
+
//# sourceMappingURL=McpRemoteFixtures.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"McpRemoteFixtures.js","sourceRoot":"","sources":["../../../../../../packages/http/mcp-server/src/__tests__/McpRemoteFixtures.ts"],"names":[],"mappings":"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","sourcesContent":["import 'reflect-metadata';\nimport { injectable } from 'inversify';\nimport {\n ApiBadRequestError,\n ApiConnectionError,\n ApiDependencyBackoffError,\n ApiDependencyError,\n ApiDependencyTimeoutError,\n ApiEndpointNotFoundError,\n ApiEndUserError,\n ApiError,\n ApiErrorCodec,\n ApiErrorHttpStatus,\n ApiForbiddenError,\n ApiImplementationError,\n ApiNotFoundError,\n ApiPath,\n ApiRateLimitedError,\n ApiRequestTimeoutError,\n ApiUnauthorizedError,\n ApiUnavailableError,\n ContextKey,\n Endpoint,\n ErrorTranslators,\n Filter,\n HttpResponseDto,\n LogLevel,\n Service,\n WebpiecesCoreHeaders,\n WpAuthJwt,\n WpAuthOidc,\n WpDto,\n WpDtoField,\n WpDtoFieldOptions,\n WpMcpAuthJwt,\n WpMcpTool,\n WpResponseDto,\n} from '@webpieces/core-util';\nimport { RequestContext } from '@webpieces/core-context';\nimport { MethodMeta, OidcHook, WpResponse } from '@webpieces/http-routing';\n\n/** Contracts, controllers and test doubles for McpRemoteBinding.integration.spec.ts. */\n\nexport const REMOTE_USER = ContextKey.trusted<string>(\n 'mcpRemoteUser',\n 'verified by the MCP access-token authority before delegation',\n 'x-mcp-remote-user',\n);\nexport const REMOTE_ROLES = ContextKey.trusted<string>(\n 'mcpRemoteRoles',\n 'verified by the MCP access-token authority before delegation',\n 'x-mcp-remote-roles',\n);\nexport const REMOTE_SERVICE = 'mcp-remote-integration';\nexport const REFUSED_SERVICE = 'mcp-remote-refused';\nexport const GARBAGE_SERVICE = 'mcp-remote-garbage';\nexport const OIDC_TOKEN = 'oidc-for-mcp-remote-integration';\nexport const EXTERNAL_MCP_BEARER = 'external-mcp-access-token';\nexport const LOCAL_ENDPOINT_JWT = 'local-endpoint-jwt-that-must-not-cross-the-remote-hop';\nexport const GATEWAY_PATH = '/gateway/mcp';\n\n@WpDto()\nexport class RemoteRequest {\n @WpDtoField(new WpDtoFieldOptions('Search text', true))\n query!: string;\n\n constructor(query?: string) {\n if (query !== undefined) this.query = query;\n }\n}\n\n@WpDto()\nexport class RemoteResponse {\n @WpDtoField(new WpDtoFieldOptions('Delegated user', true))\n user!: string;\n\n @WpDtoField(new WpDtoFieldOptions('Delegated roles', true))\n roles!: string;\n\n @WpDtoField(new WpDtoFieldOptions('Result', true))\n result!: string;\n\n constructor(user?: string, roles?: string, result?: string) {\n if (user !== undefined) this.user = user;\n if (roles !== undefined) this.roles = roles;\n if (result !== undefined) this.result = result;\n }\n}\n\n@ApiPath('/remote-mcp')\nexport abstract class RemoteMcpApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthOidc('mcp-gateway')\n @Endpoint('/search', 'rpc')\n @WpResponseDto(() => RemoteResponse)\n @WpMcpTool({\n name: 'remote_integration_search',\n description: 'Calls a remote Webpieces API.',\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n })\n search(_request: RemoteRequest): Promise<RemoteResponse> {\n throw new Error('contract only');\n }\n}\n\n@ApiPath('/missing-remote-mcp')\nexport abstract class MissingRemoteMcpApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthOidc('mcp-gateway')\n @Endpoint('/search', 'rpc')\n @WpResponseDto(() => RemoteResponse)\n @WpMcpTool({\n name: 'missing_remote_integration_search',\n description: 'Intentionally absent route.',\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n })\n search(_request: RemoteRequest): Promise<RemoteResponse> {\n throw new Error('contract only');\n }\n}\n\n@ApiPath('/refused-remote-mcp')\nexport abstract class RefusedRemoteApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthOidc('mcp-gateway')\n @Endpoint('/search', 'rpc')\n @WpResponseDto(() => RemoteResponse)\n @WpMcpTool({\n name: 'refused_remote',\n description: 'Nothing listens on this port.',\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n })\n search(_request: RemoteRequest): Promise<RemoteResponse> {\n throw new Error('contract only');\n }\n}\n\n@ApiPath('/garbage-remote-mcp')\nexport abstract class GarbageRemoteApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthOidc('mcp-gateway')\n @Endpoint('/search', 'rpc')\n @WpResponseDto(() => RemoteResponse)\n @WpMcpTool({\n name: 'garbage_remote',\n description: 'Answers an undecodable body.',\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n })\n search(_request: RemoteRequest): Promise<RemoteResponse> {\n throw new Error('contract only');\n }\n}\n\n@ApiPath('/oidc-fail-remote-mcp')\nexport abstract class OidcFailRemoteApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthOidc('mcp-gateway')\n @Endpoint('/search', 'rpc')\n @WpResponseDto(() => RemoteResponse)\n @WpMcpTool({\n name: 'oidc_fail_remote',\n description: 'Its OIDC token cannot be minted.',\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n })\n search(_request: RemoteRequest): Promise<RemoteResponse> {\n throw new Error('contract only');\n }\n}\n\n@ApiPath('/local-throw')\nexport abstract class LocalThrowApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthJwt({ allRolesAllowed: true })\n @Endpoint('/throw', 'rpc')\n @WpResponseDto(() => RemoteResponse)\n @WpMcpTool({\n name: 'local_throw',\n description: 'Throws the requested failure in-process.',\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n })\n fail(_request: RemoteRequest): Promise<RemoteResponse> {\n throw new Error('contract only');\n }\n}\n\n@ApiPath('/remote-throw')\nexport abstract class RemoteThrowApi {\n @WpMcpAuthJwt({ allRolesAllowed: true })\n @WpAuthOidc('mcp-gateway')\n @Endpoint('/throw', 'rpc')\n @WpResponseDto(() => RemoteResponse)\n @WpMcpTool({\n name: 'remote_throw',\n description: 'Throws the requested failure remotely.',\n readOnlyHint: true,\n idempotentHint: true,\n openWorldHint: false,\n })\n fail(_request: RemoteRequest): Promise<RemoteResponse> {\n throw new Error('contract only');\n }\n}\n\n/** One named throw, run identically behind the local and the remote binding. */\nexport class ThrowCase {\n constructor(\n public readonly name: string,\n public readonly expectedKind: string,\n public readonly expectedLevel: LogLevel,\n public readonly raise: () => never,\n ) {}\n}\n\nexport const THROW_CASES: readonly ThrowCase[] = [\n new ThrowCase('end-user', 'end-user', 'info', () => {\n throw new ApiEndUserError('The two passwords you entered do not match.', 'PW_MISMATCH');\n }),\n new ThrowCase('bad-request', 'bad-request', 'info', () => {\n throw new ApiBadRequestError('internal column name', '$.query', 'query must be a word');\n }),\n new ThrowCase('unauthorized', 'unauthorized', 'info', () => {\n throw new ApiUnauthorizedError('session internals');\n }),\n new ThrowCase('forbidden', 'forbidden', 'info', () => {\n throw new ApiForbiddenError('acl internals');\n }),\n new ThrowCase('not-found', 'not-found', 'info', () => {\n throw new ApiNotFoundError('row 7 missing');\n }),\n new ThrowCase('endpoint-not-found', 'endpoint-not-found', 'info', () => {\n throw new ApiEndpointNotFoundError('route internals');\n }),\n new ThrowCase('request-timeout', 'request-timeout', 'error', () => {\n throw new ApiRequestTimeoutError('slow internals');\n }),\n new ThrowCase('rate-limited', 'rate-limited', 'error', () => {\n throw new ApiRateLimitedError('quota internals');\n }),\n new ThrowCase('implementation', 'implementation', 'error', () => {\n throw new ApiImplementationError('bug internals');\n }),\n new ThrowCase('dependency', 'dependency', 'error', () => {\n throw new ApiDependencyError('upstream internals');\n }),\n new ThrowCase('unavailable', 'unavailable', 'error', () => {\n throw new ApiUnavailableError('maintenance internals');\n }),\n new ThrowCase('dependency-timeout', 'dependency-timeout', 'error', () => {\n throw new ApiDependencyTimeoutError('upstream slow internals');\n }),\n new ThrowCase('dependency-backoff', 'dependency-backoff', 'error', () => {\n throw new ApiDependencyBackoffError('backoff internals', 42);\n }),\n new ThrowCase('connection', 'implementation', 'error', () => {\n throw new ApiConnectionError('ECONNRESET internals');\n }),\n new ThrowCase('raw-error', 'implementation', 'error', () => {\n throw new Error('SECRET-raw-internals');\n }),\n new ThrowCase('thrown-string', 'implementation', 'error', () => {\n throw 'SECRET-string-internals';\n }),\n];\n\nexport function raiseFor(query: string): never {\n const found = THROW_CASES.find((candidate: ThrowCase) => candidate.name === query);\n if (!found) throw new Error(`unknown throw case ${query}`);\n return found.raise();\n}\n\n@injectable()\nexport class RemoteMcpController extends RemoteMcpApi {\n calls = 0;\n authorization?: string;\n\n override search(request: RemoteRequest): Promise<RemoteResponse> {\n this.calls += 1;\n this.authorization = RequestContext.getRequest()?.getHeader('authorization');\n return Promise.resolve(\n new RemoteResponse(\n RequestContext.getTrusted(REMOTE_USER) ?? 'missing-user',\n RequestContext.getTrusted(REMOTE_ROLES) ?? 'missing-roles',\n `remote:${request.query}`,\n ),\n );\n }\n}\n\n@injectable()\nexport class LocalThrowController extends LocalThrowApi {\n override async fail(request: RemoteRequest): Promise<RemoteResponse> {\n return raiseFor(request.query);\n }\n}\n\n@injectable()\nexport class RemoteThrowController extends RemoteThrowApi {\n override async fail(request: RemoteRequest): Promise<RemoteResponse> {\n return raiseFor(request.query);\n }\n}\n\nexport class RecordingOidcMinter {\n readonly audiences: string[] = [];\n\n constructor(\n private readonly failure?: Error,\n private readonly token = OIDC_TOKEN,\n ) {}\n\n mintIdToken(audience: string): Promise<string> {\n this.audiences.push(audience);\n if (this.failure) return Promise.reject(this.failure);\n return Promise.resolve(this.token);\n }\n}\n\nexport class RecordingOidcVerifier extends OidcHook {\n readonly tokens: string[] = [];\n readonly callerLists: string[][] = [];\n\n override verifyOidc(token: string, callers: string[]): Promise<void> {\n this.tokens.push(token);\n this.callerLists.push(callers);\n if (token !== OIDC_TOKEN) throw new Error(`unexpected OIDC token '${token}'`);\n return Promise.resolve();\n }\n}\n\n@injectable()\nexport class BoundaryProbeFilter extends Filter<MethodMeta, WpResponse<unknown>> {\n calls = 0;\n sawLogIdentity = false;\n sawTrustedUser?: string;\n\n override filter(\n meta: MethodMeta,\n next: Service<MethodMeta, WpResponse<unknown>>,\n ): Promise<WpResponse<unknown>> {\n this.calls += 1;\n this.sawLogIdentity =\n RequestContext.getUntrusted(WebpiecesCoreHeaders.CONTROLLER) ===\n 'RemoteMcpController' &&\n RequestContext.getUntrusted(WebpiecesCoreHeaders.METHOD) === 'search';\n this.sawTrustedUser = RequestContext.getTrusted(REMOTE_USER);\n return next.invoke(meta);\n }\n}\n\n/**\n * The documented gateway opt-out from http-client-node's \"a dependency's 4xx is OUR 500\" invariant:\n * an MCP gateway relays a Webpieces peer's typed failure so the model sees the same kind it would see\n * for a local binding. `relay = false` answers \"not mine\", which is the framework default.\n */\nexport class RelayWebpiecesPeerErrors implements ErrorTranslators {\n relay = true;\n\n toWire(_error: Error): HttpResponseDto | undefined {\n return undefined;\n }\n\n fromWire(response: HttpResponseDto): ApiError | undefined {\n if (!this.relay || !ApiErrorCodec.isPayload(response.body)) return undefined;\n const decoded = ApiErrorCodec.decode(response.body);\n if (!ApiErrorHttpStatus.hasCode(decoded)) return undefined;\n return ApiErrorHttpStatus.code(decoded) === response.status.code ? decoded : undefined;\n }\n}\n"]}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { Logger, LoggerFactory, LogLevel, ContextKey } from '@webpieces/core-util';
|
|
3
|
+
import { AuthenticatedCaller, JwtHook, MintedJwt } from '@webpieces/http-routing';
|
|
4
|
+
import { McpAccessTokenAuthority, VerifiedMcpCredential } from '../McpAuth';
|
|
5
|
+
export declare const USER_ID: ContextKey<string, "trusted">;
|
|
6
|
+
export declare const ENDPOINT_PATH = "/app-owned/mcp";
|
|
7
|
+
export declare const MODERN_VERSION = "2026-07-28";
|
|
8
|
+
export declare class SearchRequest {
|
|
9
|
+
query: string;
|
|
10
|
+
}
|
|
11
|
+
export declare class SearchResponse {
|
|
12
|
+
userId: string;
|
|
13
|
+
result: string;
|
|
14
|
+
}
|
|
15
|
+
export declare abstract class SearchApi {
|
|
16
|
+
search(_request: SearchRequest): Promise<SearchResponse>;
|
|
17
|
+
admin(_request: SearchRequest): Promise<SearchResponse>;
|
|
18
|
+
}
|
|
19
|
+
export declare abstract class RemoteSearchApi {
|
|
20
|
+
search(_request: SearchRequest): Promise<SearchResponse>;
|
|
21
|
+
}
|
|
22
|
+
export declare class SearchController extends SearchApi {
|
|
23
|
+
static waitStartedHook?: () => void;
|
|
24
|
+
static waitAbortedHook?: () => void;
|
|
25
|
+
adminInvocations: number;
|
|
26
|
+
search(request: SearchRequest): Promise<SearchResponse>;
|
|
27
|
+
admin(request: SearchRequest): Promise<SearchResponse>;
|
|
28
|
+
}
|
|
29
|
+
export declare class RemoteSearchClient extends RemoteSearchApi {
|
|
30
|
+
seenAuthorization?: string;
|
|
31
|
+
search(request: SearchRequest): Promise<SearchResponse>;
|
|
32
|
+
}
|
|
33
|
+
export declare class TestJwtHook extends JwtHook<string> {
|
|
34
|
+
mintedTokens: string[];
|
|
35
|
+
lifetimeSeconds: number;
|
|
36
|
+
failWith?: Error;
|
|
37
|
+
mint(subject: string): Promise<MintedJwt>;
|
|
38
|
+
parseJwt(token: string): Promise<AuthenticatedCaller>;
|
|
39
|
+
}
|
|
40
|
+
export declare class TestTokenAuthority implements McpAccessTokenAuthority<string> {
|
|
41
|
+
verificationCount: number;
|
|
42
|
+
roles: readonly string[];
|
|
43
|
+
mintAccessToken(_grant: string): Promise<never>;
|
|
44
|
+
verifyAccessToken(token: string, resource: string): Promise<VerifiedMcpCredential>;
|
|
45
|
+
}
|
|
46
|
+
export declare class RecordedLogLine {
|
|
47
|
+
readonly logger: string;
|
|
48
|
+
readonly level: LogLevel;
|
|
49
|
+
readonly message: string;
|
|
50
|
+
readonly error?: Error | undefined;
|
|
51
|
+
constructor(logger: string, level: LogLevel, message: string, error?: Error | undefined);
|
|
52
|
+
/** The rendered line an operator would see, including an attached error's message and stack. */
|
|
53
|
+
rendered(): string;
|
|
54
|
+
}
|
|
55
|
+
/** Captures every log line in-process so tests can count and level-check operator output. */
|
|
56
|
+
export declare class RecordingLoggerFactory implements LoggerFactory {
|
|
57
|
+
readonly lines: RecordedLogLine[];
|
|
58
|
+
getLogger(name: string): Logger;
|
|
59
|
+
containing(text: string): RecordedLogLine[];
|
|
60
|
+
}
|