mcpose 2.1.1 → 3.1.0

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 (101) hide show
  1. package/README.md +461 -62
  2. package/dist/backendClient.d.ts +1 -1
  3. package/dist/backendClient.d.ts.map +1 -1
  4. package/dist/backendClient.js +6 -1
  5. package/dist/backendClient.js.map +1 -1
  6. package/dist/core.d.ts +309 -35
  7. package/dist/core.d.ts.map +1 -1
  8. package/dist/core.js +1023 -186
  9. package/dist/core.js.map +1 -1
  10. package/dist/delegation.d.ts +89 -0
  11. package/dist/delegation.d.ts.map +1 -0
  12. package/dist/delegation.js +175 -0
  13. package/dist/delegation.js.map +1 -0
  14. package/dist/eventStore.d.ts +12 -0
  15. package/dist/eventStore.d.ts.map +1 -1
  16. package/dist/eventStore.js +43 -5
  17. package/dist/eventStore.js.map +1 -1
  18. package/dist/hiddenTools.d.ts +42 -0
  19. package/dist/hiddenTools.d.ts.map +1 -0
  20. package/dist/hiddenTools.js +53 -0
  21. package/dist/hiddenTools.js.map +1 -0
  22. package/dist/index.d.ts +15 -6
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +7 -3
  25. package/dist/index.js.map +1 -1
  26. package/dist/mesh.d.ts +82 -0
  27. package/dist/mesh.d.ts.map +1 -0
  28. package/dist/mesh.js +141 -0
  29. package/dist/mesh.js.map +1 -0
  30. package/dist/middleware.d.ts +13 -0
  31. package/dist/middleware.d.ts.map +1 -1
  32. package/dist/middleware.js +27 -3
  33. package/dist/middleware.js.map +1 -1
  34. package/dist/proxyContext.d.ts +65 -5
  35. package/dist/proxyContext.d.ts.map +1 -1
  36. package/dist/proxyContext.js +39 -4
  37. package/dist/proxyContext.js.map +1 -1
  38. package/dist/rejection.d.ts +7 -1
  39. package/dist/rejection.d.ts.map +1 -1
  40. package/dist/rejection.js +24 -1
  41. package/dist/rejection.js.map +1 -1
  42. package/dist/sanitizeCatalog.d.ts +37 -0
  43. package/dist/sanitizeCatalog.d.ts.map +1 -0
  44. package/dist/sanitizeCatalog.js +92 -0
  45. package/dist/sanitizeCatalog.js.map +1 -0
  46. package/dist/sessionRegistry.d.ts +79 -0
  47. package/dist/sessionRegistry.d.ts.map +1 -0
  48. package/dist/sessionRegistry.js +83 -0
  49. package/dist/sessionRegistry.js.map +1 -0
  50. package/dist/telemetry.d.ts +29 -1
  51. package/dist/telemetry.d.ts.map +1 -1
  52. package/dist/testing.d.ts +18 -2
  53. package/dist/testing.d.ts.map +1 -1
  54. package/dist/testing.js +30 -8
  55. package/dist/testing.js.map +1 -1
  56. package/dist/version.d.ts +7 -0
  57. package/dist/version.d.ts.map +1 -0
  58. package/dist/version.js +7 -0
  59. package/dist/version.js.map +1 -0
  60. package/package.json +22 -8
  61. package/src/__tests__/_helpers.ts +64 -0
  62. package/src/__tests__/backendClient.test.ts +198 -0
  63. package/src/__tests__/core.delegation.test.ts +964 -0
  64. package/src/__tests__/core.edge.test.ts +314 -0
  65. package/src/__tests__/core.hiddenTools.predicate.test.ts +236 -0
  66. package/src/__tests__/core.localTools.test.ts +314 -0
  67. package/src/__tests__/core.mesh.test.ts +1182 -0
  68. package/src/__tests__/core.stripRequestMeta.test.ts +226 -0
  69. package/src/__tests__/core.stripResultMeta.test.ts +217 -0
  70. package/src/__tests__/core.test.ts +1105 -0
  71. package/src/__tests__/core.toolCallTelemetry.test.ts +157 -0
  72. package/src/__tests__/eventStore.test.ts +141 -0
  73. package/src/__tests__/hasToolContent.test.ts +63 -0
  74. package/src/__tests__/mapToolResult.test.ts +101 -0
  75. package/src/__tests__/middleware.edge.test.ts +126 -0
  76. package/src/__tests__/middleware.test.ts +233 -0
  77. package/src/__tests__/proxyContext.test.ts +140 -0
  78. package/src/__tests__/sanitizeCatalog.test.ts +380 -0
  79. package/src/__tests__/startHttpProxy.lifecycle.test.ts +675 -0
  80. package/src/__tests__/startHttpProxy.registry.test.ts +409 -0
  81. package/src/__tests__/startHttpProxy.replay.test.ts +116 -0
  82. package/src/__tests__/startHttpProxy.security.test.ts +288 -0
  83. package/src/__tests__/startHttpProxy.test.ts +1065 -0
  84. package/src/__tests__/testing.test.ts +250 -0
  85. package/src/__tests__/version.test.ts +12 -0
  86. package/src/backendClient.ts +83 -0
  87. package/src/core.ts +2129 -0
  88. package/src/delegation.ts +224 -0
  89. package/src/eventStore.ts +106 -0
  90. package/src/hiddenTools.ts +84 -0
  91. package/src/identity.ts +15 -0
  92. package/src/index.ts +63 -0
  93. package/src/mesh.ts +204 -0
  94. package/src/middleware.ts +101 -0
  95. package/src/proxyContext.ts +108 -0
  96. package/src/rejection.ts +62 -0
  97. package/src/sanitizeCatalog.ts +126 -0
  98. package/src/sessionRegistry.ts +142 -0
  99. package/src/telemetry.ts +46 -0
  100. package/src/testing.ts +174 -0
  101. package/src/version.ts +6 -0
package/dist/index.d.ts CHANGED
@@ -1,14 +1,23 @@
1
1
  export type { Middleware } from './middleware.js';
2
- export { compose } from './middleware.js';
2
+ export { compose, markPassThroughObserver } from './middleware.js';
3
3
  export type { BackendConfig, BackendClient } from './backendClient.js';
4
4
  export { createBackendClient } from './backendClient.js';
5
5
  export type { Identity } from './identity.js';
6
- export type { ProxyContext } from './proxyContext.js';
7
- export { createProxyContext } from './proxyContext.js';
8
- export type { TelemetryEvent } from './telemetry.js';
6
+ export type { ProxyContext, ProxyIdentity, PolicyDecision, } from './proxyContext.js';
7
+ export { createProxyContext, outboundDelegationChain } from './proxyContext.js';
8
+ export type { TelemetryEvent, ToolCallTelemetryEvent, BackendDegradedTelemetryEvent, } from './telemetry.js';
9
+ export type { Backends } from './mesh.js';
9
10
  export type { PersistentEventStore } from './eventStore.js';
10
11
  export { createInMemoryEventStore } from './eventStore.js';
12
+ export type { SessionRecord, SessionRegistry } from './sessionRegistry.js';
13
+ export type { DelegationWireEntry, DelegationWirePayload, } from './delegation.js';
14
+ export { DELEGATION_META_KEY, serializeDelegationChain } from './delegation.js';
11
15
  export type { RejectionReason } from './rejection.js';
12
- export type { ProxyOptions, HttpProxyOptions, ToolMiddleware, ResourceMiddleware, ListToolsMiddleware, } from './core.js';
13
- export { hasToolContent, createProxyServer, startProxy, startHttpProxy } from './core.js';
16
+ export { rejectionMcpError } from './rejection.js';
17
+ export type { HiddenToolPredicate, DispatcherAwareBlockOptions, } from './hiddenTools.js';
18
+ export { dispatcherAwareBlock } from './hiddenTools.js';
19
+ export type { SanitizeToolDescriptionsOptions } from './sanitizeCatalog.js';
20
+ export { sanitizeToolDescriptions } from './sanitizeCatalog.js';
21
+ export type { ProxyOptions, HttpProxyOptions, LocalTool, ToolMiddleware, ResourceMiddleware, PromptMiddleware, ListToolsMiddleware, ToolResultHandlers, } from './core.js';
22
+ export { hasToolContent, mapToolResult, createProxyServer, startProxy, startHttpProxy, } from './core.js';
14
23
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAE1C,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAErD,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtD,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAEnE,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACvE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,YAAY,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,YAAY,EACV,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAEhF,YAAY,EACV,cAAc,EACd,sBAAsB,EACtB,6BAA6B,GAC9B,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE1C,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAE3D,YAAY,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE3E,YAAY,EACV,mBAAmB,EACnB,qBAAqB,GACtB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAEhF,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,YAAY,EACV,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,YAAY,EAAE,+BAA+B,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAEhE,YAAY,EACV,YAAY,EACZ,gBAAgB,EAChB,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,mBAAmB,EACnB,kBAAkB,GACnB,MAAM,WAAW,CAAC;AACnB,OAAO,EACL,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,cAAc,GACf,MAAM,WAAW,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,10 @@
1
- export { compose } from './middleware.js';
1
+ export { compose, markPassThroughObserver } from './middleware.js';
2
2
  export { createBackendClient } from './backendClient.js';
3
- export { createProxyContext } from './proxyContext.js';
3
+ export { createProxyContext, outboundDelegationChain } from './proxyContext.js';
4
4
  export { createInMemoryEventStore } from './eventStore.js';
5
- export { hasToolContent, createProxyServer, startProxy, startHttpProxy } from './core.js';
5
+ export { DELEGATION_META_KEY, serializeDelegationChain } from './delegation.js';
6
+ export { rejectionMcpError } from './rejection.js';
7
+ export { dispatcherAwareBlock } from './hiddenTools.js';
8
+ export { sanitizeToolDescriptions } from './sanitizeCatalog.js';
9
+ export { hasToolContent, mapToolResult, createProxyServer, startProxy, startHttpProxy, } from './core.js';
6
10
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAG1C,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAKzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAKvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAW3D,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAGnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AASzD,OAAO,EAAE,kBAAkB,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC;AAWhF,OAAO,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAQ3D,OAAO,EAAE,mBAAmB,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAGhF,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAMnD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAGxD,OAAO,EAAE,wBAAwB,EAAE,MAAM,sBAAsB,CAAC;AAYhE,OAAO,EACL,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,UAAU,EACV,cAAc,GACf,MAAM,WAAW,CAAC"}
package/dist/mesh.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Multi-backend (mesh) composition: backend-key validation, namespacing,
3
+ * routing, and failure-isolated listing. See ADR-0013.
4
+ */
5
+ import type { BackendClient } from './backendClient.js';
6
+ /** One named upstream of a mesh. */
7
+ export interface MeshEntry {
8
+ readonly key: string;
9
+ readonly client: BackendClient;
10
+ }
11
+ /**
12
+ * Either a single upstream (1:1 proxy, no namespacing) or a record of named
13
+ * backends keyed by backend key (mesh mode). See ADR-0013.
14
+ */
15
+ export type Backends = BackendClient | Readonly<Record<string, BackendClient>>;
16
+ /**
17
+ * Normalizes the `backends` argument to an ordered entry list. The 1:1 form
18
+ * yields a single entry with an empty key and `mesh: false`.
19
+ *
20
+ * @throws On an empty record, an empty backend key, or a key containing the
21
+ * namespace separator — each would make an exposed name ambiguous, and
22
+ * guessing which upstream a caller meant is the failure mesh routing exists
23
+ * to prevent.
24
+ */
25
+ export declare function normalizeBackends(backends: Backends): {
26
+ mesh: boolean;
27
+ entries: ReadonlyArray<MeshEntry>;
28
+ };
29
+ /** Namespaces a listed tool or prompt as `<key>__<name>`. */
30
+ export declare function namespaceName<Item extends {
31
+ name: string;
32
+ }>(key: string, item: Item): Item;
33
+ /**
34
+ * Exposes a listed resource as `mcpose://<key>/<uri>`. The upstream URI is
35
+ * appended untouched, so what the client holds is still one valid URI whose
36
+ * only meaning is "read me through this proxy", exactly as opaque as before.
37
+ */
38
+ export declare function namespaceUri<Item extends {
39
+ uri: string;
40
+ }>(key: string, item: Item): Item;
41
+ /**
42
+ * Resolves an exposed resource URI to its backend and upstream URI.
43
+ *
44
+ * Returns `undefined` for a URI not under `mcpose://`, an unknown key, or an
45
+ * empty upstream URI, and the caller rejects it as `BACKEND_UNROUTABLE`:
46
+ * there is no "which backend listed this URI" lookup, for the same reason
47
+ * tool names get no single-backend fallback.
48
+ */
49
+ export declare function routeResourceUri(uri: string, byKey: ReadonlyMap<string, BackendClient>): {
50
+ client: BackendClient;
51
+ uri: string;
52
+ } | undefined;
53
+ /**
54
+ * Resolves an exposed name to its backend and upstream name.
55
+ *
56
+ * Returns `undefined` for an un-namespaced name, an unknown key, or an empty
57
+ * upstream name. The caller turns that into a loud `BACKEND_UNROUTABLE`
58
+ * rejection: there is deliberately no "resolve it if only one backend has
59
+ * that name" fallback, because it silently re-routes once a backend is added.
60
+ */
61
+ export declare function routeNamespaced(name: string, byKey: ReadonlyMap<string, BackendClient>): {
62
+ client: BackendClient;
63
+ name: string;
64
+ } | undefined;
65
+ /** One page of a paginated list surface. */
66
+ export interface MeshPage<Item> {
67
+ items: ReadonlyArray<Item>;
68
+ nextCursor: string | undefined;
69
+ }
70
+ /**
71
+ * Lists one paginated surface across every backend concurrently, exposing
72
+ * each entry under its backend key via `expose` and isolating failures: a
73
+ * backend that throws (or never finishes paginating) contributes nothing and
74
+ * is reported through `onDegraded`, while the live backends' entries are
75
+ * still returned.
76
+ *
77
+ * Entries keep backend declaration order. A backend that fails part-way
78
+ * through pagination contributes nothing at all rather than a truncated
79
+ * list, so the response never looks complete when it is not.
80
+ */
81
+ export declare function listAcrossMesh<Item>(entries: ReadonlyArray<MeshEntry>, listPage: (client: BackendClient, cursor: string | undefined) => Promise<MeshPage<Item>>, onDegraded: (key: string, error: unknown) => void, expose: (key: string, item: Item) => Item): Promise<Item[]>;
82
+ //# sourceMappingURL=mesh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mesh.d.ts","sourceRoot":"","sources":["../src/mesh.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA6BxD,oCAAoC;AACpC,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;CAChC;AAED;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,aAAa,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;AAK/E;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,GAAG;IACrD,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,CAAC;CACnC,CAgCA;AAED,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,IAAI,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,EACzD,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,GACT,IAAI,CAEN;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,IAAI,SAAS;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,EACvD,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,IAAI,GACT,IAAI,CAEN;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,GACxC;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CASpD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,aAAa,CAAC,GACxC;IAAE,MAAM,EAAE,aAAa,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAOrD;AAED,4CAA4C;AAC5C,MAAM,WAAW,QAAQ,CAAC,IAAI;IAC5B,KAAK,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC;IAC3B,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;CAChC;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,cAAc,CAAC,IAAI,EACvC,OAAO,EAAE,aAAa,CAAC,SAAS,CAAC,EACjC,QAAQ,EAAE,CACR,MAAM,EAAE,aAAa,EACrB,MAAM,EAAE,MAAM,GAAG,SAAS,KACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAC5B,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,EACjD,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,GACxC,OAAO,CAAC,IAAI,EAAE,CAAC,CAsBjB"}
package/dist/mesh.js ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Separates a backend key from the upstream name in mesh mode:
3
+ * `<backendKey>__<name>`. A key may not contain it, so the first occurrence
4
+ * always splits the two, even when the upstream name contains one itself.
5
+ */
6
+ const BACKEND_NAMESPACE_SEPARATOR = '__';
7
+ /**
8
+ * A backend key is an identifier: it is spliced into tool and prompt names
9
+ * and, for resources, into the authority of a `mcpose://<key>/<uri>` URI
10
+ * (ADR-0022), so it is limited to what both a name and a URI host accept.
11
+ */
12
+ const BACKEND_KEY = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
13
+ /**
14
+ * The scheme a mesh exposes upstream resources under. The key is the
15
+ * authority and the upstream URI follows verbatim as the path, so the
16
+ * original is recovered by stripping a fixed prefix and never parsed.
17
+ */
18
+ const RESOURCE_SCHEME = 'mcpose://';
19
+ /**
20
+ * Pages drained from one backend before the proxy gives up on it. A backend
21
+ * that never advances its cursor would otherwise hang the whole list.
22
+ */
23
+ const MAX_PAGES_PER_BACKEND = 100;
24
+ const isBackendClient = (backends) => typeof backends.getServerCapabilities === 'function';
25
+ /**
26
+ * Normalizes the `backends` argument to an ordered entry list. The 1:1 form
27
+ * yields a single entry with an empty key and `mesh: false`.
28
+ *
29
+ * @throws On an empty record, an empty backend key, or a key containing the
30
+ * namespace separator — each would make an exposed name ambiguous, and
31
+ * guessing which upstream a caller meant is the failure mesh routing exists
32
+ * to prevent.
33
+ */
34
+ export function normalizeBackends(backends) {
35
+ if (isBackendClient(backends)) {
36
+ return { mesh: false, entries: [{ key: '', client: backends }] };
37
+ }
38
+ const entries = Object.entries(backends).map(([key, client]) => ({
39
+ key,
40
+ client,
41
+ }));
42
+ if (entries.length === 0) {
43
+ throw new Error('mcpose: backends record is empty — pass at least one named backend, or a single BackendClient for a 1:1 proxy.');
44
+ }
45
+ for (const { key } of entries) {
46
+ if (key === '') {
47
+ throw new Error('mcpose: backend key must not be empty');
48
+ }
49
+ if (key.includes(BACKEND_NAMESPACE_SEPARATOR)) {
50
+ throw new Error(`mcpose: backend key "${key}" must not contain "${BACKEND_NAMESPACE_SEPARATOR}" — it is the namespace separator`);
51
+ }
52
+ if (!BACKEND_KEY.test(key)) {
53
+ throw new Error(`mcpose: backend key "${key}" must be an identifier (letters, digits, ".", "_" and "-", starting with a letter or digit) — it names the backend inside tool names and resource URIs`);
54
+ }
55
+ }
56
+ return { mesh: true, entries };
57
+ }
58
+ /** Namespaces a listed tool or prompt as `<key>__<name>`. */
59
+ export function namespaceName(key, item) {
60
+ return { ...item, name: `${key}${BACKEND_NAMESPACE_SEPARATOR}${item.name}` };
61
+ }
62
+ /**
63
+ * Exposes a listed resource as `mcpose://<key>/<uri>`. The upstream URI is
64
+ * appended untouched, so what the client holds is still one valid URI whose
65
+ * only meaning is "read me through this proxy", exactly as opaque as before.
66
+ */
67
+ export function namespaceUri(key, item) {
68
+ return { ...item, uri: `${RESOURCE_SCHEME}${key}/${item.uri}` };
69
+ }
70
+ /**
71
+ * Resolves an exposed resource URI to its backend and upstream URI.
72
+ *
73
+ * Returns `undefined` for a URI not under `mcpose://`, an unknown key, or an
74
+ * empty upstream URI, and the caller rejects it as `BACKEND_UNROUTABLE`:
75
+ * there is no "which backend listed this URI" lookup, for the same reason
76
+ * tool names get no single-backend fallback.
77
+ */
78
+ export function routeResourceUri(uri, byKey) {
79
+ if (!uri.startsWith(RESOURCE_SCHEME))
80
+ return undefined;
81
+ const rest = uri.slice(RESOURCE_SCHEME.length);
82
+ const slash = rest.indexOf('/');
83
+ if (slash <= 0)
84
+ return undefined;
85
+ const client = byKey.get(rest.slice(0, slash));
86
+ const upstreamUri = rest.slice(slash + 1);
87
+ if (client === undefined || upstreamUri === '')
88
+ return undefined;
89
+ return { client, uri: upstreamUri };
90
+ }
91
+ /**
92
+ * Resolves an exposed name to its backend and upstream name.
93
+ *
94
+ * Returns `undefined` for an un-namespaced name, an unknown key, or an empty
95
+ * upstream name. The caller turns that into a loud `BACKEND_UNROUTABLE`
96
+ * rejection: there is deliberately no "resolve it if only one backend has
97
+ * that name" fallback, because it silently re-routes once a backend is added.
98
+ */
99
+ export function routeNamespaced(name, byKey) {
100
+ const at = name.indexOf(BACKEND_NAMESPACE_SEPARATOR);
101
+ if (at <= 0)
102
+ return undefined;
103
+ const client = byKey.get(name.slice(0, at));
104
+ const upstreamName = name.slice(at + BACKEND_NAMESPACE_SEPARATOR.length);
105
+ if (client === undefined || upstreamName === '')
106
+ return undefined;
107
+ return { client, name: upstreamName };
108
+ }
109
+ /**
110
+ * Lists one paginated surface across every backend concurrently, exposing
111
+ * each entry under its backend key via `expose` and isolating failures: a
112
+ * backend that throws (or never finishes paginating) contributes nothing and
113
+ * is reported through `onDegraded`, while the live backends' entries are
114
+ * still returned.
115
+ *
116
+ * Entries keep backend declaration order. A backend that fails part-way
117
+ * through pagination contributes nothing at all rather than a truncated
118
+ * list, so the response never looks complete when it is not.
119
+ */
120
+ export async function listAcrossMesh(entries, listPage, onDegraded, expose) {
121
+ const settled = await Promise.allSettled(entries.map(async ({ key, client }) => {
122
+ const collected = [];
123
+ let cursor;
124
+ for (let page = 0; page < MAX_PAGES_PER_BACKEND; page += 1) {
125
+ const { items, nextCursor } = await listPage(client, cursor);
126
+ for (const item of items)
127
+ collected.push(expose(key, item));
128
+ if (nextCursor === undefined)
129
+ return collected;
130
+ cursor = nextCursor;
131
+ }
132
+ throw new Error(`mcpose: backend "${key}" did not finish paginating within ${MAX_PAGES_PER_BACKEND} pages`);
133
+ }));
134
+ return settled.flatMap((outcome, index) => {
135
+ if (outcome.status === 'fulfilled')
136
+ return outcome.value;
137
+ onDegraded(entries[index].key, outcome.reason);
138
+ return [];
139
+ });
140
+ }
141
+ //# sourceMappingURL=mesh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mesh.js","sourceRoot":"","sources":["../src/mesh.ts"],"names":[],"mappings":"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"}
@@ -32,5 +32,18 @@ export declare function compose<Req, Res>(middlewares: ReadonlyArray<Middleware<
32
32
  * Used internally by mcpose core — consumers pass arrays to `ProxyOptions`.
33
33
  */
34
34
  export declare function pipe<Req, Res>(middlewares: ReadonlyArray<Middleware<Req, Res>>): MiddlewarePipeline<Req, Res>;
35
+ /**
36
+ * Marks a middleware as a pass-through observer: it still runs for tools
37
+ * listed in `ProxyOptions.passThroughTools`, which skip all other middleware.
38
+ *
39
+ * Use for middleware that must see every call regardless of pass-through
40
+ * status — audit logging, telemetry — never for middleware that transforms
41
+ * requests or responses (pass-through means "forward upstream as-is").
42
+ *
43
+ * Returns a new middleware; the input is not mutated.
44
+ */
45
+ export declare function markPassThroughObserver<Req, Res>(mw: Middleware<Req, Res>): Middleware<Req, Res>;
46
+ /** True when the middleware was wrapped by {@link markPassThroughObserver}. */
47
+ export declare function isPassThroughObserver(mw: unknown): boolean;
35
48
  export {};
36
49
  //# sourceMappingURL=middleware.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE1E,4EAA4E;AAC5E,MAAM,MAAM,UAAU,CAAC,GAAG,EAAE,GAAG,IAAI,CACjC,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAChC,OAAO,EAAE,YAAY,KAClB,OAAO,CAAC,GAAG,CAAC,CAAC;AAElB,KAAK,kBAAkB,CAAC,GAAG,EAAE,GAAG,IAAI;IAClC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3D,CACE,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,GAAG,CAAC,CAAC;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,EAC9B,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAC/C,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,CAkB9B;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,GAAG,EAC3B,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAC/C,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,CAE9B"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAsB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE1E,4EAA4E;AAC5E,MAAM,MAAM,UAAU,CAAC,GAAG,EAAE,GAAG,IAAI,CACjC,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAChC,OAAO,EAAE,YAAY,KAClB,OAAO,CAAC,GAAG,CAAC,CAAC;AAElB,KAAK,kBAAkB,CAAC,GAAG,EAAE,GAAG,IAAI;IAClC,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IAC3D,CACE,GAAG,EAAE,GAAG,EACR,IAAI,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC,EAChC,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,GAAG,CAAC,CAAC;CACjB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,GAAG,EAC9B,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAC/C,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,CAoB9B;AAED;;;;GAIG;AACH,wBAAgB,IAAI,CAAC,GAAG,EAAE,GAAG,EAC3B,WAAW,EAAE,aAAa,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,GAC/C,kBAAkB,CAAC,GAAG,EAAE,GAAG,CAAC,CAE9B;AAID;;;;;;;;;GASG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,GAAG,EAC9C,EAAE,EAAE,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,GACvB,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAQtB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,OAAO,GAAG,OAAO,CAE1D"}
@@ -20,18 +20,19 @@ import { createProxyContext } from './proxyContext.js';
20
20
  * const result = await pipeline(req, (r) => upstream.callTool(r.params));
21
21
  */
22
22
  export function compose(middlewares) {
23
- return ((req, next, context = createProxyContext()) => {
23
+ return (req, next, context = createProxyContext()) => {
24
24
  let index = -1;
25
25
  const dispatch = (i, currentReq) => {
26
26
  if (i <= index) {
27
27
  return Promise.reject(new Error('next() called multiple times'));
28
28
  }
29
29
  index = i;
30
- const fn = i < middlewares.length ? middlewares[i] : (_r, n) => next(_r);
30
+ // Past the end of the pipeline, the terminal handler runs.
31
+ const fn = middlewares[i] ?? ((r) => next(r));
31
32
  return Promise.resolve(fn(currentReq, (r) => dispatch(i + 1, r), context));
32
33
  };
33
34
  return dispatch(0, req);
34
- });
35
+ };
35
36
  }
36
37
  /**
37
38
  * Like `compose` but in response-processing order (first = innermost).
@@ -41,4 +42,27 @@ export function compose(middlewares) {
41
42
  export function pipe(middlewares) {
42
43
  return compose([...middlewares].reverse());
43
44
  }
45
+ const PASS_THROUGH_OBSERVER = Symbol.for('mcpose.passThroughObserver');
46
+ /**
47
+ * Marks a middleware as a pass-through observer: it still runs for tools
48
+ * listed in `ProxyOptions.passThroughTools`, which skip all other middleware.
49
+ *
50
+ * Use for middleware that must see every call regardless of pass-through
51
+ * status — audit logging, telemetry — never for middleware that transforms
52
+ * requests or responses (pass-through means "forward upstream as-is").
53
+ *
54
+ * Returns a new middleware; the input is not mutated.
55
+ */
56
+ export function markPassThroughObserver(mw) {
57
+ const marked = (req, next, context) => mw(req, next, context);
58
+ Object.defineProperty(marked, PASS_THROUGH_OBSERVER, {
59
+ value: true,
60
+ enumerable: false,
61
+ });
62
+ return marked;
63
+ }
64
+ /** True when the middleware was wrapped by {@link markPassThroughObserver}. */
65
+ export function isPassThroughObserver(mw) {
66
+ return typeof mw === 'function' && PASS_THROUGH_OBSERVER in mw;
67
+ }
44
68
  //# sourceMappingURL=middleware.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,kBAAkB,EAAqB,MAAM,mBAAmB,CAAC;AAkB1E;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CACrB,WAAgD;IAEhD,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,UAAwB,kBAAkB,EAAE,EAAE,EAAE;QAClE,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;QAEf,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,UAAe,EAAgB,EAAE;YAC5D,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;gBACf,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;YACnE,CAAC;YACD,KAAK,GAAG,CAAC,CAAC;YAEV,MAAM,EAAE,GACN,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAEjE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;QAC7E,CAAC,CAAC;QAEF,OAAO,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1B,CAAC,CAAiC,CAAC;AACrC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAClB,WAAgD;IAEhD,OAAO,OAAO,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAC7C,CAAC"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,kBAAkB,EAAqB,MAAM,mBAAmB,CAAC;AAkB1E;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CACrB,WAAgD;IAEhD,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,UAAwB,kBAAkB,EAAE,EAAE,EAAE;QACjE,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC;QAEf,MAAM,QAAQ,GAAG,CAAC,CAAS,EAAE,UAAe,EAAgB,EAAE;YAC5D,IAAI,CAAC,IAAI,KAAK,EAAE,CAAC;gBACf,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,8BAA8B,CAAC,CAAC,CAAC;YACnE,CAAC;YACD,KAAK,GAAG,CAAC,CAAC;YAEV,2DAA2D;YAC3D,MAAM,EAAE,GAAyB,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YAEpE,OAAO,OAAO,CAAC,OAAO,CACpB,EAAE,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CACnD,CAAC;QACJ,CAAC,CAAC;QAEF,OAAO,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC1B,CAAC,CAAC;AACJ,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,IAAI,CAClB,WAAgD;IAEhD,OAAO,OAAO,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,MAAM,qBAAqB,GAAG,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;AAEvE;;;;;;;;;GASG;AACH,MAAM,UAAU,uBAAuB,CACrC,EAAwB;IAExB,MAAM,MAAM,GAAyB,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,CAC1D,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;IACzB,MAAM,CAAC,cAAc,CAAC,MAAM,EAAE,qBAAqB,EAAE;QACnD,KAAK,EAAE,IAAI;QACX,UAAU,EAAE,KAAK;KAClB,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,qBAAqB,CAAC,EAAW;IAC/C,OAAO,OAAO,EAAE,KAAK,UAAU,IAAI,qBAAqB,IAAI,EAAE,CAAC;AACjE,CAAC"}
@@ -1,4 +1,27 @@
1
1
  import type { Identity } from './identity.js';
2
+ /**
3
+ * The proxy instance a request passed through, as resolved from
4
+ * `ProxyOptions.name`/`version` (defaults included). Provenance, not a
5
+ * principal: it is never an entry in `delegatedFrom` and takes no part in
6
+ * the caller-attribution model (ADR-0011, ADR-0012).
7
+ */
8
+ export interface ProxyIdentity {
9
+ readonly name: string;
10
+ readonly version: string;
11
+ }
12
+ /**
13
+ * A policy decision already made, stamped on the context by the policy
14
+ * middleware (ADR-0017). It is a record, not a handle to query: core never
15
+ * evaluates policy itself, and a second evaluation path would let inner
16
+ * middleware ask a different question than the gate answered.
17
+ *
18
+ * `reason` carries the `RejectionReason` that accompanied a denial.
19
+ */
20
+ export interface PolicyDecision {
21
+ readonly decision: 'allow' | 'deny';
22
+ readonly ruleId?: string;
23
+ readonly reason?: string;
24
+ }
2
25
  /** Normalized request metadata that mcpose passes through middleware layers. */
3
26
  export interface ProxyContext {
4
27
  requestId: string;
@@ -8,11 +31,48 @@ export interface ProxyContext {
8
31
  signal?: AbortSignal;
9
32
  /** Resolved caller identity. Present when {@link HttpProxyOptions.resolveIdentity} is configured. */
10
33
  identity?: Identity;
11
- /** Agent delegation chain — populated when an upstream A2A agent delegates through mcpose. */
34
+ /**
35
+ * Agent delegation chain, oldest-first. Populated from the inbound
36
+ * request's `_meta["mcpose/delegation"]` when a caller presents one
37
+ * (ADR-0016), or by a host that stamps its own, which wins.
38
+ *
39
+ * Attribution, never authorization: every entry extracted from the wire
40
+ * carries empty `roles` and `claims`, because a presented chain is written
41
+ * by the previous hop.
42
+ */
12
43
  delegatedFrom?: Identity[];
13
- /** @stable Reserved for v3 policy engine. */
14
- policy?: never;
44
+ /** The proxy instance handling this request. Stamped by {@link createProxyServer}. */
45
+ proxy?: ProxyIdentity;
46
+ /**
47
+ * The policy decision for this call. Stamped by the `@mcpose/policy`
48
+ * middleware before it calls `next` (allow) or throws (deny), so
49
+ * middleware running inside the policy layer can rely on
50
+ * `decision: 'allow'` (ADR-0017). Core never writes it.
51
+ */
52
+ policy?: PolicyDecision;
15
53
  }
16
- /** Creates a middleware context with a fresh request ID. */
17
- export declare function createProxyContext(overrides?: Partial<ProxyContext>): ProxyContext;
54
+ /**
55
+ * Canonical oldest-first delegation chain a host attaches to outbound calls
56
+ * made on behalf of the inbound caller (ADR-0011). Returns a fresh array and
57
+ * never mutates or aliases `ctx.delegatedFrom`, because the audit middleware
58
+ * reads the same context object when recording the inbound call.
59
+ *
60
+ * Core attaches this chain itself to every call it forwards to a backend, at
61
+ * `params._meta["mcpose/delegation"]` (ADR-0016). A local tool handler makes
62
+ * its own outbound calls, so its host attaches the chain: for an outbound MCP
63
+ * call, `serializeDelegationChain(outboundDelegationChain(context))` under
64
+ * `DELEGATION_META_KEY` produces exactly what core sends.
65
+ */
66
+ export declare function outboundDelegationChain(ctx: ProxyContext): Identity[];
67
+ /**
68
+ * Creates a middleware context with a fresh request ID.
69
+ *
70
+ * Every override may be passed explicitly as `undefined` — it means "not
71
+ * provided" and the key is dropped from the result, which is why the
72
+ * parameter is not a plain `Partial<ProxyContext>` under
73
+ * `exactOptionalPropertyTypes`.
74
+ */
75
+ export declare function createProxyContext(overrides?: {
76
+ [K in keyof ProxyContext]?: ProxyContext[K] | undefined;
77
+ }): ProxyContext;
18
78
  //# sourceMappingURL=proxyContext.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"proxyContext.d.ts","sourceRoot":"","sources":["../src/proxyContext.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,qGAAqG;IACrG,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,8FAA8F;IAC9F,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC3B,6CAA6C;IAC7C,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB;AAED,4DAA4D;AAC5D,wBAAgB,kBAAkB,CAChC,SAAS,GAAE,OAAO,CAAC,YAAY,CAAM,GACpC,YAAY,CAUd"}
1
+ {"version":3,"file":"proxyContext.d.ts","sourceRoot":"","sources":["../src/proxyContext.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,gFAAgF;AAChF,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,qGAAqG;IACrG,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB;;;;;;;;OAQG;IACH,aAAa,CAAC,EAAE,QAAQ,EAAE,CAAC;IAC3B,sFAAsF;IACtF,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,cAAc,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,YAAY,GAAG,QAAQ,EAAE,CAKrE;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,GAAE;KAAG,CAAC,IAAI,MAAM,YAAY,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,GAAG,SAAS;CAAO,GAC1E,YAAY,CAoBd"}
@@ -1,14 +1,49 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- /** Creates a middleware context with a fresh request ID. */
2
+ /**
3
+ * Canonical oldest-first delegation chain a host attaches to outbound calls
4
+ * made on behalf of the inbound caller (ADR-0011). Returns a fresh array and
5
+ * never mutates or aliases `ctx.delegatedFrom`, because the audit middleware
6
+ * reads the same context object when recording the inbound call.
7
+ *
8
+ * Core attaches this chain itself to every call it forwards to a backend, at
9
+ * `params._meta["mcpose/delegation"]` (ADR-0016). A local tool handler makes
10
+ * its own outbound calls, so its host attaches the chain: for an outbound MCP
11
+ * call, `serializeDelegationChain(outboundDelegationChain(context))` under
12
+ * `DELEGATION_META_KEY` produces exactly what core sends.
13
+ */
14
+ export function outboundDelegationChain(ctx) {
15
+ return [
16
+ ...(ctx.delegatedFrom ?? []),
17
+ ...(ctx.identity === undefined ? [] : [ctx.identity]),
18
+ ];
19
+ }
20
+ /**
21
+ * Creates a middleware context with a fresh request ID.
22
+ *
23
+ * Every override may be passed explicitly as `undefined` — it means "not
24
+ * provided" and the key is dropped from the result, which is why the
25
+ * parameter is not a plain `Partial<ProxyContext>` under
26
+ * `exactOptionalPropertyTypes`.
27
+ */
3
28
  export function createProxyContext(overrides = {}) {
4
29
  return {
30
+ // Deliberately `||`, not `??`: an empty-string requestId is useless for
31
+ // correlation, so it is regenerated like a missing one.
5
32
  requestId: overrides.requestId || randomUUID(),
6
33
  transport: overrides.transport ?? 'stdio',
7
- ...(overrides.sessionId === undefined ? {} : { sessionId: overrides.sessionId }),
34
+ ...(overrides.sessionId === undefined
35
+ ? {}
36
+ : { sessionId: overrides.sessionId }),
8
37
  ...(overrides.headers === undefined ? {} : { headers: overrides.headers }),
9
38
  ...(overrides.signal === undefined ? {} : { signal: overrides.signal }),
10
- ...(overrides.identity === undefined ? {} : { identity: overrides.identity }),
11
- ...(overrides.delegatedFrom === undefined ? {} : { delegatedFrom: overrides.delegatedFrom }),
39
+ ...(overrides.identity === undefined
40
+ ? {}
41
+ : { identity: overrides.identity }),
42
+ ...(overrides.delegatedFrom === undefined
43
+ ? {}
44
+ : { delegatedFrom: overrides.delegatedFrom }),
45
+ ...(overrides.proxy === undefined ? {} : { proxy: overrides.proxy }),
46
+ ...(overrides.policy === undefined ? {} : { policy: overrides.policy }),
12
47
  };
13
48
  }
14
49
  //# sourceMappingURL=proxyContext.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"proxyContext.js","sourceRoot":"","sources":["../src/proxyContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAkBzC,4DAA4D;AAC5D,MAAM,UAAU,kBAAkB,CAChC,YAAmC,EAAE;IAErC,OAAO;QACL,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,UAAU,EAAE;QAC9C,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,OAAO;QACzC,GAAG,CAAC,SAAS,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC;QAChF,GAAG,CAAC,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC;QAC1E,GAAG,CAAC,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC;QACvE,GAAG,CAAC,SAAS,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC7E,GAAG,CAAC,SAAS,CAAC,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,CAAC,aAAa,EAAE,CAAC;KAC7F,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"proxyContext.js","sourceRoot":"","sources":["../src/proxyContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AA0DzC;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAiB;IACvD,OAAO;QACL,GAAG,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC;QAC5B,GAAG,CAAC,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;KACtD,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,YAAyE,EAAE;IAE3E,OAAO;QACL,wEAAwE;QACxE,wDAAwD;QACxD,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,UAAU,EAAE;QAC9C,SAAS,EAAE,SAAS,CAAC,SAAS,IAAI,OAAO;QACzC,GAAG,CAAC,SAAS,CAAC,SAAS,KAAK,SAAS;YACnC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,SAAS,EAAE,CAAC;QACvC,GAAG,CAAC,SAAS,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,CAAC;QAC1E,GAAG,CAAC,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC;QACvE,GAAG,CAAC,SAAS,CAAC,QAAQ,KAAK,SAAS;YAClC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,QAAQ,EAAE,CAAC;QACrC,GAAG,CAAC,SAAS,CAAC,aAAa,KAAK,SAAS;YACvC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,aAAa,EAAE,SAAS,CAAC,aAAa,EAAE,CAAC;QAC/C,GAAG,CAAC,SAAS,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,EAAE,CAAC;QACpE,GAAG,CAAC,SAAS,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,CAAC;KACxE,CAAC;AACJ,CAAC"}
@@ -9,7 +9,13 @@ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
9
9
  *
10
10
  * @stable
11
11
  */
12
- export type RejectionReason = 'TOOL_HIDDEN' | 'RESOURCE_HIDDEN' | 'POLICY_DENIED' | 'IDENTITY_UNRESOLVED' | 'CONSENT_MISSING' | 'SENSITIVITY_BLOCKED' | 'DELEGATION_INVALID' | 'BUDGET_EXCEEDED' | 'SESSION_LIMIT' | 'BODY_LIMIT';
12
+ export type RejectionReason = 'TOOL_HIDDEN' | 'RESOURCE_HIDDEN' | 'BACKEND_UNROUTABLE' | 'POLICY_DENIED' | 'IDENTITY_UNRESOLVED' | 'CONSENT_MISSING' | 'SENSITIVITY_BLOCKED' | 'DELEGATION_INVALID' | 'BUDGET_EXCEEDED' | 'SESSION_LIMIT' | 'BODY_LIMIT';
13
+ /**
14
+ * Narrows an untrusted value to `RejectionReason`.
15
+ * `Object.hasOwn`, not `in`: the value can come from an upstream error, so a
16
+ * prototype key like `constructor` must not pass as a member.
17
+ */
18
+ export declare function isRejectionReason(value: unknown): value is RejectionReason;
13
19
  /**
14
20
  * Creates an `McpError` with the rejection reason embedded in `error.data`.
15
21
  * The top-level `code` is unchanged so clients that only inspect the code
@@ -1 +1 @@
1
- {"version":3,"file":"rejection.d.ts","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GACvB,aAAa,GACb,iBAAiB,GACjB,eAAe,GACf,qBAAqB,GACrB,iBAAiB,GACjB,qBAAqB,GACrB,oBAAoB,GACpB,iBAAiB,GACjB,eAAe,GACf,YAAY,CAAC;AAEjB;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,MAAM,GACd,QAAQ,CAEV"}
1
+ {"version":3,"file":"rejection.d.ts","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;;;;;;GASG;AACH,MAAM,MAAM,eAAe,GACvB,aAAa,GACb,iBAAiB,GACjB,oBAAoB,GACpB,eAAe,GACf,qBAAqB,GACrB,iBAAiB,GACjB,qBAAqB,GACrB,oBAAoB,GACpB,iBAAiB,GACjB,eAAe,GACf,YAAY,CAAC;AAkBjB;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,eAAe,CAE1E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,eAAe,EACvB,IAAI,EAAE,SAAS,EACf,OAAO,EAAE,MAAM,GACd,QAAQ,CAEV"}
package/dist/rejection.js CHANGED
@@ -1,4 +1,27 @@
1
- import { McpError } from '@modelcontextprotocol/sdk/types.js';
1
+ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
2
+ // The `Record` annotation keeps this table and the union from drifting apart:
3
+ // a missing or extra key is a compile error.
4
+ const REJECTION_REASONS = {
5
+ TOOL_HIDDEN: true,
6
+ RESOURCE_HIDDEN: true,
7
+ BACKEND_UNROUTABLE: true,
8
+ POLICY_DENIED: true,
9
+ IDENTITY_UNRESOLVED: true,
10
+ CONSENT_MISSING: true,
11
+ SENSITIVITY_BLOCKED: true,
12
+ DELEGATION_INVALID: true,
13
+ BUDGET_EXCEEDED: true,
14
+ SESSION_LIMIT: true,
15
+ BODY_LIMIT: true,
16
+ };
17
+ /**
18
+ * Narrows an untrusted value to `RejectionReason`.
19
+ * `Object.hasOwn`, not `in`: the value can come from an upstream error, so a
20
+ * prototype key like `constructor` must not pass as a member.
21
+ */
22
+ export function isRejectionReason(value) {
23
+ return typeof value === 'string' && Object.hasOwn(REJECTION_REASONS, value);
24
+ }
2
25
  /**
3
26
  * Creates an `McpError` with the rejection reason embedded in `error.data`.
4
27
  * The top-level `code` is unchanged so clients that only inspect the code
@@ -1 +1 @@
1
- {"version":3,"file":"rejection.js","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAa,MAAM,oCAAoC,CAAC;AAwBzE;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAuB,EACvB,IAAe,EACf,OAAe;IAEf,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC,CAAC;AAClE,CAAC"}
1
+ {"version":3,"file":"rejection.js","sourceRoot":"","sources":["../src/rejection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAyBzE,8EAA8E;AAC9E,6CAA6C;AAC7C,MAAM,iBAAiB,GAAkC;IACvD,WAAW,EAAE,IAAI;IACjB,eAAe,EAAE,IAAI;IACrB,kBAAkB,EAAE,IAAI;IACxB,aAAa,EAAE,IAAI;IACnB,mBAAmB,EAAE,IAAI;IACzB,eAAe,EAAE,IAAI;IACrB,mBAAmB,EAAE,IAAI;IACzB,kBAAkB,EAAE,IAAI;IACxB,eAAe,EAAE,IAAI;IACrB,aAAa,EAAE,IAAI;IACnB,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,KAAK,CAAC,CAAC;AAC9E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAAuB,EACvB,IAAe,EACf,OAAe;IAEf,OAAO,IAAI,QAAQ,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,eAAe,EAAE,MAAM,EAAE,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Egress sanitizer for the tool catalog: `tools/list` forwards upstream
3
+ * descriptions verbatim into model context, and real upstreams leak org
4
+ * slugs and internal hostnames there. See ADR-0010.
5
+ */
6
+ import type { ListToolsMiddleware } from './core.js';
7
+ /** Options for {@link sanitizeToolDescriptions}. */
8
+ export interface SanitizeToolDescriptionsOptions {
9
+ /**
10
+ * Extra patterns to remove. A string replaces every literal occurrence;
11
+ * a RegExp is normalized to global, with any sticky flag dropped, so
12
+ * every match is replaced.
13
+ */
14
+ patterns?: ReadonlyArray<string | RegExp>;
15
+ /** Replacement text for every match. Default: `''` (plain removal). */
16
+ replacement?: string;
17
+ }
18
+ /**
19
+ * A {@link ListToolsMiddleware} that sanitizes tool descriptions and every
20
+ * `description` field nested in `inputSchema` / `outputSchema`. Names,
21
+ * titles, and schema structure are untouched, because clients route on
22
+ * them.
23
+ *
24
+ * Always strips http(s) URLs; `patterns` adds more. Place it **last** in
25
+ * `listToolsMiddleware` so it sanitizes the output of other list
26
+ * middleware and local tools.
27
+ *
28
+ * @example
29
+ * listToolsMiddleware: [
30
+ * sanitizeToolDescriptions({
31
+ * patterns: ['acme-corp', /\bi-[0-9a-f]{8,17}\b/],
32
+ * replacement: '[redacted]',
33
+ * }),
34
+ * ]
35
+ */
36
+ export declare function sanitizeToolDescriptions(options?: SanitizeToolDescriptionsOptions): ListToolsMiddleware;
37
+ //# sourceMappingURL=sanitizeCatalog.d.ts.map