@webpieces/mcp-server 0.4.773 → 0.4.775

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +83 -8
  2. package/package.json +11 -5
  3. package/src/McpApiBinding.d.ts +14 -0
  4. package/src/McpApiBinding.js +42 -0
  5. package/src/McpApiBinding.js.map +1 -0
  6. package/src/McpApiDispatcher.d.ts +10 -19
  7. package/src/McpApiDispatcher.js +35 -44
  8. package/src/McpApiDispatcher.js.map +1 -1
  9. package/src/McpAuth.d.ts +11 -2
  10. package/src/McpAuth.js +5 -1
  11. package/src/McpAuth.js.map +1 -1
  12. package/src/McpAuthorityCompileAssertions.js.map +1 -1
  13. package/src/McpBindOptions.d.ts +17 -0
  14. package/src/McpBindOptions.js +36 -0
  15. package/src/McpBindOptions.js.map +1 -0
  16. package/src/McpDeployment.d.ts +11 -0
  17. package/src/McpDeployment.js +32 -0
  18. package/src/McpDeployment.js.map +1 -0
  19. package/src/McpInvocationContext.d.ts +14 -0
  20. package/src/McpInvocationContext.js +25 -0
  21. package/src/McpInvocationContext.js.map +1 -0
  22. package/src/McpToolRegistry.d.ts +7 -4
  23. package/src/McpToolRegistry.js +20 -16
  24. package/src/McpToolRegistry.js.map +1 -1
  25. package/src/WpMcpErrorTranslator.d.ts +69 -0
  26. package/src/WpMcpErrorTranslator.js +168 -0
  27. package/src/WpMcpErrorTranslator.js.map +1 -0
  28. package/src/WpMcpServer.d.ts +55 -24
  29. package/src/WpMcpServer.js +331 -154
  30. package/src/WpMcpServer.js.map +1 -1
  31. package/src/__tests__/McpHttpTestHarness.d.ts +40 -0
  32. package/src/__tests__/McpHttpTestHarness.js +158 -0
  33. package/src/__tests__/McpHttpTestHarness.js.map +1 -0
  34. package/src/__tests__/McpRemoteFixtures.d.ts +93 -0
  35. package/src/__tests__/McpRemoteFixtures.js +409 -0
  36. package/src/__tests__/McpRemoteFixtures.js.map +1 -0
  37. package/src/__tests__/WpMcpServerTestFixtures.d.ts +60 -0
  38. package/src/__tests__/WpMcpServerTestFixtures.js +269 -0
  39. package/src/__tests__/WpMcpServerTestFixtures.js.map +1 -0
  40. package/src/index.d.ts +10 -3
  41. package/src/index.js +18 -4
  42. package/src/index.js.map +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"McpDeployment.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpDeployment.ts"],"names":[],"mappings":";;;AAAA,yDAAsF;AAItF,uFAAuF;AACvF,MAAa,aAAa;IAEF;IACA;IACA;IAHpB,YACoB,IAAuB,EACvB,GAAmB,EACnB,KAAa;QAFb,SAAI,GAAJ,IAAI,CAAmB;QACvB,QAAG,GAAH,GAAG,CAAgB;QACnB,UAAK,GAAL,KAAK,CAAQ;QAE7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC;YACrC,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAClE,CAAC;IAED,6EAA6E;IAC7E,MAAM,CAAC,aAAa,CAAC,KAAK,GAAG,MAAM;QAC/B,OAAO,IAAI,aAAa,CAAC,gBAAgB,EAAE,IAAI,+BAAsB,EAAE,EAAE,KAAK,CAAC,CAAC;IACpF,CAAC;IAED,6EAA6E;IAC7E,MAAM,CAAC,WAAW,CAAC,GAAmB,EAAE,KAAK,GAAG,MAAM;QAClD,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;QAC1F,IAAI,GAAG,YAAY,+BAAsB,EAAE,CAAC;YACxC,MAAM,IAAI,KAAK,CACX,4FAA4F,CAC/F,CAAC;QACN,CAAC;QACD,OAAO,IAAI,aAAa,CAAC,aAAa,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;IACxD,CAAC;CACJ;AAzBD,sCAyBC","sourcesContent":["import { InMemoryServerEventBus, ServerEventBus } from '@modelcontextprotocol/server';\n\nexport type McpDeploymentMode = 'single-process' | 'distributed';\n\n/** Makes process topology and bounded list-cache behavior explicit at construction. */\nexport class McpDeployment {\n private constructor(\n public readonly mode: McpDeploymentMode,\n public readonly bus: ServerEventBus,\n public readonly ttlMs: number,\n ) {\n if (!Number.isFinite(ttlMs) || ttlMs <= 0)\n throw new Error('MCP list cache ttlMs must be positive.');\n }\n\n // webpieces-disable no-function-outside-class -- explicit deployment factory\n static singleProcess(ttlMs = 30_000): McpDeployment {\n return new McpDeployment('single-process', new InMemoryServerEventBus(), ttlMs);\n }\n\n // webpieces-disable no-function-outside-class -- explicit deployment factory\n static distributed(bus: ServerEventBus, ttlMs = 30_000): McpDeployment {\n if (!bus) throw new Error('Distributed MCP deployment requires a shared ServerEventBus.');\n if (bus instanceof InMemoryServerEventBus) {\n throw new Error(\n 'Distributed MCP deployment cannot use InMemoryServerEventBus; supply a cross-instance bus.',\n );\n }\n return new McpDeployment('distributed', bus, ttlMs);\n }\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import { ContextKey } from '@webpieces/core-util';
2
+ export type McpProgressReporter = (progress: number, total?: number, message?: string) => Promise<void>;
3
+ /** Trusted, context-local MCP facts available to a bound application endpoint. */
4
+ export declare class McpInvocationContext {
5
+ readonly jsonRpcId: string | number;
6
+ readonly toolName: string;
7
+ readonly subject: string;
8
+ readonly roles: readonly string[];
9
+ readonly signal: AbortSignal;
10
+ readonly reportProgress?: McpProgressReporter | undefined;
11
+ constructor(jsonRpcId: string | number, toolName: string, subject: string, roles: readonly string[], signal: AbortSignal, reportProgress?: McpProgressReporter | undefined);
12
+ }
13
+ /** Context-only: it has no HTTP header and cannot be asserted by a remote caller. */
14
+ export declare const MCP_INVOCATION_CONTEXT: ContextKey<McpInvocationContext, "trusted">;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MCP_INVOCATION_CONTEXT = exports.McpInvocationContext = void 0;
4
+ const core_util_1 = require("@webpieces/core-util");
5
+ /** Trusted, context-local MCP facts available to a bound application endpoint. */
6
+ class McpInvocationContext {
7
+ jsonRpcId;
8
+ toolName;
9
+ subject;
10
+ roles;
11
+ signal;
12
+ reportProgress;
13
+ constructor(jsonRpcId, toolName, subject, roles, signal, reportProgress) {
14
+ this.jsonRpcId = jsonRpcId;
15
+ this.toolName = toolName;
16
+ this.subject = subject;
17
+ this.roles = roles;
18
+ this.signal = signal;
19
+ this.reportProgress = reportProgress;
20
+ }
21
+ }
22
+ exports.McpInvocationContext = McpInvocationContext;
23
+ /** Context-only: it has no HTTP header and cannot be asserted by a remote caller. */
24
+ exports.MCP_INVOCATION_CONTEXT = core_util_1.ContextKey.trusted('mcpInvocationContext', 'constructed from the official MCP SDK request and the verified MCP credential', undefined, false, false);
25
+ //# sourceMappingURL=McpInvocationContext.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"McpInvocationContext.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpInvocationContext.ts"],"names":[],"mappings":";;;AAAA,oDAAkD;AAQlD,kFAAkF;AAClF,MAAa,oBAAoB;IAET;IACA;IACA;IACA;IACA;IACA;IANpB,YACoB,SAA0B,EAC1B,QAAgB,EAChB,OAAe,EACf,KAAwB,EACxB,MAAmB,EACnB,cAAoC;QALpC,cAAS,GAAT,SAAS,CAAiB;QAC1B,aAAQ,GAAR,QAAQ,CAAQ;QAChB,YAAO,GAAP,OAAO,CAAQ;QACf,UAAK,GAAL,KAAK,CAAmB;QACxB,WAAM,GAAN,MAAM,CAAa;QACnB,mBAAc,GAAd,cAAc,CAAsB;IACrD,CAAC;CACP;AATD,oDASC;AAED,qFAAqF;AACxE,QAAA,sBAAsB,GAAG,sBAAU,CAAC,OAAO,CACpD,sBAAsB,EACtB,+EAA+E,EAC/E,SAAS,EACT,KAAK,EACL,KAAK,CACR,CAAC","sourcesContent":["import { ContextKey } from '@webpieces/core-util';\n\nexport type McpProgressReporter = (\n progress: number,\n total?: number,\n message?: string,\n) => Promise<void>;\n\n/** Trusted, context-local MCP facts available to a bound application endpoint. */\nexport class McpInvocationContext {\n constructor(\n public readonly jsonRpcId: string | number,\n public readonly toolName: string,\n public readonly subject: string,\n public readonly roles: readonly string[],\n public readonly signal: AbortSignal,\n public readonly reportProgress?: McpProgressReporter,\n ) {}\n}\n\n/** Context-only: it has no HTTP header and cannot be asserted by a remote caller. */\nexport const MCP_INVOCATION_CONTEXT = ContextKey.trusted<McpInvocationContext>(\n 'mcpInvocationContext',\n 'constructed from the official MCP SDK request and the verified MCP credential',\n undefined,\n false,\n false,\n);\n"]}
@@ -1,5 +1,6 @@
1
- import { ApiJsonSchema, AuthMeta, DtoClass, DtoSchemaBuilder, WpMcpToolMetadata } from '@webpieces/core-util';
1
+ import { ApiJsonSchema, AuthMeta, DtoClass, DtoSchemaBuilder, WpMcpJwtAuthMetadata, WpMcpToolMetadata } from '@webpieces/core-util';
2
2
  import { ClassType } from '@webpieces/http-routing';
3
+ import { McpApiBinding } from './McpApiBinding';
3
4
  /** Fully resolved contract metadata for one MCP tool. */
4
5
  export declare class RegisteredMcpTool {
5
6
  readonly apiClass: ClassType;
@@ -8,19 +9,21 @@ export declare class RegisteredMcpTool {
8
9
  readonly description: string;
9
10
  readonly annotations: WpMcpToolMetadata['hints'];
10
11
  readonly authMeta: AuthMeta;
12
+ readonly mcpAuth: WpMcpJwtAuthMetadata;
13
+ readonly binding: McpApiBinding;
11
14
  readonly requestClass: DtoClass;
12
15
  readonly responseClass: DtoClass;
13
16
  readonly inputSchema: ApiJsonSchema;
14
17
  readonly outputSchema: ApiJsonSchema;
15
- constructor(apiClass: ClassType, methodName: string, name: string, description: string, annotations: WpMcpToolMetadata['hints'], authMeta: AuthMeta, requestClass: DtoClass, responseClass: DtoClass, inputSchema: ApiJsonSchema, outputSchema: ApiJsonSchema);
16
- /** Listing is advisory; the ordinary endpoint AuthFilter is the only authorization boundary. */
18
+ constructor(apiClass: ClassType, methodName: string, name: string, description: string, annotations: WpMcpToolMetadata['hints'], authMeta: AuthMeta, mcpAuth: WpMcpJwtAuthMetadata, binding: McpApiBinding, requestClass: DtoClass, responseClass: DtoClass, inputSchema: ApiJsonSchema, outputSchema: ApiJsonSchema);
19
+ /** Listing is advisory; MCP authorization is rechecked before the endpoint auth boundary. */
17
20
  isVisibleTo(listingRoles: readonly string[]): boolean;
18
21
  }
19
22
  /** Fail-fast registry built entirely from API/decorator metadata. */
20
23
  export declare class McpToolRegistry {
21
24
  readonly tools: readonly RegisteredMcpTool[];
22
25
  readonly schemaBuilder: DtoSchemaBuilder;
23
- constructor(apiClasses: readonly ClassType[]);
26
+ constructor(bindings: readonly McpApiBinding[]);
24
27
  find(name: string): RegisteredMcpTool | undefined;
25
28
  private resolve;
26
29
  }
@@ -10,28 +10,30 @@ class RegisteredMcpTool {
10
10
  description;
11
11
  annotations;
12
12
  authMeta;
13
+ mcpAuth;
14
+ binding;
13
15
  requestClass;
14
16
  responseClass;
15
17
  inputSchema;
16
18
  outputSchema;
17
- constructor(apiClass, methodName, name, description, annotations, authMeta, requestClass, responseClass, inputSchema, outputSchema) {
19
+ constructor(apiClass, methodName, name, description, annotations, authMeta, mcpAuth, binding, requestClass, responseClass, inputSchema, outputSchema) {
18
20
  this.apiClass = apiClass;
19
21
  this.methodName = methodName;
20
22
  this.name = name;
21
23
  this.description = description;
22
24
  this.annotations = annotations;
23
25
  this.authMeta = authMeta;
26
+ this.mcpAuth = mcpAuth;
27
+ this.binding = binding;
24
28
  this.requestClass = requestClass;
25
29
  this.responseClass = responseClass;
26
30
  this.inputSchema = inputSchema;
27
31
  this.outputSchema = outputSchema;
28
32
  }
29
- /** Listing is advisory; the ordinary endpoint AuthFilter is the only authorization boundary. */
33
+ /** Listing is advisory; MCP authorization is rechecked before the endpoint auth boundary. */
30
34
  isVisibleTo(listingRoles) {
31
- if (this.authMeta.mode.kind !== 'jwt')
32
- return true;
33
- const required = (0, core_util_1.rolesRequired)(this.authMeta.mode.requirement);
34
- return required.length === 0 || required.some((role) => listingRoles.includes(role));
35
+ const required = (0, core_util_1.rolesRequired)(this.mcpAuth.requirement);
36
+ return (required.length === 0 || required.some((role) => listingRoles.includes(role)));
35
37
  }
36
38
  }
37
39
  exports.RegisteredMcpTool = RegisteredMcpTool;
@@ -39,16 +41,17 @@ exports.RegisteredMcpTool = RegisteredMcpTool;
39
41
  class McpToolRegistry {
40
42
  tools;
41
43
  schemaBuilder = new core_util_1.DtoSchemaBuilder();
42
- constructor(apiClasses) {
44
+ constructor(bindings) {
43
45
  const registered = [];
44
46
  const names = new Set();
45
- for (const apiClass of apiClasses) {
47
+ for (const binding of bindings) {
48
+ const apiClass = binding.api;
46
49
  for (const metadata of (0, core_util_1.getWpMcpTools)(apiClass)) {
47
50
  if (names.has(metadata.name)) {
48
51
  throw new Error(`Duplicate @WpMcpTool name '${metadata.name}'. Tool names must be globally unique.`);
49
52
  }
50
53
  names.add(metadata.name);
51
- registered.push(this.resolve(apiClass, metadata));
54
+ registered.push(this.resolve(apiClass, metadata, binding));
52
55
  }
53
56
  }
54
57
  this.tools = registered;
@@ -56,7 +59,7 @@ class McpToolRegistry {
56
59
  find(name) {
57
60
  return this.tools.find((tool) => tool.name === name);
58
61
  }
59
- resolve(apiClass, metadata) {
62
+ resolve(apiClass, metadata, binding) {
60
63
  const endpoints = (0, core_util_1.getEndpoints)(apiClass) ?? {};
61
64
  if (!endpoints[metadata.methodName]) {
62
65
  throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} must also be an @Endpoint.`);
@@ -65,15 +68,16 @@ class McpToolRegistry {
65
68
  throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} must be an RPC endpoint.`);
66
69
  }
67
70
  const authMeta = (0, core_util_1.getAuthMeta)(apiClass, metadata.methodName);
68
- if (!authMeta || (authMeta.mode.kind !== 'jwt' && authMeta.mode.kind !== 'public')) {
69
- throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} must use @WpAuthJwt or @WpAuthPublic.`);
70
- }
71
- if (authMeta.mode.kind === 'public' && !metadata.hints.readOnlyHint) {
72
- throw new Error(`Public MCP tool ${metadata.name} must be read-only.`);
71
+ if (!authMeta)
72
+ throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} has no HTTP auth.`);
73
+ const mcpAuth = (0, core_util_1.getWpMcpAuthJwt)(apiClass, metadata.methodName);
74
+ if (!mcpAuth) {
75
+ throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} must declare @WpMcpAuthJwt(...).`);
73
76
  }
77
+ binding.validateMethod(metadata.methodName);
74
78
  const requestClass = this.schemaBuilder.requestClassOf(apiClass, metadata.methodName);
75
79
  const responseClass = this.schemaBuilder.responseClassOf(apiClass, metadata.methodName);
76
- return new RegisteredMcpTool(apiClass, metadata.methodName, metadata.name, metadata.description, metadata.hints, authMeta, requestClass, responseClass, this.schemaBuilder.build(requestClass), this.schemaBuilder.build(responseClass));
80
+ return new RegisteredMcpTool(apiClass, metadata.methodName, metadata.name, metadata.description, metadata.hints, authMeta, mcpAuth, binding, requestClass, responseClass, this.schemaBuilder.build(requestClass), this.schemaBuilder.build(responseClass));
77
81
  }
78
82
  }
79
83
  exports.McpToolRegistry = McpToolRegistry;
@@ -1 +1 @@
1
- {"version":3,"file":"McpToolRegistry.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpToolRegistry.ts"],"names":[],"mappings":";;;AAAA,oDAW8B;AAG9B,yDAAyD;AACzD,MAAa,iBAAiB;IAEN;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IAVpB,YACoB,QAAmB,EACnB,UAAkB,EAClB,IAAY,EACZ,WAAmB,EACnB,WAAuC,EACvC,QAAkB,EAClB,YAAsB,EACtB,aAAuB,EACvB,WAA0B,EAC1B,YAA2B;QAT3B,aAAQ,GAAR,QAAQ,CAAW;QACnB,eAAU,GAAV,UAAU,CAAQ;QAClB,SAAI,GAAJ,IAAI,CAAQ;QACZ,gBAAW,GAAX,WAAW,CAAQ;QACnB,gBAAW,GAAX,WAAW,CAA4B;QACvC,aAAQ,GAAR,QAAQ,CAAU;QAClB,iBAAY,GAAZ,YAAY,CAAU;QACtB,kBAAa,GAAb,aAAa,CAAU;QACvB,gBAAW,GAAX,WAAW,CAAe;QAC1B,iBAAY,GAAZ,YAAY,CAAe;IAC5C,CAAC;IAEJ,gGAAgG;IAChG,WAAW,CAAC,YAA+B;QACvC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;QACnD,MAAM,QAAQ,GAAG,IAAA,yBAAa,EAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC/D,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACjG,CAAC;CACJ;AApBD,8CAoBC;AAED,qEAAqE;AACrE,MAAa,eAAe;IACf,KAAK,CAA+B;IACpC,aAAa,GAAG,IAAI,4BAAgB,EAAE,CAAC;IAEhD,YAAY,UAAgC;QACxC,MAAM,UAAU,GAAwB,EAAE,CAAC;QAC3C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;QAChC,KAAK,MAAM,QAAQ,IAAI,UAAU,EAAE,CAAC;YAChC,KAAK,MAAM,QAAQ,IAAI,IAAA,yBAAa,EAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7C,IAAI,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC3B,MAAM,IAAI,KAAK,CAAC,8BAA8B,QAAQ,CAAC,IAAI,wCAAwC,CAAC,CAAC;gBACzG,CAAC;gBACD,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACzB,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;YACtD,CAAC;QACL,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC;IAC5B,CAAC;IAED,IAAI,CAAC,IAAY;QACb,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAuB,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IAC5E,CAAC;IAEO,OAAO,CAAC,QAAmB,EAAE,QAA2B;QAC5D,MAAM,SAAS,GAAG,IAAA,wBAAY,EAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAC/C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,cAAc,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,UAAU,6BAA6B,CAAC,CAAC;QACrG,CAAC;QACD,IAAI,IAAA,2BAAe,EAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,KAAK,KAAK,EAAE,CAAC;YAC3D,MAAM,IAAI,KAAK,CAAC,cAAc,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,UAAU,2BAA2B,CAAC,CAAC;QACnG,CAAC;QACD,MAAM,QAAQ,GAAG,IAAA,uBAAW,EAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QAC5D,IAAI,CAAC,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,KAAK,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,EAAE,CAAC;YACjF,MAAM,IAAI,KAAK,CACX,cAAc,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,UAAU,wCAAwC,CAC7F,CAAC;QACN,CAAC;QACD,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;YAClE,MAAM,IAAI,KAAK,CAAC,mBAAmB,QAAQ,CAAC,IAAI,qBAAqB,CAAC,CAAC;QAC3E,CAAC;QACD,MAAM,YAAY,GAAG,IAAI,CAAC,aAAa,CAAC,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QACtF,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;QACxF,OAAO,IAAI,iBAAiB,CACxB,QAAQ,EACR,QAAQ,CAAC,UAAU,EACnB,QAAQ,CAAC,IAAI,EACb,QAAQ,CAAC,WAAW,EACpB,QAAQ,CAAC,KAAK,EACd,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,YAAY,CAAC,EACtC,IAAI,CAAC,aAAa,CAAC,KAAK,CAAC,aAAa,CAAC,CAC1C,CAAC;IACN,CAAC;CACJ;AAvDD,0CAuDC","sourcesContent":["import {\n ApiJsonSchema,\n AuthMeta,\n DtoClass,\n DtoSchemaBuilder,\n getAuthMeta,\n getEndpointKind,\n getEndpoints,\n getWpMcpTools,\n rolesRequired,\n WpMcpToolMetadata,\n} from '@webpieces/core-util';\nimport { ClassType } from '@webpieces/http-routing';\n\n/** Fully resolved contract metadata for one MCP tool. */\nexport class RegisteredMcpTool {\n constructor(\n public readonly apiClass: ClassType,\n public readonly methodName: string,\n public readonly name: string,\n public readonly description: string,\n public readonly annotations: WpMcpToolMetadata['hints'],\n public readonly authMeta: AuthMeta,\n public readonly requestClass: DtoClass,\n public readonly responseClass: DtoClass,\n public readonly inputSchema: ApiJsonSchema,\n public readonly outputSchema: ApiJsonSchema,\n ) {}\n\n /** Listing is advisory; the ordinary endpoint AuthFilter is the only authorization boundary. */\n isVisibleTo(listingRoles: readonly string[]): boolean {\n if (this.authMeta.mode.kind !== 'jwt') return true;\n const required = rolesRequired(this.authMeta.mode.requirement);\n return required.length === 0 || required.some((role: string) => listingRoles.includes(role));\n }\n}\n\n/** Fail-fast registry built entirely from API/decorator metadata. */\nexport class McpToolRegistry {\n readonly tools: readonly RegisteredMcpTool[];\n readonly schemaBuilder = new DtoSchemaBuilder();\n\n constructor(apiClasses: readonly ClassType[]) {\n const registered: RegisteredMcpTool[] = [];\n const names = new Set<string>();\n for (const apiClass of apiClasses) {\n for (const metadata of getWpMcpTools(apiClass)) {\n if (names.has(metadata.name)) {\n throw new Error(`Duplicate @WpMcpTool name '${metadata.name}'. Tool names must be globally unique.`);\n }\n names.add(metadata.name);\n registered.push(this.resolve(apiClass, metadata));\n }\n }\n this.tools = registered;\n }\n\n find(name: string): RegisteredMcpTool | undefined {\n return this.tools.find((tool: RegisteredMcpTool) => tool.name === name);\n }\n\n private resolve(apiClass: ClassType, metadata: WpMcpToolMetadata): RegisteredMcpTool {\n const endpoints = getEndpoints(apiClass) ?? {};\n if (!endpoints[metadata.methodName]) {\n throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} must also be an @Endpoint.`);\n }\n if (getEndpointKind(apiClass, metadata.methodName) !== 'rpc') {\n throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} must be an RPC endpoint.`);\n }\n const authMeta = getAuthMeta(apiClass, metadata.methodName);\n if (!authMeta || (authMeta.mode.kind !== 'jwt' && authMeta.mode.kind !== 'public')) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must use @WpAuthJwt or @WpAuthPublic.`,\n );\n }\n if (authMeta.mode.kind === 'public' && !metadata.hints.readOnlyHint) {\n throw new Error(`Public MCP tool ${metadata.name} must be read-only.`);\n }\n const requestClass = this.schemaBuilder.requestClassOf(apiClass, metadata.methodName);\n const responseClass = this.schemaBuilder.responseClassOf(apiClass, metadata.methodName);\n return new RegisteredMcpTool(\n apiClass,\n metadata.methodName,\n metadata.name,\n metadata.description,\n metadata.hints,\n authMeta,\n requestClass,\n responseClass,\n this.schemaBuilder.build(requestClass),\n this.schemaBuilder.build(responseClass),\n );\n }\n}\n"]}
1
+ {"version":3,"file":"McpToolRegistry.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/McpToolRegistry.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n ApiJsonSchema,\n AuthMeta,\n DtoClass,\n DtoSchemaBuilder,\n getAuthMeta,\n getEndpointKind,\n getEndpoints,\n getWpMcpTools,\n getWpMcpAuthJwt,\n rolesRequired,\n WpMcpJwtAuthMetadata,\n WpMcpToolMetadata,\n} from '@webpieces/core-util';\nimport { ClassType } from '@webpieces/http-routing';\nimport { McpApiBinding } from './McpApiBinding';\n\n/** Fully resolved contract metadata for one MCP tool. */\nexport class RegisteredMcpTool {\n constructor(\n public readonly apiClass: ClassType,\n public readonly methodName: string,\n public readonly name: string,\n public readonly description: string,\n public readonly annotations: WpMcpToolMetadata['hints'],\n public readonly authMeta: AuthMeta,\n public readonly mcpAuth: WpMcpJwtAuthMetadata,\n public readonly binding: McpApiBinding,\n public readonly requestClass: DtoClass,\n public readonly responseClass: DtoClass,\n public readonly inputSchema: ApiJsonSchema,\n public readonly outputSchema: ApiJsonSchema,\n ) {}\n\n /** Listing is advisory; MCP authorization is rechecked before the endpoint auth boundary. */\n isVisibleTo(listingRoles: readonly string[]): boolean {\n const required = rolesRequired(this.mcpAuth.requirement);\n return (\n required.length === 0 || required.some((role: string) => listingRoles.includes(role))\n );\n }\n}\n\n/** Fail-fast registry built entirely from API/decorator metadata. */\nexport class McpToolRegistry {\n readonly tools: readonly RegisteredMcpTool[];\n readonly schemaBuilder = new DtoSchemaBuilder();\n\n constructor(bindings: readonly McpApiBinding[]) {\n const registered: RegisteredMcpTool[] = [];\n const names = new Set<string>();\n for (const binding of bindings) {\n const apiClass = binding.api;\n for (const metadata of getWpMcpTools(apiClass)) {\n if (names.has(metadata.name)) {\n throw new Error(\n `Duplicate @WpMcpTool name '${metadata.name}'. Tool names must be globally unique.`,\n );\n }\n names.add(metadata.name);\n registered.push(this.resolve(apiClass, metadata, binding));\n }\n }\n this.tools = registered;\n }\n\n find(name: string): RegisteredMcpTool | undefined {\n return this.tools.find((tool: RegisteredMcpTool) => tool.name === name);\n }\n\n private resolve(\n apiClass: ClassType,\n metadata: WpMcpToolMetadata,\n binding: McpApiBinding,\n ): RegisteredMcpTool {\n const endpoints = getEndpoints(apiClass) ?? {};\n if (!endpoints[metadata.methodName]) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must also be an @Endpoint.`,\n );\n }\n if (getEndpointKind(apiClass, metadata.methodName) !== 'rpc') {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must be an RPC endpoint.`,\n );\n }\n const authMeta = getAuthMeta(apiClass, metadata.methodName);\n if (!authMeta)\n throw new Error(`@WpMcpTool ${apiClass.name}.${metadata.methodName} has no HTTP auth.`);\n const mcpAuth = getWpMcpAuthJwt(apiClass, metadata.methodName);\n if (!mcpAuth) {\n throw new Error(\n `@WpMcpTool ${apiClass.name}.${metadata.methodName} must declare @WpMcpAuthJwt(...).`,\n );\n }\n binding.validateMethod(metadata.methodName);\n const requestClass = this.schemaBuilder.requestClassOf(apiClass, metadata.methodName);\n const responseClass = this.schemaBuilder.responseClassOf(apiClass, metadata.methodName);\n return new RegisteredMcpTool(\n apiClass,\n metadata.methodName,\n metadata.name,\n metadata.description,\n metadata.hints,\n authMeta,\n mcpAuth,\n binding,\n requestClass,\n responseClass,\n this.schemaBuilder.build(requestClass),\n this.schemaBuilder.build(responseClass),\n );\n }\n}\n"]}
@@ -0,0 +1,69 @@
1
+ import { CallToolResult, ProtocolError } from '@modelcontextprotocol/server';
2
+ import { Response } from 'express';
3
+ /** `_meta` key carrying the Webpieces requestId on every tools/call result, success or failure. */
4
+ export declare const MCP_REQUEST_ID_META_KEY = "webpieces/requestId";
5
+ /** JSON-RPC error `data` so a user can quote the requestId of any protocol-level failure. */
6
+ export declare class McpErrorData {
7
+ readonly requestId: string;
8
+ constructor(requestId: string);
9
+ }
10
+ /** Correlation facts for one failure; rendered into the single operator log line. */
11
+ export declare class McpFailureScope {
12
+ readonly requestId: string;
13
+ readonly jsonRpcId: string | number | null;
14
+ readonly method: string;
15
+ readonly toolName?: string | undefined;
16
+ constructor(requestId: string, jsonRpcId: string | number | null, method: string, toolName?: string | undefined);
17
+ describe(): string;
18
+ }
19
+ /** The only error shape rendered into model-visible MCP tool content. */
20
+ export declare class ModelVisibleToolError {
21
+ readonly kind: string;
22
+ readonly message: string;
23
+ readonly requestId: string;
24
+ readonly field?: string | undefined;
25
+ readonly callerMessage?: string | undefined;
26
+ readonly errorCode?: string | undefined;
27
+ readonly retryAfterSeconds?: number | undefined;
28
+ constructor(kind: string, message: string, requestId: string, field?: string | undefined, callerMessage?: string | undefined, errorCode?: string | undefined, retryAfterSeconds?: number | undefined);
29
+ }
30
+ /** JSON-RPC error body written by the HTTP boundary before the SDK is involved. */
31
+ export declare class McpHttpErrorBody {
32
+ readonly error: McpHttpErrorDetail;
33
+ readonly id: string | number | null;
34
+ readonly jsonrpc = "2.0";
35
+ constructor(error: McpHttpErrorDetail, id: string | number | null);
36
+ }
37
+ export declare class McpHttpErrorDetail {
38
+ readonly code: number;
39
+ readonly message: string;
40
+ readonly data: McpErrorData;
41
+ constructor(code: number, message: string, data: McpErrorData);
42
+ }
43
+ /**
44
+ * The ONE place every MCP failure is mapped to a reply, mirroring `ApiErrorHttpMapper` for HTTP.
45
+ * Each MCP entry point (the bind HTTP handler, tools/list, tools/call) has exactly one catch that
46
+ * only delegates here. Normalization and per-kind log levels are the shared `ApiErrorBoundary`
47
+ * rules, and published text comes only from `ApiErrorCodec.encode`: this class never puts an
48
+ * error's own message on the wire unless it is an `ApiEndUserError`.
49
+ */
50
+ export declare class WpMcpErrorTranslator {
51
+ private readonly boundary;
52
+ /**
53
+ * Pre-SDK failures: 401 + WWW-Authenticate, 403, 400 (-32700), anything else 500 (-32603).
54
+ * A failure after response headers were sent can only end the stream.
55
+ */
56
+ toHttp(thrown: Error, res: Response, scope: McpFailureScope, wwwAuthenticate: string): void;
57
+ /** Express body-parser failures (malformed JSON, body too large) are caller input errors. */
58
+ toHttpBodyFailure(cause: Error, res: Response, scope: McpFailureScope, wwwAuth: string): void;
59
+ /** tools/list and other result-less methods: bad-request → -32602, anything else → -32603. */
60
+ toProtocolError(thrown: Error, scope: McpFailureScope): ProtocolError;
61
+ /** tools/call with a name no binding registered: a JSON-RPC protocol error per MCP 2026-07-28. */
62
+ unknownTool(toolName: string, scope: McpFailureScope): ProtocolError;
63
+ /** Every tools/call failure after the tool is found: an `isError: true` result the model sees. */
64
+ toToolResult(thrown: Error, scope: McpFailureScope): CallToolResult;
65
+ /** The `_meta` every tools/call result carries so a user can quote its requestId. */
66
+ resultMeta(requestId: string): Record<string, string>;
67
+ private modelVisible;
68
+ private writeHttp;
69
+ }
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WpMcpErrorTranslator = exports.McpHttpErrorDetail = exports.McpHttpErrorBody = exports.ModelVisibleToolError = exports.McpFailureScope = exports.McpErrorData = exports.MCP_REQUEST_ID_META_KEY = void 0;
4
+ const server_1 = require("@modelcontextprotocol/server");
5
+ const core_util_1 = require("@webpieces/core-util");
6
+ const log = core_util_1.LogManager.getLogger('WpMcpErrorTranslator');
7
+ /** `_meta` key carrying the Webpieces requestId on every tools/call result, success or failure. */
8
+ exports.MCP_REQUEST_ID_META_KEY = 'webpieces/requestId';
9
+ /** JSON-RPC error `data` so a user can quote the requestId of any protocol-level failure. */
10
+ class McpErrorData {
11
+ requestId;
12
+ constructor(requestId) {
13
+ this.requestId = requestId;
14
+ }
15
+ }
16
+ exports.McpErrorData = McpErrorData;
17
+ /** Correlation facts for one failure; rendered into the single operator log line. */
18
+ class McpFailureScope {
19
+ requestId;
20
+ jsonRpcId;
21
+ method;
22
+ toolName;
23
+ constructor(requestId, jsonRpcId, method, toolName) {
24
+ this.requestId = requestId;
25
+ this.jsonRpcId = jsonRpcId;
26
+ this.method = method;
27
+ this.toolName = toolName;
28
+ }
29
+ describe() {
30
+ const tool = this.toolName ? ` tool=${this.toolName}` : '';
31
+ return `requestId=${this.requestId} jsonRpcId=${String(this.jsonRpcId)} method=${this.method}${tool}`;
32
+ }
33
+ }
34
+ exports.McpFailureScope = McpFailureScope;
35
+ /** The only error shape rendered into model-visible MCP tool content. */
36
+ class ModelVisibleToolError {
37
+ kind;
38
+ message;
39
+ requestId;
40
+ field;
41
+ callerMessage;
42
+ errorCode;
43
+ retryAfterSeconds;
44
+ constructor(kind, message, requestId, field, callerMessage, errorCode, retryAfterSeconds) {
45
+ this.kind = kind;
46
+ this.message = message;
47
+ this.requestId = requestId;
48
+ this.field = field;
49
+ this.callerMessage = callerMessage;
50
+ this.errorCode = errorCode;
51
+ this.retryAfterSeconds = retryAfterSeconds;
52
+ }
53
+ }
54
+ exports.ModelVisibleToolError = ModelVisibleToolError;
55
+ /** JSON-RPC error body written by the HTTP boundary before the SDK is involved. */
56
+ class McpHttpErrorBody {
57
+ error;
58
+ id;
59
+ jsonrpc = '2.0';
60
+ constructor(error, id) {
61
+ this.error = error;
62
+ this.id = id;
63
+ }
64
+ }
65
+ exports.McpHttpErrorBody = McpHttpErrorBody;
66
+ class McpHttpErrorDetail {
67
+ code;
68
+ message;
69
+ data;
70
+ constructor(code, message, data) {
71
+ this.code = code;
72
+ this.message = message;
73
+ this.data = data;
74
+ }
75
+ }
76
+ exports.McpHttpErrorDetail = McpHttpErrorDetail;
77
+ /**
78
+ * The ONE place every MCP failure is mapped to a reply, mirroring `ApiErrorHttpMapper` for HTTP.
79
+ * Each MCP entry point (the bind HTTP handler, tools/list, tools/call) has exactly one catch that
80
+ * only delegates here. Normalization and per-kind log levels are the shared `ApiErrorBoundary`
81
+ * rules, and published text comes only from `ApiErrorCodec.encode`: this class never puts an
82
+ * error's own message on the wire unless it is an `ApiEndUserError`.
83
+ */
84
+ class WpMcpErrorTranslator {
85
+ boundary = new core_util_1.ApiErrorBoundary(log);
86
+ /**
87
+ * Pre-SDK failures: 401 + WWW-Authenticate, 403, 400 (-32700), anything else 500 (-32603).
88
+ * A failure after response headers were sent can only end the stream.
89
+ */
90
+ toHttp(thrown, res, scope, wwwAuthenticate) {
91
+ const error = this.boundary.normalize(thrown);
92
+ this.boundary.logOperatorDetail(error, scope.describe());
93
+ if (res.headersSent) {
94
+ res.end();
95
+ return;
96
+ }
97
+ const generic = core_util_1.ApiErrorCodec.encode(error).message;
98
+ switch (error.kind) {
99
+ case 'unauthorized':
100
+ res.setHeader('WWW-Authenticate', wwwAuthenticate);
101
+ this.writeHttp(res, 401, -32_000, generic, scope);
102
+ return;
103
+ case 'forbidden':
104
+ this.writeHttp(res, 403, -32_000, generic, scope);
105
+ return;
106
+ case 'bad-request':
107
+ this.writeHttp(res, 400, server_1.ProtocolErrorCode.ParseError, generic, scope);
108
+ return;
109
+ default:
110
+ this.writeHttp(res, 500, server_1.ProtocolErrorCode.InternalError, 'Internal Error', scope);
111
+ }
112
+ }
113
+ /** Express body-parser failures (malformed JSON, body too large) are caller input errors. */
114
+ toHttpBodyFailure(cause, res, scope, wwwAuth) {
115
+ this.toHttp(new core_util_1.ApiBadRequestError(`MCP request body rejected: ${cause.message}`, undefined, undefined, cause), res, scope, wwwAuth);
116
+ }
117
+ /** tools/list and other result-less methods: bad-request → -32602, anything else → -32603. */
118
+ toProtocolError(thrown, scope) {
119
+ const error = this.boundary.normalize(thrown);
120
+ this.boundary.logOperatorDetail(error, scope.describe());
121
+ const payload = core_util_1.ApiErrorCodec.encode(error);
122
+ if (error.kind === 'bad-request') {
123
+ return new server_1.ProtocolError(server_1.ProtocolErrorCode.InvalidParams, payload.callerMessage ?? payload.message, new McpErrorData(scope.requestId));
124
+ }
125
+ const message = error.kind === 'end-user' ? payload.message : 'Internal Error';
126
+ return new server_1.ProtocolError(server_1.ProtocolErrorCode.InternalError, message, new McpErrorData(scope.requestId));
127
+ }
128
+ /** tools/call with a name no binding registered: a JSON-RPC protocol error per MCP 2026-07-28. */
129
+ unknownTool(toolName, scope) {
130
+ log.info(`Unknown MCP tool: ${scope.describe()}`);
131
+ return new server_1.ProtocolError(server_1.ProtocolErrorCode.InvalidParams, `Unknown tool: ${toolName}`, new McpErrorData(scope.requestId));
132
+ }
133
+ /** Every tools/call failure after the tool is found: an `isError: true` result the model sees. */
134
+ toToolResult(thrown, scope) {
135
+ const error = this.boundary.normalize(thrown);
136
+ this.boundary.logOperatorDetail(error, scope.describe());
137
+ const visible = this.modelVisible(core_util_1.ApiErrorCodec.encode(error), scope);
138
+ const text = JSON.stringify(visible);
139
+ return {
140
+ content: [{ type: 'text', text }],
141
+ isError: true,
142
+ _meta: this.resultMeta(scope.requestId),
143
+ };
144
+ }
145
+ /** The `_meta` every tools/call result carries so a user can quote its requestId. */
146
+ resultMeta(requestId) {
147
+ return { [exports.MCP_REQUEST_ID_META_KEY]: requestId };
148
+ }
149
+ modelVisible(payload, scope) {
150
+ switch (payload.kind) {
151
+ case 'end-user':
152
+ return new ModelVisibleToolError(payload.kind, payload.message, scope.requestId, undefined, undefined, payload.errorCode);
153
+ case 'bad-request':
154
+ return new ModelVisibleToolError(payload.kind, payload.callerMessage ?? payload.message, scope.requestId, payload.field, payload.callerMessage);
155
+ case 'implementation':
156
+ return new ModelVisibleToolError(payload.kind, `Internal error in tool ${scope.toolName ?? 'unknown'} (requestId ${scope.requestId}). ` +
157
+ 'This is a bug in the tool, not in your arguments; retrying with different ' +
158
+ 'arguments will not help.', scope.requestId);
159
+ default:
160
+ return new ModelVisibleToolError(payload.kind, payload.message, scope.requestId, undefined, undefined, undefined, payload.retryAfterSeconds);
161
+ }
162
+ }
163
+ writeHttp(res, status, code, message, scope) {
164
+ res.status(status).json(new McpHttpErrorBody(new McpHttpErrorDetail(code, message, new McpErrorData(scope.requestId)), scope.jsonRpcId));
165
+ }
166
+ }
167
+ exports.WpMcpErrorTranslator = WpMcpErrorTranslator;
168
+ //# sourceMappingURL=WpMcpErrorTranslator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WpMcpErrorTranslator.js","sourceRoot":"","sources":["../../../../../packages/http/mcp-server/src/WpMcpErrorTranslator.ts"],"names":[],"mappings":"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","sourcesContent":["import { CallToolResult, ProtocolError, ProtocolErrorCode } from '@modelcontextprotocol/server';\nimport { Response } from 'express';\nimport {\n ApiBadRequestError,\n ApiErrorBoundary,\n ApiErrorCodec,\n ApiErrorPayload,\n DtoValue,\n LogManager,\n} from '@webpieces/core-util';\n\nconst log = LogManager.getLogger('WpMcpErrorTranslator');\n\n/** `_meta` key carrying the Webpieces requestId on every tools/call result, success or failure. */\nexport const MCP_REQUEST_ID_META_KEY = 'webpieces/requestId';\n\n/** JSON-RPC error `data` so a user can quote the requestId of any protocol-level failure. */\nexport class McpErrorData {\n constructor(public readonly requestId: string) {}\n}\n\n/** Correlation facts for one failure; rendered into the single operator log line. */\nexport class McpFailureScope {\n constructor(\n public readonly requestId: string,\n public readonly jsonRpcId: string | number | null,\n public readonly method: string,\n public readonly toolName?: string,\n ) {}\n\n describe(): string {\n const tool = this.toolName ? ` tool=${this.toolName}` : '';\n return `requestId=${this.requestId} jsonRpcId=${String(this.jsonRpcId)} method=${this.method}${tool}`;\n }\n}\n\n/** The only error shape rendered into model-visible MCP tool content. */\nexport class ModelVisibleToolError {\n constructor(\n public readonly kind: string,\n public readonly message: string,\n public readonly requestId: string,\n public readonly field?: string,\n public readonly callerMessage?: string,\n public readonly errorCode?: string,\n public readonly retryAfterSeconds?: number,\n ) {}\n}\n\n/** JSON-RPC error body written by the HTTP boundary before the SDK is involved. */\nexport class McpHttpErrorBody {\n readonly jsonrpc = '2.0';\n constructor(\n public readonly error: McpHttpErrorDetail,\n public readonly id: string | number | null,\n ) {}\n}\n\nexport class McpHttpErrorDetail {\n constructor(\n public readonly code: number,\n public readonly message: string,\n public readonly data: McpErrorData,\n ) {}\n}\n\n/**\n * The ONE place every MCP failure is mapped to a reply, mirroring `ApiErrorHttpMapper` for HTTP.\n * Each MCP entry point (the bind HTTP handler, tools/list, tools/call) has exactly one catch that\n * only delegates here. Normalization and per-kind log levels are the shared `ApiErrorBoundary`\n * rules, and published text comes only from `ApiErrorCodec.encode`: this class never puts an\n * error's own message on the wire unless it is an `ApiEndUserError`.\n */\nexport class WpMcpErrorTranslator {\n private readonly boundary = new ApiErrorBoundary(log);\n\n /**\n * Pre-SDK failures: 401 + WWW-Authenticate, 403, 400 (-32700), anything else 500 (-32603).\n * A failure after response headers were sent can only end the stream.\n */\n toHttp(thrown: Error, res: Response, scope: McpFailureScope, wwwAuthenticate: string): void {\n const error = this.boundary.normalize(thrown);\n this.boundary.logOperatorDetail(error, scope.describe());\n if (res.headersSent) {\n res.end();\n return;\n }\n const generic = ApiErrorCodec.encode(error).message;\n switch (error.kind) {\n case 'unauthorized':\n res.setHeader('WWW-Authenticate', wwwAuthenticate);\n this.writeHttp(res, 401, -32_000, generic, scope);\n return;\n case 'forbidden':\n this.writeHttp(res, 403, -32_000, generic, scope);\n return;\n case 'bad-request':\n this.writeHttp(res, 400, ProtocolErrorCode.ParseError, generic, scope);\n return;\n default:\n this.writeHttp(res, 500, ProtocolErrorCode.InternalError, 'Internal Error', scope);\n }\n }\n\n /** Express body-parser failures (malformed JSON, body too large) are caller input errors. */\n toHttpBodyFailure(cause: Error, res: Response, scope: McpFailureScope, wwwAuth: string): void {\n this.toHttp(\n new ApiBadRequestError(\n `MCP request body rejected: ${cause.message}`,\n undefined,\n undefined,\n cause,\n ),\n res,\n scope,\n wwwAuth,\n );\n }\n\n /** tools/list and other result-less methods: bad-request → -32602, anything else → -32603. */\n toProtocolError(thrown: Error, scope: McpFailureScope): ProtocolError {\n const error = this.boundary.normalize(thrown);\n this.boundary.logOperatorDetail(error, scope.describe());\n const payload = ApiErrorCodec.encode(error);\n if (error.kind === 'bad-request') {\n return new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n payload.callerMessage ?? payload.message,\n new McpErrorData(scope.requestId),\n );\n }\n const message = error.kind === 'end-user' ? payload.message : 'Internal Error';\n return new ProtocolError(\n ProtocolErrorCode.InternalError,\n message,\n new McpErrorData(scope.requestId),\n );\n }\n\n /** tools/call with a name no binding registered: a JSON-RPC protocol error per MCP 2026-07-28. */\n unknownTool(toolName: string, scope: McpFailureScope): ProtocolError {\n log.info(`Unknown MCP tool: ${scope.describe()}`);\n return new ProtocolError(\n ProtocolErrorCode.InvalidParams,\n `Unknown tool: ${toolName}`,\n new McpErrorData(scope.requestId),\n );\n }\n\n /** Every tools/call failure after the tool is found: an `isError: true` result the model sees. */\n toToolResult(thrown: Error, scope: McpFailureScope): CallToolResult {\n const error = this.boundary.normalize(thrown);\n this.boundary.logOperatorDetail(error, scope.describe());\n const visible = this.modelVisible(ApiErrorCodec.encode(error), scope);\n const text = JSON.stringify(visible as DtoValue);\n return {\n content: [{ type: 'text', text }],\n isError: true,\n _meta: this.resultMeta(scope.requestId),\n };\n }\n\n /** The `_meta` every tools/call result carries so a user can quote its requestId. */\n resultMeta(requestId: string): Record<string, string> {\n return { [MCP_REQUEST_ID_META_KEY]: requestId };\n }\n\n private modelVisible(payload: ApiErrorPayload, scope: McpFailureScope): ModelVisibleToolError {\n switch (payload.kind) {\n case 'end-user':\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n scope.requestId,\n undefined,\n undefined,\n payload.errorCode,\n );\n case 'bad-request':\n return new ModelVisibleToolError(\n payload.kind,\n payload.callerMessage ?? payload.message,\n scope.requestId,\n payload.field,\n payload.callerMessage,\n );\n case 'implementation':\n return new ModelVisibleToolError(\n payload.kind,\n `Internal error in tool ${scope.toolName ?? 'unknown'} (requestId ${scope.requestId}). ` +\n 'This is a bug in the tool, not in your arguments; retrying with different ' +\n 'arguments will not help.',\n scope.requestId,\n );\n default:\n return new ModelVisibleToolError(\n payload.kind,\n payload.message,\n scope.requestId,\n undefined,\n undefined,\n undefined,\n payload.retryAfterSeconds,\n );\n }\n }\n\n private writeHttp(\n res: Response,\n status: number,\n code: number,\n message: string,\n scope: McpFailureScope,\n ): void {\n res.status(status).json(\n new McpHttpErrorBody(\n new McpHttpErrorDetail(code, message, new McpErrorData(scope.requestId)),\n scope.jsonRpcId,\n ),\n );\n }\n}\n"]}
@@ -1,34 +1,65 @@
1
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
- import { ApiFactory, ClassType } from '@webpieces/http-routing';
1
+ import { Express } from 'express';
3
2
  import { McpProtectedResourceMetadata, WpMcpServerConfig } from './McpAuth';
4
- /** The only error shape rendered into model-visible MCP content. */
5
- export declare class ModelVisibleToolError {
6
- readonly kind: string;
7
- readonly message: string;
8
- readonly requestId?: string | undefined;
9
- readonly field?: string | undefined;
10
- readonly callerMessage?: string | undefined;
11
- readonly errorCode?: string | undefined;
12
- readonly retryAfterSeconds?: number | undefined;
13
- constructor(kind: string, message: string, requestId?: string | undefined, field?: string | undefined, callerMessage?: string | undefined, errorCode?: string | undefined, retryAfterSeconds?: number | undefined);
14
- }
3
+ import { McpBindOptions } from './McpBindOptions';
15
4
  /**
16
- * Node-only MCP adapter. Build one Server per authenticated transport/session; every tool call still
17
- * re-enters the endpoint's ordinary AuthFilter, so tools/list visibility never grants access.
5
+ * Modern-only MCP 2026-07-28 adapter around the official SDK's request-scoped HTTP handler.
6
+ *
7
+ * Error boundary: every failure is mapped by the one `WpMcpErrorTranslator`, and each entry point
8
+ * has exactly one catch that only delegates to it: the bind HTTP handler (`toHttp`), tools/list
9
+ * (`toProtocolError`) and tools/call (`toToolResult`). `subscriptions/listen` is served entirely by
10
+ * the SDK's listen router, so Webpieces has no handler (and no catch) there. The external bearer is
11
+ * verified exactly once per POST, at the HTTP boundary, before the SDK is involved.
18
12
  */
19
13
  export declare class WpMcpServer<TGrant, TMintRequest> {
20
14
  private readonly config;
21
- private readonly registry;
22
15
  private readonly dispatcher;
23
- constructor(config: WpMcpServerConfig<TGrant, TMintRequest>, apiFactory: ApiFactory, apiClasses: readonly ClassType[]);
24
- build(accessToken: string): Promise<Server>;
16
+ private readonly translator;
17
+ private registry?;
18
+ private handler?;
19
+ private streamingHandler?;
20
+ private nodeHandler?;
21
+ private streamingNodeHandler?;
22
+ private revision?;
23
+ constructor(config: WpMcpServerConfig<TGrant, TMintRequest>);
24
+ bind(app: Express, options: McpBindOptions): void;
25
25
  protectedResourceMetadata(): McpProtectedResourceMetadata;
26
- private buildForAccessToken;
27
- private verifyCredential;
26
+ /** Gracefully closes active listen/request streams; clients must reconnect and refresh lists. */
27
+ close(): Promise<void>;
28
+ /** Level-triggered invalidation; the authoritative list is always fetched again by clients. */
29
+ toolsChanged(): void;
30
+ get registryRevision(): string | undefined;
31
+ private createHandler;
32
+ /** Entry point #1: the one catch for everything before and around the SDK exchange. */
33
+ private handleHttp;
34
+ private serveExpress;
35
+ /** Verifies the external bearer once for this POST. Handlers never re-verify it. */
36
+ private authenticate;
37
+ private disconnectSignal;
38
+ private buildSdkServer;
39
+ /** Entry point #2: tools/list has no tool-result channel, so failures are JSON-RPC errors. */
40
+ private handleListTools;
41
+ private listTools;
42
+ private toolDefinition;
43
+ /**
44
+ * Entry point #3: an unknown tool is a JSON-RPC -32602; every failure after the tool is found
45
+ * is an `isError: true` result, identically for local and remote bindings.
46
+ */
47
+ private handleCallTool;
48
+ /** Everything a tool call does. No catch in here: failures propagate to handleCallTool. */
49
+ private callTool;
50
+ private mintEndpointJwt;
28
51
  private validateCredential;
29
- private call;
30
- private validateEndpointJwt;
31
- private apiError;
32
- private errorResult;
52
+ private authInfo;
53
+ private progressReporter;
54
+ private progressParams;
55
+ private requestId;
56
+ private sdkScope;
57
+ private httpScope;
58
+ private challenge;
59
+ private bodyObject;
60
+ private hasProgressToken;
61
+ private toHttpRequest;
62
+ private calculateRegistryRevision;
63
+ private requireRegistry;
33
64
  private record;
34
65
  }