orpc-fn 0.8.0 → 0.9.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 (144) hide show
  1. package/README.md +66 -28
  2. package/dist/assemble-procedure.d.ts.map +1 -1
  3. package/dist/assemble-procedure.js +3 -1
  4. package/dist/assemble-procedure.js.map +1 -1
  5. package/dist/client.d.ts +13 -3
  6. package/dist/client.d.ts.map +1 -1
  7. package/dist/client.js +21 -1
  8. package/dist/client.js.map +1 -1
  9. package/dist/create-fn.d.ts +63 -5
  10. package/dist/create-fn.d.ts.map +1 -1
  11. package/dist/create-fn.js +45 -16
  12. package/dist/create-fn.js.map +1 -1
  13. package/dist/expected-client-error.d.ts +6 -0
  14. package/dist/expected-client-error.d.ts.map +1 -1
  15. package/dist/expected-client-error.js +16 -0
  16. package/dist/expected-client-error.js.map +1 -1
  17. package/dist/hono.d.ts +44 -22
  18. package/dist/hono.d.ts.map +1 -1
  19. package/dist/hono.js +51 -20
  20. package/dist/hono.js.map +1 -1
  21. package/dist/index.d.ts +1 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +0 -1
  24. package/dist/index.js.map +1 -1
  25. package/dist/issues.d.ts +12 -0
  26. package/dist/issues.d.ts.map +1 -0
  27. package/dist/issues.js +22 -0
  28. package/dist/issues.js.map +1 -0
  29. package/dist/json-schema.d.ts +3 -6
  30. package/dist/json-schema.d.ts.map +1 -1
  31. package/dist/json-schema.js +12 -7
  32. package/dist/json-schema.js.map +1 -1
  33. package/dist/live/codec.d.ts +2 -0
  34. package/dist/live/codec.d.ts.map +1 -1
  35. package/dist/live/codec.js +21 -16
  36. package/dist/live/codec.js.map +1 -1
  37. package/dist/live/fn-live.d.ts +6 -19
  38. package/dist/live/fn-live.d.ts.map +1 -1
  39. package/dist/live/fn-live.js +15 -23
  40. package/dist/live/fn-live.js.map +1 -1
  41. package/dist/live/index.d.ts +2 -2
  42. package/dist/live/index.d.ts.map +1 -1
  43. package/dist/live/index.js +1 -1
  44. package/dist/live/index.js.map +1 -1
  45. package/dist/live/memory.d.ts +11 -1
  46. package/dist/live/memory.d.ts.map +1 -1
  47. package/dist/live/memory.js +14 -1
  48. package/dist/live/memory.js.map +1 -1
  49. package/dist/live/pub-sub.d.ts +50 -8
  50. package/dist/live/pub-sub.d.ts.map +1 -1
  51. package/dist/live/pub-sub.js +79 -26
  52. package/dist/live/pub-sub.js.map +1 -1
  53. package/dist/live/redis-bun.d.ts +6 -1
  54. package/dist/live/redis-bun.d.ts.map +1 -1
  55. package/dist/live/redis-bun.js +10 -1
  56. package/dist/live/redis-bun.js.map +1 -1
  57. package/dist/live/scoped-change-bus.d.ts +3 -1
  58. package/dist/live/scoped-change-bus.d.ts.map +1 -1
  59. package/dist/live/scoped-change-bus.js +3 -0
  60. package/dist/live/scoped-change-bus.js.map +1 -1
  61. package/dist/mastra.d.ts +8 -3
  62. package/dist/mastra.d.ts.map +1 -1
  63. package/dist/mastra.js +8 -1
  64. package/dist/mastra.js.map +1 -1
  65. package/dist/mcp/core.d.ts +31 -14
  66. package/dist/mcp/core.d.ts.map +1 -1
  67. package/dist/mcp/core.js +39 -20
  68. package/dist/mcp/core.js.map +1 -1
  69. package/dist/mcp/sdk.d.ts +6 -2
  70. package/dist/mcp/sdk.d.ts.map +1 -1
  71. package/dist/mcp/sdk.js +9 -3
  72. package/dist/mcp/sdk.js.map +1 -1
  73. package/dist/mcp/server.d.ts +6 -2
  74. package/dist/mcp/server.d.ts.map +1 -1
  75. package/dist/mcp/server.js +9 -3
  76. package/dist/mcp/server.js.map +1 -1
  77. package/dist/mcp.d.ts +3 -7
  78. package/dist/mcp.d.ts.map +1 -1
  79. package/dist/mcp.js +4 -6
  80. package/dist/mcp.js.map +1 -1
  81. package/dist/meta.d.ts +14 -2
  82. package/dist/meta.d.ts.map +1 -1
  83. package/dist/meta.js +6 -2
  84. package/dist/meta.js.map +1 -1
  85. package/dist/otel.d.ts +16 -4
  86. package/dist/otel.d.ts.map +1 -1
  87. package/dist/otel.js +40 -10
  88. package/dist/otel.js.map +1 -1
  89. package/dist/react.d.ts +6 -1
  90. package/dist/react.d.ts.map +1 -1
  91. package/dist/react.js +5 -1
  92. package/dist/react.js.map +1 -1
  93. package/dist/route-options.d.ts.map +1 -1
  94. package/dist/route-options.js +4 -0
  95. package/dist/route-options.js.map +1 -1
  96. package/dist/stream-call.d.ts +15 -0
  97. package/dist/stream-call.d.ts.map +1 -0
  98. package/dist/stream-call.js +13 -0
  99. package/dist/stream-call.js.map +1 -0
  100. package/dist/timing.d.ts +2 -0
  101. package/dist/timing.d.ts.map +1 -1
  102. package/dist/timing.js +2 -0
  103. package/dist/timing.js.map +1 -1
  104. package/dist/tool-errors.d.ts +24 -0
  105. package/dist/tool-errors.d.ts.map +1 -0
  106. package/dist/tool-errors.js +36 -0
  107. package/dist/tool-errors.js.map +1 -0
  108. package/dist/types.d.ts +2 -0
  109. package/dist/types.d.ts.map +1 -1
  110. package/dist/types.js.map +1 -1
  111. package/dist/vue.d.ts +47 -4
  112. package/dist/vue.d.ts.map +1 -1
  113. package/dist/vue.js +39 -9
  114. package/dist/vue.js.map +1 -1
  115. package/package.json +1 -1
  116. package/src/assemble-procedure.ts +3 -1
  117. package/src/client.ts +26 -2
  118. package/src/create-fn.ts +149 -19
  119. package/src/expected-client-error.ts +19 -0
  120. package/src/hono.ts +120 -41
  121. package/src/index.ts +3 -2
  122. package/src/issues.ts +35 -0
  123. package/src/json-schema.ts +16 -10
  124. package/src/live/codec.ts +21 -19
  125. package/src/live/fn-live.ts +18 -36
  126. package/src/live/index.ts +2 -2
  127. package/src/live/memory.ts +22 -1
  128. package/src/live/pub-sub.ts +142 -37
  129. package/src/live/redis-bun.ts +23 -2
  130. package/src/live/scoped-change-bus.ts +10 -1
  131. package/src/mastra.ts +10 -4
  132. package/src/mcp/core.ts +76 -29
  133. package/src/mcp/sdk.ts +27 -2
  134. package/src/mcp/server.ts +27 -2
  135. package/src/mcp.ts +5 -16
  136. package/src/meta.ts +28 -4
  137. package/src/otel.ts +41 -9
  138. package/src/react.ts +10 -3
  139. package/src/route-options.ts +8 -0
  140. package/src/stream-call.ts +15 -0
  141. package/src/timing.ts +3 -0
  142. package/src/tool-errors.ts +52 -0
  143. package/src/types.ts +2 -0
  144. package/src/vue.ts +92 -19
@@ -8,11 +8,12 @@ import { createRedisTransport } from './redis.js';
8
8
  * Call `close()` on shutdown to close the subscriber connection it created.
9
9
  */
10
10
  export function bunRedisTransport(client, options = {}) {
11
+ const openSubscriber = subscriberSource(client, options.subscriber);
11
12
  return createRedisTransport({
12
13
  send: (command, args) => client.send(command, args),
13
14
  async connectSubscriber(handlers) {
14
15
  const owned = !options.subscriber;
15
- const subscriber = options.subscriber ?? (await client.duplicate());
16
+ const subscriber = await openSubscriber();
16
17
  const dispatch = (message, channel) => handlers.onMessage(channel, message);
17
18
  const ours = new Set();
18
19
  // Bun reconnects on its own but drops every subscription. Also fires on
@@ -55,4 +56,12 @@ export function bunRedisTransport(client, options = {}) {
55
56
  }
56
57
  });
57
58
  }
59
+ function subscriberSource(client, given) {
60
+ if (given)
61
+ return async () => given;
62
+ const { duplicate } = client;
63
+ if (typeof duplicate !== 'function')
64
+ throw new TypeError('orpc-fn: bunRedisTransport needs a client with duplicate() or a subscriber');
65
+ return () => duplicate.call(client);
66
+ }
58
67
  //# sourceMappingURL=redis-bun.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis-bun.js","sourceRoot":"","sources":["../../src/live/redis-bun.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAiBjD;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB,CAChC,MAA0B,EAC1B,OAAO,GAAwC,EAAE;IAEjD,OAAO,oBAAoB,CAAC;QAC3B,IAAI,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC;QACnD,KAAK,CAAC,iBAAiB,CAAC,QAAQ;YAC/B,MAAM,KAAK,GAAG,CAAC,OAAO,CAAC,UAAU,CAAA;YACjC,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,CAAC,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC,CAAA;YACnE,MAAM,QAAQ,GAAa,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,CAC/C,QAAQ,CAAC,SAAS,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;YACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;YAC9B,wEAAwE;YACxE,2DAA2D;YAC3D,UAAU,CAAC,SAAS,GAAG,GAAG,EAAE,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAA;YACnD,mEAAmE;YACnE,sBAAsB;YACtB,UAAU,CAAC,OAAO,GAAG,CAAC,KAAK,EAAE,EAAE,CAC9B,QAAQ,CAAC,MAAM,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,yBAAyB,CAAC,CAAC,CAAA;YAC/D,OAAO;gBACN,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE;oBACtB,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;oBACjB,OAAO,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;gBAC/C,CAAC;gBACD,sEAAsE;gBACtE,kEAAkE;gBAClE,gDAAgD;gBAChD,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;oBAC9B,MAAM,OAAO,CAAC,OAAO,EAAE;yBACrB,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;yBAC3C,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;oBACjB,OAAO,UAAU,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;gBAC/C,CAAC;gBACD,WAAW,EAAE,CAAC,OAAO,EAAE,EAAE;oBACxB,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;oBACpB,OAAO,UAAU,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAA;gBACjD,CAAC;gBACD,SAAS,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,SAAS,KAAK,KAAK;gBAC/C,KAAK;oBACJ,6DAA6D;oBAC7D,UAAU,CAAC,SAAS,GAAG,GAAG,EAAE,GAAE,CAAC,CAAA;oBAC/B,UAAU,CAAC,OAAO,GAAG,GAAG,EAAE,GAAE,CAAC,CAAA;oBAC7B,IAAI,KAAK;wBAAE,OAAO,UAAU,CAAC,KAAK,EAAE,CAAA;oBACpC,yEAAyE;oBACzE,OAAO,OAAO,CAAC,GAAG,CACjB,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CACzB,OAAO,CAAC,OAAO,EAAE;yBACf,IAAI,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;yBACrD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CACjB,CACD,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;gBACjB,CAAC;aACD,CAAA;QACF,CAAC;KACD,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { createRedisTransport } from './redis.js'\n\ntype Listener = (message: string, channel: string) => void\n\n/** The part of Bun's `RedisClient` this transport uses. */\nexport interface BunRedisClientLike {\n\tsend(command: string, args: string[]): Promise<unknown>\n\tsubscribe(channel: string, listener: Listener): Promise<unknown>\n\tunsubscribe(channel: string, listener?: Listener): Promise<unknown>\n\tduplicate(): Promise<BunRedisClientLike>\n\tclose(): void\n\t/** Bun's `RedisClient.connected`; read by `stats()` when present. */\n\treadonly connected?: boolean\n\tonconnect: ((this: never) => void) | null\n\tonclose: ((this: never, error: Error) => void) | null\n}\n\n/**\n * `PubSubTransport` for Bun's built-in `RedisClient`. Subscriptions use a\n * dedicated connection (`client.duplicate()` unless `subscriber` is given)\n * that re-subscribes its channels after every reconnect; configure retries on\n * the client (`maxRetries`) since the duplicate copies its options.\n *\n * Call `close()` on shutdown to close the subscriber connection it created.\n */\nexport function bunRedisTransport(\n\tclient: BunRedisClientLike,\n\toptions: { subscriber?: BunRedisClientLike } = {}\n) {\n\treturn createRedisTransport({\n\t\tsend: (command, args) => client.send(command, args),\n\t\tasync connectSubscriber(handlers) {\n\t\t\tconst owned = !options.subscriber\n\t\t\tconst subscriber = options.subscriber ?? (await client.duplicate())\n\t\t\tconst dispatch: Listener = (message, channel) =>\n\t\t\t\thandlers.onMessage(channel, message)\n\t\t\tconst ours = new Set<string>()\n\t\t\t// Bun reconnects on its own but drops every subscription. Also fires on\n\t\t\t// the first connect, when there is nothing to restore yet.\n\t\t\tsubscriber.onconnect = () => handlers.onReconnect()\n\t\t\t// Fires only when Bun gives up reconnecting (and on close(), which\n\t\t\t// detaches it first).\n\t\t\tsubscriber.onclose = (error) =>\n\t\t\t\thandlers.onLost(error ?? new Error('Redis subscriber closed'))\n\t\t\treturn {\n\t\t\t\tsubscribe: (channel) => {\n\t\t\t\t\tours.add(channel)\n\t\t\t\t\treturn subscriber.subscribe(channel, dispatch)\n\t\t\t\t},\n\t\t\t\t// Bun keeps listeners across a reconnect and does not dedupe them, so\n\t\t\t\t// clear the channel before adding its listener again. unsubscribe\n\t\t\t\t// throws synchronously outside subscriber mode.\n\t\t\t\tresubscribe: async (channel) => {\n\t\t\t\t\tawait Promise.resolve()\n\t\t\t\t\t\t.then(() => subscriber.unsubscribe(channel))\n\t\t\t\t\t\t.catch(() => {})\n\t\t\t\t\treturn subscriber.subscribe(channel, dispatch)\n\t\t\t\t},\n\t\t\t\tunsubscribe: (channel) => {\n\t\t\t\t\tours.delete(channel)\n\t\t\t\t\treturn subscriber.unsubscribe(channel, dispatch)\n\t\t\t\t},\n\t\t\t\tconnected: () => subscriber.connected !== false,\n\t\t\t\tclose() {\n\t\t\t\t\t// No-ops, not null: Bun calls `onclose` without checking it.\n\t\t\t\t\tsubscriber.onconnect = () => {}\n\t\t\t\t\tsubscriber.onclose = () => {}\n\t\t\t\t\tif (owned) return subscriber.close()\n\t\t\t\t\t// A given subscriber stays open: remove only this transport's listeners.\n\t\t\t\t\treturn Promise.all(\n\t\t\t\t\t\t[...ours].map((channel) =>\n\t\t\t\t\t\t\tPromise.resolve()\n\t\t\t\t\t\t\t\t.then(() => subscriber.unsubscribe(channel, dispatch))\n\t\t\t\t\t\t\t\t.catch(() => {})\n\t\t\t\t\t\t)\n\t\t\t\t\t).then(() => {})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n"]}
1
+ {"version":3,"file":"redis-bun.js","sourceRoot":"","sources":["../../src/live/redis-bun.ts"],"names":[],"mappings":"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","sourcesContent":["import { createRedisTransport } from './redis.js'\n\ntype Listener = (message: string, channel: string) => void\n\n/** The part of Bun's `RedisClient` this transport uses. */\nexport interface BunRedisClientLike {\n\tsend(command: string, args: string[]): Promise<unknown>\n\tsubscribe(channel: string, listener: Listener): Promise<unknown>\n\tunsubscribe(channel: string, listener?: Listener): Promise<unknown>\n\tduplicate(): Promise<BunRedisClientLike>\n\tclose(): void\n\t/** Bun's `RedisClient.connected`; read by `stats()` when present. */\n\treadonly connected?: boolean\n\tonconnect: ((this: never) => void) | null\n\tonclose: ((this: never, error: Error) => void) | null\n}\n\n/**\n * What the command client must offer: `send`, and `duplicate` unless a\n * `subscriber` is given. A wrapper with timeouts around Bun's client fits.\n */\nexport type BunRedisCommandClientLike = Pick<BunRedisClientLike, 'send'> &\n\tPartial<Pick<BunRedisClientLike, 'duplicate'>>\n\n/**\n * `PubSubTransport` for Bun's built-in `RedisClient`. Subscriptions use a\n * dedicated connection (`client.duplicate()` unless `subscriber` is given)\n * that re-subscribes its channels after every reconnect; configure retries on\n * the client (`maxRetries`) since the duplicate copies its options.\n *\n * Call `close()` on shutdown to close the subscriber connection it created.\n */\nexport function bunRedisTransport(\n\tclient: BunRedisCommandClientLike,\n\toptions: { subscriber?: BunRedisClientLike } = {}\n) {\n\tconst openSubscriber = subscriberSource(client, options.subscriber)\n\treturn createRedisTransport({\n\t\tsend: (command, args) => client.send(command, args),\n\t\tasync connectSubscriber(handlers) {\n\t\t\tconst owned = !options.subscriber\n\t\t\tconst subscriber = await openSubscriber()\n\t\t\tconst dispatch: Listener = (message, channel) =>\n\t\t\t\thandlers.onMessage(channel, message)\n\t\t\tconst ours = new Set<string>()\n\t\t\t// Bun reconnects on its own but drops every subscription. Also fires on\n\t\t\t// the first connect, when there is nothing to restore yet.\n\t\t\tsubscriber.onconnect = () => handlers.onReconnect()\n\t\t\t// Fires only when Bun gives up reconnecting (and on close(), which\n\t\t\t// detaches it first).\n\t\t\tsubscriber.onclose = (error) =>\n\t\t\t\thandlers.onLost(error ?? new Error('Redis subscriber closed'))\n\t\t\treturn {\n\t\t\t\tsubscribe: (channel) => {\n\t\t\t\t\tours.add(channel)\n\t\t\t\t\treturn subscriber.subscribe(channel, dispatch)\n\t\t\t\t},\n\t\t\t\t// Bun keeps listeners across a reconnect and does not dedupe them, so\n\t\t\t\t// clear the channel before adding its listener again. unsubscribe\n\t\t\t\t// throws synchronously outside subscriber mode.\n\t\t\t\tresubscribe: async (channel) => {\n\t\t\t\t\tawait Promise.resolve()\n\t\t\t\t\t\t.then(() => subscriber.unsubscribe(channel))\n\t\t\t\t\t\t.catch(() => {})\n\t\t\t\t\treturn subscriber.subscribe(channel, dispatch)\n\t\t\t\t},\n\t\t\t\tunsubscribe: (channel) => {\n\t\t\t\t\tours.delete(channel)\n\t\t\t\t\treturn subscriber.unsubscribe(channel, dispatch)\n\t\t\t\t},\n\t\t\t\tconnected: () => subscriber.connected !== false,\n\t\t\t\tclose() {\n\t\t\t\t\t// No-ops, not null: Bun calls `onclose` without checking it.\n\t\t\t\t\tsubscriber.onconnect = () => {}\n\t\t\t\t\tsubscriber.onclose = () => {}\n\t\t\t\t\tif (owned) return subscriber.close()\n\t\t\t\t\t// A given subscriber stays open: remove only this transport's listeners.\n\t\t\t\t\treturn Promise.all(\n\t\t\t\t\t\t[...ours].map((channel) =>\n\t\t\t\t\t\t\tPromise.resolve()\n\t\t\t\t\t\t\t\t.then(() => subscriber.unsubscribe(channel, dispatch))\n\t\t\t\t\t\t\t\t.catch(() => {})\n\t\t\t\t\t\t)\n\t\t\t\t\t).then(() => {})\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunction subscriberSource(\n\tclient: BunRedisCommandClientLike,\n\tgiven: BunRedisClientLike | undefined\n): () => Promise<BunRedisClientLike> {\n\tif (given) return async () => given\n\tconst { duplicate } = client\n\tif (typeof duplicate !== 'function')\n\t\tthrow new TypeError(\n\t\t\t'orpc-fn: bunRedisTransport needs a client with duplicate() or a subscriber'\n\t\t)\n\treturn () => duplicate.call(client)\n}\n"]}
@@ -29,6 +29,8 @@ declare const noInput: z.ZodObject<{}, z.core.$strip>;
29
29
  * subscriber in a scope "something changed". Each bus then names only its
30
30
  * channel prefix: events go to `${prefix}:${scope}`. Publishing is safe by
31
31
  * default (`safePublish: true`), because buses publish after committed writes.
32
+ * The scope is the access decision (a subscriber hears only its own scope),
33
+ * so `authFn` defaults to `true`.
32
34
  *
33
35
  * ```ts
34
36
  * const defineChangeBus = defineScopedChangeBus(createPubSub, {
@@ -43,7 +45,7 @@ declare const noInput: z.ZodObject<{}, z.core.$strip>;
43
45
  * // fnLive({ live: { channel: dealChanges.channel, ... } }) shares the channel.
44
46
  * ```
45
47
  */
46
- export declare function defineScopedChangeBus<TDef extends FnDefinition, TScopeEvent>(createPubSub: CreatePubSub<TDef>, scope: ChangeBusScope<AnyFnContext<TDef>, TScopeEvent>): <TEventSchema extends ZodType<TScopeEvent>, TInputSchema extends ObjectSchema = typeof noInput, TKey extends ProcedureKey<TDef> = TDef['default']>(options: Omit<PubSubOptions<TDef, TInputSchema, TEventSchema, NoInfer<TKey>>, 'channel' | 'inputSchema'> & GuardOptions<TDef['guards']> & Pick<FnRouteOptions<TDef, TKey, z.output<TInputSchema>>, 'meta' | 'guardResolvers'> & ProcedureOption<TDef, TKey> & {
48
+ export declare function defineScopedChangeBus<TDef extends FnDefinition, TScopeEvent>(createPubSub: CreatePubSub<TDef>, scope: ChangeBusScope<AnyFnContext<TDef>, TScopeEvent>): <TEventSchema extends ZodType<TScopeEvent>, TInputSchema extends ObjectSchema = typeof noInput, TKey extends ProcedureKey<TDef> = TDef['default']>(options: Omit<PubSubOptions<TDef, TInputSchema, TEventSchema, NoInfer<TKey>>, 'channel' | 'inputSchema' | 'authFn'> & Partial<Pick<PubSubOptions<TDef, TInputSchema, TEventSchema, NoInfer<TKey>>, 'authFn'>> & GuardOptions<TDef['guards']> & Pick<FnRouteOptions<TDef, TKey, z.output<TInputSchema>>, 'meta' | 'guardResolvers'> & ProcedureOption<TDef, TKey> & {
47
49
  /** Channel prefix; the scope key is appended as `${prefix}:${scope}`. */
48
50
  prefix: string;
49
51
  /** What the subscriber provides (default: no input). */
@@ -1 +1 @@
1
- {"version":3,"file":"scoped-change-bus.d.ts","sourceRoot":"","sources":["../../src/live/scoped-change-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACrC,OAAO,KAAK,EACX,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EACX,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,aAAa,EACb,MAAM,cAAc,CAAA;AAErB,6EAA6E;AAC7E,MAAM,MAAM,cAAc,CAAC,QAAQ,EAAE,MAAM,IAAI;IAC9C,sFAAsF;IACtF,SAAS,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAA;IACxC,0DAA0D;IAC1D,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CAClC,CAAA;AAED,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,CAAC,QAAQ,EAAE,MAAM,IAAI;IAChD,SAAS,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,QAAQ,CAAA;KAAE,KAAK,MAAM,CAAA;IACpD,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,eAAe,CAC1B,IAAI,SAAS,YAAY,EACzB,YAAY,SAAS,YAAY,EACjC,YAAY,SAAS,OAAO,EAC5B,IAAI,SAAS,YAAY,CAAC,IAAI,CAAC,EAC/B,WAAW,IACR,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG;IACpD,OAAO,EAAE,gBAAgB,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAA;IAC1D;;;OAGG;IACH,UAAU,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,MAAM,CAAA;CAC1C,CAAA;AAED,QAAA,MAAM,OAAO,gCAAe,CAAA;AAE5B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,SAAS,YAAY,EAAE,WAAW,EAC3E,YAAY,EAAE,YAAY,CAAC,IAAI,CAAC,EAChC,KAAK,EAAE,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,IAGrD,YAAY,SAAS,OAAO,CAAC,WAAW,CAAC,EACzC,YAAY,SAAS,YAAY,GAAG,OAAO,OAAO,EAClD,IAAI,SAAS,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,WAExC,IAAI,CACZ,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,EAC9D,SAAS,GAAG,aAAa,CACzB,GACA,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAC5B,IAAI,CACH,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,EAClD,MAAM,GAAG,gBAAgB,CACzB,GACD,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG;IAC7B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IACd,wDAAwD;IACxD,WAAW,CAAC,EAAE,YAAY,CAAA;CAC1B,KACA,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,CAAC,CAoBvE"}
1
+ {"version":3,"file":"scoped-change-bus.d.ts","sourceRoot":"","sources":["../../src/live/scoped-change-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACrC,OAAO,KAAK,EACX,YAAY,EACZ,YAAY,EACZ,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,MAAM,aAAa,CAAA;AACpB,OAAO,KAAK,EACX,YAAY,EACZ,YAAY,EACZ,MAAM,EACN,aAAa,EACb,MAAM,cAAc,CAAA;AAErB,6EAA6E;AAC7E,MAAM,MAAM,cAAc,CAAC,QAAQ,EAAE,MAAM,IAAI;IAC9C,sFAAsF;IACtF,SAAS,EAAE,CAAC,OAAO,EAAE,QAAQ,KAAK,MAAM,CAAA;IACxC,0DAA0D;IAC1D,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CAClC,CAAA;AAED,iFAAiF;AACjF,MAAM,MAAM,gBAAgB,CAAC,QAAQ,EAAE,MAAM,IAAI;IAChD,SAAS,EAAE,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,QAAQ,CAAA;KAAE,KAAK,MAAM,CAAA;IACpD,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;CAClC,CAAA;AAED,MAAM,MAAM,eAAe,CAC1B,IAAI,SAAS,YAAY,EACzB,YAAY,SAAS,YAAY,EACjC,YAAY,SAAS,OAAO,EAC5B,IAAI,SAAS,YAAY,CAAC,IAAI,CAAC,EAC/B,WAAW,IACR,MAAM,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG;IACpD,OAAO,EAAE,gBAAgB,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,CAAA;IAC1D;;;OAGG;IACH,UAAU,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,MAAM,CAAA;CAC1C,CAAA;AAED,QAAA,MAAM,OAAO,gCAAe,CAAA;AAE5B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,SAAS,YAAY,EAAE,WAAW,EAC3E,YAAY,EAAE,YAAY,CAAC,IAAI,CAAC,EAChC,KAAK,EAAE,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,IAGrD,YAAY,SAAS,OAAO,CAAC,WAAW,CAAC,EACzC,YAAY,SAAS,YAAY,GAAG,OAAO,OAAO,EAClD,IAAI,SAAS,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,WAExC,IAAI,CACZ,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,EAC9D,SAAS,GAAG,aAAa,GAAG,QAAQ,CACpC,GACA,OAAO,CACN,IAAI,CACH,aAAa,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,EAC9D,QAAQ,CACR,CACD,GACD,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,GAC5B,IAAI,CACH,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,EAClD,MAAM,GAAG,gBAAgB,CACzB,GACD,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,GAAG;IAC7B,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAA;IACd,wDAAwD;IACxD,WAAW,CAAC,EAAE,YAAY,CAAA;CAC1B,KACA,eAAe,CAAC,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,IAAI,EAAE,WAAW,CAAC,CAqBvE"}
@@ -5,6 +5,8 @@ const noInput = z.object({});
5
5
  * subscriber in a scope "something changed". Each bus then names only its
6
6
  * channel prefix: events go to `${prefix}:${scope}`. Publishing is safe by
7
7
  * default (`safePublish: true`), because buses publish after committed writes.
8
+ * The scope is the access decision (a subscriber hears only its own scope),
9
+ * so `authFn` defaults to `true`.
8
10
  *
9
11
  * ```ts
10
12
  * const defineChangeBus = defineScopedChangeBus(createPubSub, {
@@ -30,6 +32,7 @@ export function defineScopedChangeBus(createPubSub, scope) {
30
32
  // signature of createPubSub cannot see.
31
33
  const bus = createPubSub({
32
34
  safePublish: true,
35
+ authFn: true,
33
36
  ...pubsubOptions,
34
37
  inputSchema: pubsubOptions.inputSchema ?? noInput,
35
38
  channel
@@ -1 +1 @@
1
- {"version":3,"file":"scoped-change-bus.js","sourceRoot":"","sources":["../../src/live/scoped-change-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,CAAC,EAAE,MAAM,KAAK,CAAA;AA6CrC,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AAE5B;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,qBAAqB,CACpC,YAAgC,EAChC,KAAsD;IAEtD,OAAO,CAKN,OAcE,EACqE,EAAE;QACzE,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,GAAG,OAAO,CAAA;QAC5C,MAAM,OAAO,GAAsD;YAClE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE;YACnE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;SACvD,CAAA;QACD,qEAAqE;QACrE,wCAAwC;QACxC,MAAM,GAAG,GAAI,YAA4C,CAAC;YACzD,WAAW,EAAE,IAAI;YACjB,GAAG,aAAa;YAChB,WAAW,EAAE,aAAa,CAAC,WAAW,IAAI,OAAO;YACjD,OAAO;SACP,CAAmD,CAAA;QACpD,0EAA0E;QAC1E,MAAM,UAAU,GAAG,GAAG,CAAC,qBAEZ,CAAA;QACX,OAAO,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA;IACvC,CAAC,CAAA;AACF,CAAC","sourcesContent":["import { type ZodType, z } from 'zod'\nimport type {\n\tAnyFnContext,\n\tFnDefinition,\n\tFnRouteOptions,\n\tGuardOptions,\n\tProcedureKey,\n\tProcedureOption\n} from '../types.js'\nimport type {\n\tCreatePubSub,\n\tObjectSchema,\n\tPubSub,\n\tPubSubOptions\n} from './pub-sub.js'\n\n/** Where a change bus partitions its channels, e.g. one per organization. */\nexport type ChangeBusScope<TContext, TEvent> = {\n\t/** The subscriber's scope key, from its context. Throw to refuse the subscription. */\n\tsubscribe: (context: TContext) => string\n\t/** The event's scope key, carried in the event itself. */\n\tpublish: (event: TEvent) => string\n}\n\n/** A bus's channel definition; pass it to `fnLive` to share the bus's events. */\nexport type ChangeBusChannel<TContext, TEvent> = {\n\tsubscribe: (params: { context: TContext }) => string\n\tpublish: (event: TEvent) => string\n}\n\nexport type ScopedChangeBus<\n\tTDef extends FnDefinition,\n\tTInputSchema extends ObjectSchema,\n\tTEventSchema extends ZodType,\n\tTKey extends ProcedureKey<TDef>,\n\tTScopeEvent\n> = PubSub<TDef, TInputSchema, TEventSchema, TKey> & {\n\tchannel: ChangeBusChannel<AnyFnContext<TDef>, TScopeEvent>\n\t/**\n\t * The full channel name for a scope, namespace included:\n\t * `backlogKey(bus.channelFor({ organizationId }))` reads its backlog.\n\t */\n\tchannelFor: (scope: TScopeEvent) => string\n}\n\nconst noInput = z.object({})\n\n/**\n * Binds the app's `createPubSub` to one scope rule, for buses that tell every\n * subscriber in a scope \"something changed\". Each bus then names only its\n * channel prefix: events go to `${prefix}:${scope}`. Publishing is safe by\n * default (`safePublish: true`), because buses publish after committed writes.\n *\n * ```ts\n * const defineChangeBus = defineScopedChangeBus(createPubSub, {\n * subscribe: (context) => activeOrganizationId(context),\n * publish: (event: { organizationId: string }) => event.organizationId\n * })\n * export const dealChanges = defineChangeBus({\n * name: 'deal.subscribe',\n * prefix: 'deal:change',\n * eventSchema: z.object({ organizationId: z.string(), dealId: z.string() })\n * })\n * // fnLive({ live: { channel: dealChanges.channel, ... } }) shares the channel.\n * ```\n */\nexport function defineScopedChangeBus<TDef extends FnDefinition, TScopeEvent>(\n\tcreatePubSub: CreatePubSub<TDef>,\n\tscope: ChangeBusScope<AnyFnContext<TDef>, TScopeEvent>\n) {\n\treturn <\n\t\tTEventSchema extends ZodType<TScopeEvent>,\n\t\tTInputSchema extends ObjectSchema = typeof noInput,\n\t\tTKey extends ProcedureKey<TDef> = TDef['default']\n\t>(\n\t\toptions: Omit<\n\t\t\tPubSubOptions<TDef, TInputSchema, TEventSchema, NoInfer<TKey>>,\n\t\t\t'channel' | 'inputSchema'\n\t\t> &\n\t\t\tGuardOptions<TDef['guards']> &\n\t\t\tPick<\n\t\t\t\tFnRouteOptions<TDef, TKey, z.output<TInputSchema>>,\n\t\t\t\t'meta' | 'guardResolvers'\n\t\t\t> &\n\t\t\tProcedureOption<TDef, TKey> & {\n\t\t\t\t/** Channel prefix; the scope key is appended as `${prefix}:${scope}`. */\n\t\t\t\tprefix: string\n\t\t\t\t/** What the subscriber provides (default: no input). */\n\t\t\t\tinputSchema?: TInputSchema\n\t\t\t}\n\t): ScopedChangeBus<TDef, TInputSchema, TEventSchema, TKey, TScopeEvent> => {\n\t\tconst { prefix, ...pubsubOptions } = options\n\t\tconst channel: ChangeBusChannel<AnyFnContext<TDef>, TScopeEvent> = {\n\t\t\tsubscribe: ({ context }) => `${prefix}:${scope.subscribe(context)}`,\n\t\t\tpublish: (event) => `${prefix}:${scope.publish(event)}`\n\t\t}\n\t\t// The bus's context is one member of AnyFnContext, which the generic\n\t\t// signature of createPubSub cannot see.\n\t\tconst bus = (createPubSub as (options: object) => object)({\n\t\t\tsafePublish: true,\n\t\t\t...pubsubOptions,\n\t\t\tinputSchema: pubsubOptions.inputSchema ?? noInput,\n\t\t\tchannel\n\t\t}) as PubSub<TDef, TInputSchema, TEventSchema, TKey>\n\t\t// The scope reads only its own fields, so a scope stands in for an event.\n\t\tconst channelFor = bus.getPublishChannelName as (\n\t\t\tscope: TScopeEvent\n\t\t) => string\n\t\treturn { ...bus, channel, channelFor }\n\t}\n}\n"]}
1
+ {"version":3,"file":"scoped-change-bus.js","sourceRoot":"","sources":["../../src/live/scoped-change-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,CAAC,EAAE,MAAM,KAAK,CAAA;AA6CrC,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;AAE5B;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,qBAAqB,CACpC,YAAgC,EAChC,KAAsD;IAEtD,OAAO,CAKN,OAoBE,EACqE,EAAE;QACzE,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,GAAG,OAAO,CAAA;QAC5C,MAAM,OAAO,GAAsD;YAClE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,CAAC,EAAE;YACnE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;SACvD,CAAA;QACD,qEAAqE;QACrE,wCAAwC;QACxC,MAAM,GAAG,GAAI,YAA4C,CAAC;YACzD,WAAW,EAAE,IAAI;YACjB,MAAM,EAAE,IAAI;YACZ,GAAG,aAAa;YAChB,WAAW,EAAE,aAAa,CAAC,WAAW,IAAI,OAAO;YACjD,OAAO;SACP,CAAmD,CAAA;QACpD,0EAA0E;QAC1E,MAAM,UAAU,GAAG,GAAG,CAAC,qBAEZ,CAAA;QACX,OAAO,EAAE,GAAG,GAAG,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA;IACvC,CAAC,CAAA;AACF,CAAC","sourcesContent":["import { type ZodType, z } from 'zod'\nimport type {\n\tAnyFnContext,\n\tFnDefinition,\n\tFnRouteOptions,\n\tGuardOptions,\n\tProcedureKey,\n\tProcedureOption\n} from '../types.js'\nimport type {\n\tCreatePubSub,\n\tObjectSchema,\n\tPubSub,\n\tPubSubOptions\n} from './pub-sub.js'\n\n/** Where a change bus partitions its channels, e.g. one per organization. */\nexport type ChangeBusScope<TContext, TEvent> = {\n\t/** The subscriber's scope key, from its context. Throw to refuse the subscription. */\n\tsubscribe: (context: TContext) => string\n\t/** The event's scope key, carried in the event itself. */\n\tpublish: (event: TEvent) => string\n}\n\n/** A bus's channel definition; pass it to `fnLive` to share the bus's events. */\nexport type ChangeBusChannel<TContext, TEvent> = {\n\tsubscribe: (params: { context: TContext }) => string\n\tpublish: (event: TEvent) => string\n}\n\nexport type ScopedChangeBus<\n\tTDef extends FnDefinition,\n\tTInputSchema extends ObjectSchema,\n\tTEventSchema extends ZodType,\n\tTKey extends ProcedureKey<TDef>,\n\tTScopeEvent\n> = PubSub<TDef, TInputSchema, TEventSchema, TKey> & {\n\tchannel: ChangeBusChannel<AnyFnContext<TDef>, TScopeEvent>\n\t/**\n\t * The full channel name for a scope, namespace included:\n\t * `backlogKey(bus.channelFor({ organizationId }))` reads its backlog.\n\t */\n\tchannelFor: (scope: TScopeEvent) => string\n}\n\nconst noInput = z.object({})\n\n/**\n * Binds the app's `createPubSub` to one scope rule, for buses that tell every\n * subscriber in a scope \"something changed\". Each bus then names only its\n * channel prefix: events go to `${prefix}:${scope}`. Publishing is safe by\n * default (`safePublish: true`), because buses publish after committed writes.\n * The scope is the access decision (a subscriber hears only its own scope),\n * so `authFn` defaults to `true`.\n *\n * ```ts\n * const defineChangeBus = defineScopedChangeBus(createPubSub, {\n * subscribe: (context) => activeOrganizationId(context),\n * publish: (event: { organizationId: string }) => event.organizationId\n * })\n * export const dealChanges = defineChangeBus({\n * name: 'deal.subscribe',\n * prefix: 'deal:change',\n * eventSchema: z.object({ organizationId: z.string(), dealId: z.string() })\n * })\n * // fnLive({ live: { channel: dealChanges.channel, ... } }) shares the channel.\n * ```\n */\nexport function defineScopedChangeBus<TDef extends FnDefinition, TScopeEvent>(\n\tcreatePubSub: CreatePubSub<TDef>,\n\tscope: ChangeBusScope<AnyFnContext<TDef>, TScopeEvent>\n) {\n\treturn <\n\t\tTEventSchema extends ZodType<TScopeEvent>,\n\t\tTInputSchema extends ObjectSchema = typeof noInput,\n\t\tTKey extends ProcedureKey<TDef> = TDef['default']\n\t>(\n\t\toptions: Omit<\n\t\t\tPubSubOptions<TDef, TInputSchema, TEventSchema, NoInfer<TKey>>,\n\t\t\t'channel' | 'inputSchema' | 'authFn'\n\t\t> &\n\t\t\tPartial<\n\t\t\t\tPick<\n\t\t\t\t\tPubSubOptions<TDef, TInputSchema, TEventSchema, NoInfer<TKey>>,\n\t\t\t\t\t'authFn'\n\t\t\t\t>\n\t\t\t> &\n\t\t\tGuardOptions<TDef['guards']> &\n\t\t\tPick<\n\t\t\t\tFnRouteOptions<TDef, TKey, z.output<TInputSchema>>,\n\t\t\t\t'meta' | 'guardResolvers'\n\t\t\t> &\n\t\t\tProcedureOption<TDef, TKey> & {\n\t\t\t\t/** Channel prefix; the scope key is appended as `${prefix}:${scope}`. */\n\t\t\t\tprefix: string\n\t\t\t\t/** What the subscriber provides (default: no input). */\n\t\t\t\tinputSchema?: TInputSchema\n\t\t\t}\n\t): ScopedChangeBus<TDef, TInputSchema, TEventSchema, TKey, TScopeEvent> => {\n\t\tconst { prefix, ...pubsubOptions } = options\n\t\tconst channel: ChangeBusChannel<AnyFnContext<TDef>, TScopeEvent> = {\n\t\t\tsubscribe: ({ context }) => `${prefix}:${scope.subscribe(context)}`,\n\t\t\tpublish: (event) => `${prefix}:${scope.publish(event)}`\n\t\t}\n\t\t// The bus's context is one member of AnyFnContext, which the generic\n\t\t// signature of createPubSub cannot see.\n\t\tconst bus = (createPubSub as (options: object) => object)({\n\t\t\tsafePublish: true,\n\t\t\tauthFn: true,\n\t\t\t...pubsubOptions,\n\t\t\tinputSchema: pubsubOptions.inputSchema ?? noInput,\n\t\t\tchannel\n\t\t}) as PubSub<TDef, TInputSchema, TEventSchema, TKey>\n\t\t// The scope reads only its own fields, so a scope stands in for an event.\n\t\tconst channelFor = bus.getPublishChannelName as (\n\t\t\tscope: TScopeEvent\n\t\t) => string\n\t\treturn { ...bus, channel, channelFor }\n\t}\n}\n"]}
package/dist/mastra.d.ts CHANGED
@@ -2,12 +2,13 @@ import type { RequestContext } from '@mastra/core/request-context';
2
2
  import { type Tool } from '@mastra/core/tools';
3
3
  import { type AnyProcedure } from '@orpc/server';
4
4
  import { toToolName } from './meta.js';
5
+ import { type ToolErrorOptions } from './tool-errors.js';
5
6
  import type { ProcedureInput, ProcedureOutput } from './types.js';
6
- export { toToolName };
7
- export type CreateMastraToolOptions = {
7
+ export { toToolName, type ToolErrorOptions };
8
+ export type CreateMastraToolOptions = ToolErrorOptions & {
8
9
  /** Override the tool id (defaults to the sanitized `fn` name). */
9
10
  id?: string;
10
- /** Override the tool description (defaults to the route summary). */
11
+ /** Override the tool description (defaults to the route description, else its summary). */
11
12
  description?: string;
12
13
  /** Gate execution behind the AI SDK tool-approval flow. */
13
14
  requireApproval?: boolean;
@@ -47,6 +48,10 @@ type ObjectInputCheck<TProc> = MastraToolInput<TProc> extends Record<string, unk
47
48
  * Mastra checks the input against the procedure's schema (with dates coerced
48
49
  * from strings) but passes the raw input on for the procedure to parse;
49
50
  * the output is the procedure's parsed result, passed through unchanged.
51
+ *
52
+ * A failed call throws an `Error` whose message is what the model reads:
53
+ * `CODE: message` with validation paths for an expected error, a fixed
54
+ * sentence for anything else (reported to `onError`).
50
55
  */
51
56
  export declare function createMastraTool<TProc extends AnyProcedure>(procedure: TProc & ObjectInputCheck<TProc>, options?: CreateMastraToolOptions): MastraTool<TProc>;
52
57
  //# sourceMappingURL=mastra.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mastra.d.ts","sourceRoot":"","sources":["../src/mastra.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,cAAc,CAAA;AAOtD,OAAO,EAAc,UAAU,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjE,OAAO,EAAE,UAAU,EAAE,CAAA;AAErB,MAAM,MAAM,uBAAuB,GAAG;IACrC,kEAAkE;IAClE,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,qEAAqE;IACrE,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QACzB,MAAM,EAAE,MAAM,CAAA;QACd,UAAU,EAAE,MAAM,CAAA;QAClB,OAAO,EAAE,SAAS,GAAG,OAAO,CAAA;KAC5B,KAAK,IAAI,CAAA;IACV,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,cAAc,EAAE,cAAc,KAAK,OAAO,CAAA;CACrD,CAAA;AAED,gFAAgF;AAChF,MAAM,MAAM,eAAe,CAAC,KAAK,IAChC,OAAO,SAAS,cAAc,CAAC,KAAK,CAAC,GAClC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACrB,cAAc,CAAC,KAAK,CAAC,CAAA;AAEzB,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,YAAY,IAAI,IAAI,CACxD,eAAe,CAAC,KAAK,CAAC,EACtB,eAAe,CAAC,KAAK,CAAC,CACtB,CAAA;AAED,4EAA4E;AAC5E,KAAK,gBAAgB,CAAC,KAAK,IAC1B,eAAe,CAAC,KAAK,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnD,OAAO,GACP;IAAE,8CAA8C,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,CAAA;AAE7E;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,YAAY,EAC1D,SAAS,EAAE,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,EAC1C,OAAO,GAAE,uBAA4B,GACnC,UAAU,CAAC,KAAK,CAAC,CAwFnB"}
1
+ {"version":3,"file":"mastra.d.ts","sourceRoot":"","sources":["../src/mastra.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAA;AAClE,OAAO,EAAc,KAAK,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAC1D,OAAO,EAAE,KAAK,YAAY,EAAQ,MAAM,cAAc,CAAA;AAOtD,OAAO,EAAc,UAAU,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,EAAE,KAAK,gBAAgB,EAAiB,MAAM,kBAAkB,CAAA;AACvE,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjE,OAAO,EAAE,UAAU,EAAE,KAAK,gBAAgB,EAAE,CAAA;AAE5C,MAAM,MAAM,uBAAuB,GAAG,gBAAgB,GAAG;IACxD,kEAAkE;IAClE,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB;;;OAGG;IACH,uBAAuB,CAAC,EAAE,OAAO,CAAA;IACjC,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE;QACzB,MAAM,EAAE,MAAM,CAAA;QACd,UAAU,EAAE,MAAM,CAAA;QAClB,OAAO,EAAE,SAAS,GAAG,OAAO,CAAA;KAC5B,KAAK,IAAI,CAAA;IACV,wFAAwF;IACxF,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,cAAc,EAAE,cAAc,KAAK,OAAO,CAAA;CACrD,CAAA;AAED,gFAAgF;AAChF,MAAM,MAAM,eAAe,CAAC,KAAK,IAChC,OAAO,SAAS,cAAc,CAAC,KAAK,CAAC,GAClC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GACrB,cAAc,CAAC,KAAK,CAAC,CAAA;AAEzB,0EAA0E;AAC1E,MAAM,MAAM,UAAU,CAAC,KAAK,SAAS,YAAY,IAAI,IAAI,CACxD,eAAe,CAAC,KAAK,CAAC,EACtB,eAAe,CAAC,KAAK,CAAC,CACtB,CAAA;AAED,4EAA4E;AAC5E,KAAK,gBAAgB,CAAC,KAAK,IAC1B,eAAe,CAAC,KAAK,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnD,OAAO,GACP;IAAE,8CAA8C,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,CAAA;AAE7E;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,YAAY,EAC1D,SAAS,EAAE,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,EAC1C,OAAO,GAAE,uBAA4B,GACnC,UAAU,CAAC,KAAK,CAAC,CAyFnB"}
package/dist/mastra.js CHANGED
@@ -2,6 +2,7 @@ import { createTool } from '@mastra/core/tools';
2
2
  import { call } from '@orpc/server';
3
3
  import { isObjectJsonSchema, passThroughOutputSchema, rawInputSchema } from './json-schema.js';
4
4
  import { readFnMeta, toToolName } from './meta.js';
5
+ import { toolErrorText } from './tool-errors.js';
5
6
  export { toToolName };
6
7
  /**
7
8
  * Creates a Mastra tool from a procedure made with `fn()`. Execution calls
@@ -11,6 +12,10 @@ export { toToolName };
11
12
  * Mastra checks the input against the procedure's schema (with dates coerced
12
13
  * from strings) but passes the raw input on for the procedure to parse;
13
14
  * the output is the procedure's parsed result, passed through unchanged.
15
+ *
16
+ * A failed call throws an `Error` whose message is what the model reads:
17
+ * `CODE: message` with validation paths for an expected error, a fixed
18
+ * sentence for anything else (reported to `onError`).
14
19
  */
15
20
  export function createMastraTool(procedure, options = {}) {
16
21
  const meta = readFnMeta(procedure);
@@ -82,7 +87,9 @@ export function createMastraTool(procedure, options = {}) {
82
87
  }
83
88
  catch (error) {
84
89
  finish('error');
85
- throw error;
90
+ if (ctx?.abortSignal?.aborted)
91
+ throw error;
92
+ throw new Error(toolErrorText(error, id, options));
86
93
  }
87
94
  }
88
95
  });
@@ -1 +1 @@
1
- {"version":3,"file":"mastra.js","sourceRoot":"","sources":["../src/mastra.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RequestContext } from '@mastra/core/request-context'\nimport { createTool, type Tool } from '@mastra/core/tools'\nimport { type AnyProcedure, call } from '@orpc/server'\nimport {\n\tisObjectJsonSchema,\n\tpassThroughOutputSchema,\n\trawInputSchema,\n\ttype ToolSchema\n} from './json-schema.js'\nimport { readFnMeta, toToolName } from './meta.js'\nimport type { ProcedureInput, ProcedureOutput } from './types.js'\n\nexport { toToolName }\n\nexport type CreateMastraToolOptions = {\n\t/** Override the tool id (defaults to the sanitized `fn` name). */\n\tid?: string\n\t/** Override the tool description (defaults to the route summary). */\n\tdescription?: string\n\t/** Gate execution behind the AI SDK tool-approval flow. */\n\trequireApproval?: boolean\n\t/**\n\t * Accept a procedure without an input schema (the tool then takes an empty\n\t * object). For no-input queries like `teams.list`.\n\t */\n\tallowMissingInputSchema?: boolean\n\tonExecuteFinish?: (event: {\n\t\ttoolId: string\n\t\tdurationMs: number\n\t\toutcome: 'success' | 'error'\n\t}) => void\n\t/** Key holding the oRPC context in Mastra's `RequestContext`. Default 'orpcContext'. */\n\tcontextKey?: string\n\t/**\n\t * Reads the oRPC context from the run's request context, in place of\n\t * `contextKey`. Mastra writes plain request-context values into run\n\t * snapshots and spans, so an app whose context holds a session keeps it\n\t * in a slot that serializes to nothing and reads it here.\n\t */\n\tcontext?: (requestContext: RequestContext) => unknown\n}\n\n/** What the tool takes: the procedure's raw input, or `{}` without a schema. */\nexport type MastraToolInput<TProc> =\n\tunknown extends ProcedureInput<TProc>\n\t\t? Record<string, never>\n\t\t: ProcedureInput<TProc>\n\n/** A Mastra tool typed by the procedure's raw input and parsed output. */\nexport type MastraTool<TProc extends AnyProcedure> = Tool<\n\tMastraToolInput<TProc>,\n\tProcedureOutput<TProc>\n>\n\n/** LLM tool parameters must be an object; other inputs are a type error. */\ntype ObjectInputCheck<TProc> =\n\tMastraToolInput<TProc> extends Record<string, unknown>\n\t\t? unknown\n\t\t: { 'orpc-fn: an agent tool needs an object input': ProcedureInput<TProc> }\n\n/**\n * Creates a Mastra tool from a procedure made with `fn()`. Execution calls\n * the procedure with the oRPC context stored in the Mastra request context\n * (`requestContext.set('orpcContext', context)`), so auth and guards apply.\n *\n * Mastra checks the input against the procedure's schema (with dates coerced\n * from strings) but passes the raw input on for the procedure to parse;\n * the output is the procedure's parsed result, passed through unchanged.\n */\nexport function createMastraTool<TProc extends AnyProcedure>(\n\tprocedure: TProc & ObjectInputCheck<TProc>,\n\toptions: CreateMastraToolOptions = {}\n): MastraTool<TProc> {\n\tconst meta = readFnMeta(procedure)\n\tif (meta.stream)\n\t\tthrow new TypeError('orpc-fn: streaming procedures cannot be Mastra tools')\n\tconst name = options.id ?? meta.name\n\tif (!name) {\n\t\tthrow new Error(\n\t\t\t'Procedure must be created with fn() to use createMastraTool'\n\t\t)\n\t}\n\tlet inputSchema: ToolSchema | undefined\n\tif (meta.inputSchema) {\n\t\tinputSchema = rawInputSchema(meta.inputSchema)\n\t\tif (!isObjectJsonSchema(inputSchema['~standard'].jsonSchema.input())) {\n\t\t\tthrow new Error(`${name} must take an object input to be an agent tool`)\n\t\t}\n\t} else if (!options.allowMissingInputSchema) {\n\t\tthrow new Error(\n\t\t\t`${name} must declare an input schema to be exposed as an agent tool`\n\t\t)\n\t}\n\n\tconst id = options.id ?? toToolName(name)\n\tconst contextKey = options.contextKey ?? 'orpcContext'\n\n\tconst tool = createTool({\n\t\tid,\n\t\tdescription: options.description ?? meta.description,\n\t\trequireApproval: options.requireApproval === true,\n\t\tinputSchema:\n\t\t\tinputSchema ??\n\t\t\t({\n\t\t\t\t'~standard': {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tvendor: 'orpc-fn',\n\t\t\t\t\tvalidate: () => ({ value: {} }),\n\t\t\t\t\tjsonSchema: {\n\t\t\t\t\t\tinput: () => ({ type: 'object', properties: {} }),\n\t\t\t\t\t\toutput: () => ({ type: 'object', properties: {} })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} satisfies ToolSchema),\n\t\t...(meta.outputSchema\n\t\t\t? { outputSchema: passThroughOutputSchema(meta.outputSchema) }\n\t\t\t: {}),\n\t\texecute: async (input, ctx) => {\n\t\t\tconst requestContext = ctx?.requestContext as RequestContext | undefined\n\t\t\tconst context =\n\t\t\t\trequestContext &&\n\t\t\t\t(options.context\n\t\t\t\t\t? options.context(requestContext)\n\t\t\t\t\t: requestContext.get(contextKey))\n\t\t\tif (!context) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\toptions.context\n\t\t\t\t\t\t? 'createMastraTool execution found no oRPC context through its context option'\n\t\t\t\t\t\t: `createMastraTool execution requires ${contextKey} in the Mastra request context`\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst startedAt = performance.now()\n\t\t\tconst finish = (outcome: 'success' | 'error') => {\n\t\t\t\ttry {\n\t\t\t\t\toptions.onExecuteFinish?.({\n\t\t\t\t\t\ttoolId: id,\n\t\t\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\t\t\toutcome\n\t\t\t\t\t})\n\t\t\t\t} catch {\n\t\t\t\t\t/* A telemetry hook cannot change tool execution. */\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await call(\n\t\t\t\t\tprocedure,\n\t\t\t\t\tmeta.inputSchema ? input : undefined,\n\t\t\t\t\tctx?.abortSignal ? { context, signal: ctx.abortSignal } : { context }\n\t\t\t\t)\n\t\t\t\tfinish('success')\n\t\t\t\treturn result\n\t\t\t} catch (error) {\n\t\t\t\tfinish('error')\n\t\t\t\tthrow error\n\t\t\t}\n\t\t}\n\t})\n\n\t// The runtime schemas are untyped `ToolSchema`s; `MastraTool` is the typed surface.\n\treturn tool as unknown as MastraTool<TProc>\n}\n"]}
1
+ {"version":3,"file":"mastra.js","sourceRoot":"","sources":["../src/mastra.ts"],"names":[],"mappings":"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","sourcesContent":["import type { RequestContext } from '@mastra/core/request-context'\nimport { createTool, type Tool } from '@mastra/core/tools'\nimport { type AnyProcedure, call } from '@orpc/server'\nimport {\n\tisObjectJsonSchema,\n\tpassThroughOutputSchema,\n\trawInputSchema,\n\ttype ToolSchema\n} from './json-schema.js'\nimport { readFnMeta, toToolName } from './meta.js'\nimport { type ToolErrorOptions, toolErrorText } from './tool-errors.js'\nimport type { ProcedureInput, ProcedureOutput } from './types.js'\n\nexport { toToolName, type ToolErrorOptions }\n\nexport type CreateMastraToolOptions = ToolErrorOptions & {\n\t/** Override the tool id (defaults to the sanitized `fn` name). */\n\tid?: string\n\t/** Override the tool description (defaults to the route description, else its summary). */\n\tdescription?: string\n\t/** Gate execution behind the AI SDK tool-approval flow. */\n\trequireApproval?: boolean\n\t/**\n\t * Accept a procedure without an input schema (the tool then takes an empty\n\t * object). For no-input queries like `teams.list`.\n\t */\n\tallowMissingInputSchema?: boolean\n\tonExecuteFinish?: (event: {\n\t\ttoolId: string\n\t\tdurationMs: number\n\t\toutcome: 'success' | 'error'\n\t}) => void\n\t/** Key holding the oRPC context in Mastra's `RequestContext`. Default 'orpcContext'. */\n\tcontextKey?: string\n\t/**\n\t * Reads the oRPC context from the run's request context, in place of\n\t * `contextKey`. Mastra writes plain request-context values into run\n\t * snapshots and spans, so an app whose context holds a session keeps it\n\t * in a slot that serializes to nothing and reads it here.\n\t */\n\tcontext?: (requestContext: RequestContext) => unknown\n}\n\n/** What the tool takes: the procedure's raw input, or `{}` without a schema. */\nexport type MastraToolInput<TProc> =\n\tunknown extends ProcedureInput<TProc>\n\t\t? Record<string, never>\n\t\t: ProcedureInput<TProc>\n\n/** A Mastra tool typed by the procedure's raw input and parsed output. */\nexport type MastraTool<TProc extends AnyProcedure> = Tool<\n\tMastraToolInput<TProc>,\n\tProcedureOutput<TProc>\n>\n\n/** LLM tool parameters must be an object; other inputs are a type error. */\ntype ObjectInputCheck<TProc> =\n\tMastraToolInput<TProc> extends Record<string, unknown>\n\t\t? unknown\n\t\t: { 'orpc-fn: an agent tool needs an object input': ProcedureInput<TProc> }\n\n/**\n * Creates a Mastra tool from a procedure made with `fn()`. Execution calls\n * the procedure with the oRPC context stored in the Mastra request context\n * (`requestContext.set('orpcContext', context)`), so auth and guards apply.\n *\n * Mastra checks the input against the procedure's schema (with dates coerced\n * from strings) but passes the raw input on for the procedure to parse;\n * the output is the procedure's parsed result, passed through unchanged.\n *\n * A failed call throws an `Error` whose message is what the model reads:\n * `CODE: message` with validation paths for an expected error, a fixed\n * sentence for anything else (reported to `onError`).\n */\nexport function createMastraTool<TProc extends AnyProcedure>(\n\tprocedure: TProc & ObjectInputCheck<TProc>,\n\toptions: CreateMastraToolOptions = {}\n): MastraTool<TProc> {\n\tconst meta = readFnMeta(procedure)\n\tif (meta.stream)\n\t\tthrow new TypeError('orpc-fn: streaming procedures cannot be Mastra tools')\n\tconst name = options.id ?? meta.name\n\tif (!name) {\n\t\tthrow new Error(\n\t\t\t'Procedure must be created with fn() to use createMastraTool'\n\t\t)\n\t}\n\tlet inputSchema: ToolSchema | undefined\n\tif (meta.inputSchema) {\n\t\tinputSchema = rawInputSchema(meta.inputSchema)\n\t\tif (!isObjectJsonSchema(inputSchema['~standard'].jsonSchema.input())) {\n\t\t\tthrow new Error(`${name} must take an object input to be an agent tool`)\n\t\t}\n\t} else if (!options.allowMissingInputSchema) {\n\t\tthrow new Error(\n\t\t\t`${name} must declare an input schema to be exposed as an agent tool`\n\t\t)\n\t}\n\n\tconst id = options.id ?? toToolName(name)\n\tconst contextKey = options.contextKey ?? 'orpcContext'\n\n\tconst tool = createTool({\n\t\tid,\n\t\tdescription: options.description ?? meta.description,\n\t\trequireApproval: options.requireApproval === true,\n\t\tinputSchema:\n\t\t\tinputSchema ??\n\t\t\t({\n\t\t\t\t'~standard': {\n\t\t\t\t\tversion: 1,\n\t\t\t\t\tvendor: 'orpc-fn',\n\t\t\t\t\tvalidate: () => ({ value: {} }),\n\t\t\t\t\tjsonSchema: {\n\t\t\t\t\t\tinput: () => ({ type: 'object', properties: {} }),\n\t\t\t\t\t\toutput: () => ({ type: 'object', properties: {} })\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} satisfies ToolSchema),\n\t\t...(meta.outputSchema\n\t\t\t? { outputSchema: passThroughOutputSchema(meta.outputSchema) }\n\t\t\t: {}),\n\t\texecute: async (input, ctx) => {\n\t\t\tconst requestContext = ctx?.requestContext as RequestContext | undefined\n\t\t\tconst context =\n\t\t\t\trequestContext &&\n\t\t\t\t(options.context\n\t\t\t\t\t? options.context(requestContext)\n\t\t\t\t\t: requestContext.get(contextKey))\n\t\t\tif (!context) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\toptions.context\n\t\t\t\t\t\t? 'createMastraTool execution found no oRPC context through its context option'\n\t\t\t\t\t\t: `createMastraTool execution requires ${contextKey} in the Mastra request context`\n\t\t\t\t)\n\t\t\t}\n\t\t\tconst startedAt = performance.now()\n\t\t\tconst finish = (outcome: 'success' | 'error') => {\n\t\t\t\ttry {\n\t\t\t\t\toptions.onExecuteFinish?.({\n\t\t\t\t\t\ttoolId: id,\n\t\t\t\t\t\tdurationMs: performance.now() - startedAt,\n\t\t\t\t\t\toutcome\n\t\t\t\t\t})\n\t\t\t\t} catch {\n\t\t\t\t\t/* A telemetry hook cannot change tool execution. */\n\t\t\t\t}\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tconst result = await call(\n\t\t\t\t\tprocedure,\n\t\t\t\t\tmeta.inputSchema ? input : undefined,\n\t\t\t\t\tctx?.abortSignal ? { context, signal: ctx.abortSignal } : { context }\n\t\t\t\t)\n\t\t\t\tfinish('success')\n\t\t\t\treturn result\n\t\t\t} catch (error) {\n\t\t\t\tfinish('error')\n\t\t\t\tif (ctx?.abortSignal?.aborted) throw error\n\t\t\t\tthrow new Error(toolErrorText(error, id, options))\n\t\t\t}\n\t\t}\n\t})\n\n\t// The runtime schemas are untyped `ToolSchema`s; `MastraTool` is the typed surface.\n\treturn tool as unknown as MastraTool<TProc>\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { type AnyRouter, type InferRouterInitialContext } from '@orpc/server';
2
2
  import { listTools, type McpTool, type ProcedureFilter } from '../mcp.js';
3
+ import { type ToolErrorOptions } from '../tool-errors.js';
3
4
  /** Text-only tool result: the shape both MCP SDK majors accept. */
4
5
  export type TextToolResult = {
5
6
  content: {
@@ -8,34 +9,50 @@ export type TextToolResult = {
8
9
  }[];
9
10
  isError?: boolean;
10
11
  };
11
- export type RegisterMcpToolsOptions<R extends AnyRouter, TResult> = {
12
+ /** Which procedures become tools, and their names and hints. */
13
+ export type McpToolSelection = {
12
14
  /** Tool exposure must be an explicit application choice. */
13
15
  filter: ProcedureFilter;
16
+ readOnly?: ProcedureFilter;
17
+ name?: NonNullable<Parameters<typeof listTools>[1]>['name'];
18
+ };
19
+ /** How a registered tool is called and how its outcome reads. */
20
+ export type McpCallOptions<R extends AnyRouter, TResult> = ToolErrorOptions & {
14
21
  context: (request: {
15
22
  name: string;
16
23
  signal: AbortSignal;
17
24
  }) => InferRouterInitialContext<R> | Promise<InferRouterInitialContext<R>>;
18
- readOnly?: ProcedureFilter;
19
- name?: NonNullable<Parameters<typeof listTools>[1]>['name'];
20
25
  formatResult?: (value: unknown, tool: McpTool) => TResult | Promise<TResult>;
21
26
  };
27
+ export type RegisterMcpToolsOptions<R extends AnyRouter, TResult> = McpToolSelection & McpCallOptions<R, TResult>;
28
+ type ToolDescription = Omit<McpTool['config'], 'inputSchema'> & {
29
+ name: string;
30
+ inputSchema: Record<string, unknown> & {
31
+ type: 'object';
32
+ };
33
+ };
34
+ /** Tools listed and converted once, then registered per server or request. */
35
+ export type PreparedMcpTools<R extends AnyRouter> = {
36
+ readonly router: R;
37
+ readonly names: readonly string[];
38
+ readonly tools: readonly McpTool[];
39
+ /** @internal The `tools/list` answer. */
40
+ readonly descriptions: readonly ToolDescription[];
41
+ };
42
+ /**
43
+ * List and convert a router's tools once, for an app that builds an MCP
44
+ * server per request: `registerPreparedMcpTools(server, prepared, { context })`.
45
+ */
46
+ export declare function prepareMcpTools<R extends AnyRouter>(router: R, options: McpToolSelection): PreparedMcpTools<R>;
22
47
  /** Default protocol result: JSON/text. Provide formatResult for other MCP content. */
23
48
  export declare function formatMcpResult(value: unknown): TextToolResult;
24
49
  /** @internal SDK-independent `tools/list` and `tools/call` behind both adapters. */
25
- export declare function createToolHandlers<R extends AnyRouter, TResult>(router: R, options: RegisterMcpToolsOptions<R, TResult>): {
50
+ export declare function createToolHandlers<R extends AnyRouter, TResult>(prepared: PreparedMcpTools<R>, options: McpCallOptions<R, TResult>): {
26
51
  names: readonly string[];
27
52
  list: () => {
28
- tools: {
29
- description?: string;
30
- annotations: {
31
- readOnlyHint: boolean;
32
- };
33
- name: string;
34
- inputSchema: {
35
- type: 'object';
36
- };
37
- }[];
53
+ tools: ToolDescription[];
38
54
  };
39
55
  call: (name: string, args: Record<string, unknown> | undefined, signal: AbortSignal) => Promise<TResult | TextToolResult>;
40
56
  };
57
+ export {};
41
58
  //# sourceMappingURL=core.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/mcp/core.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,SAAS,EACd,KAAK,yBAAyB,EAE9B,MAAM,cAAc,CAAA;AAErB,OAAO,EAAE,SAAS,EAAE,KAAK,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AAEzE,mEAAmE;AACnE,MAAM,MAAM,cAAc,GAAG;IAC5B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACzC,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,SAAS,EAAE,OAAO,IAAI;IACnE,4DAA4D;IAC5D,MAAM,EAAE,eAAe,CAAA;IACvB,OAAO,EAAE,CAAC,OAAO,EAAE;QAClB,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,WAAW,CAAA;KACnB,KAAK,yBAAyB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1E,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B,IAAI,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;IAC3D,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5E,CAAA;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAQ9D;AAOD,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,SAAS,EAAE,OAAO,EAC9D,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,uBAAuB,CAAC,CAAC,EAAE,OAAO,CAAC;IAwB3C,KAAK,EAAuB,SAAS,MAAM,EAAE;IAC7C,IAAI;QAAW,KAAK;;;;;;;sBALkB,QAAQ;;;;IAM9C,IAAI,SACG,MAAM,QACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,UACjC,WAAW,KACjB,OAAO,CAAC,OAAO,GAAG,cAAc,CAAC;EA0BrC"}
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/mcp/core.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,SAAS,EACd,KAAK,yBAAyB,EAE9B,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,SAAS,EAAE,KAAK,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,WAAW,CAAA;AACzE,OAAO,EAAE,KAAK,gBAAgB,EAAiB,MAAM,mBAAmB,CAAA;AAExE,mEAAmE;AACnE,MAAM,MAAM,cAAc,GAAG;IAC5B,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAA;IACzC,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED,gEAAgE;AAChE,MAAM,MAAM,gBAAgB,GAAG;IAC9B,4DAA4D;IAC5D,MAAM,EAAE,eAAe,CAAA;IACvB,QAAQ,CAAC,EAAE,eAAe,CAAA;IAC1B,IAAI,CAAC,EAAE,WAAW,CAAC,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;CAC3D,CAAA;AAED,iEAAiE;AACjE,MAAM,MAAM,cAAc,CAAC,CAAC,SAAS,SAAS,EAAE,OAAO,IAAI,gBAAgB,GAAG;IAC7E,OAAO,EAAE,CAAC,OAAO,EAAE;QAClB,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,WAAW,CAAA;KACnB,KAAK,yBAAyB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAC,CAAA;IAC1E,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CAC5E,CAAA;AAED,MAAM,MAAM,uBAAuB,CAClC,CAAC,SAAS,SAAS,EACnB,OAAO,IACJ,gBAAgB,GAAG,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC,CAAA;AAEjD,KAAK,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,GAAG;IAC/D,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAA;CACzD,CAAA;AAED,8EAA8E;AAC9E,MAAM,MAAM,gBAAgB,CAAC,CAAC,SAAS,SAAS,IAAI;IACnD,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAClB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,CAAA;IAClC,yCAAyC;IACzC,QAAQ,CAAC,YAAY,EAAE,SAAS,eAAe,EAAE,CAAA;CACjD,CAAA;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS,SAAS,EAClD,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,gBAAgB,GACvB,gBAAgB,CAAC,CAAC,CAAC,CA+BrB;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAQ9D;AAOD,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,SAAS,EAAE,OAAO,EAC9D,QAAQ,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC7B,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE,OAAO,CAAC;IAIlC,KAAK;IACL,IAAI;QAAW,KAAK;;IACpB,IAAI,SACG,MAAM,QACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,UACjC,WAAW,KACjB,OAAO,CAAC,OAAO,GAAG,cAAc,CAAC;EA2BrC"}
package/dist/mcp/core.js CHANGED
@@ -1,26 +1,21 @@
1
1
  import { call } from '@orpc/server';
2
+ import { formatIssue } from '../issues.js';
2
3
  import { isObjectJsonSchema } from '../json-schema.js';
3
4
  import { listTools } from '../mcp.js';
4
- /** Default protocol result: JSON/text. Provide formatResult for other MCP content. */
5
- export function formatMcpResult(value) {
6
- const text = typeof value === 'string'
7
- ? value
8
- : (JSON.stringify(value, (_key, item) => typeof item === 'bigint' ? item.toString() : item) ?? 'null');
9
- return { content: [{ type: 'text', text }] };
10
- }
11
- const errorResult = (text) => ({
12
- content: [{ type: 'text', text }],
13
- isError: true
14
- });
15
- /** @internal SDK-independent `tools/list` and `tools/call` behind both adapters. */
16
- export function createToolHandlers(router, options) {
17
- const definitions = listTools(router, {
5
+ import { toolErrorText } from '../tool-errors.js';
6
+ /**
7
+ * List and convert a router's tools once, for an app that builds an MCP
8
+ * server per request: `registerPreparedMcpTools(server, prepared, { context })`.
9
+ */
10
+ export function prepareMcpTools(router, options) {
11
+ if (typeof options.filter !== 'function')
12
+ throw new TypeError('orpc-fn: MCP tools need a filter; tool exposure is an explicit choice');
13
+ const tools = listTools(router, {
18
14
  filter: options.filter,
19
15
  ...(options.readOnly ? { readOnly: options.readOnly } : {}),
20
16
  ...(options.name ? { name: options.name } : {})
21
17
  });
22
- const tools = new Map(definitions.map((tool) => [tool.name, tool]));
23
- const descriptions = definitions.map((tool) => {
18
+ const descriptions = tools.map((tool) => {
24
19
  const inputSchema = tool.config.inputSchema?.['~standard'].jsonSchema.input() ?? { type: 'object', properties: {} };
25
20
  if (!isObjectJsonSchema(inputSchema))
26
21
  throw new TypeError(`orpc-fn: MCP tool ${tool.name} needs an object input`);
@@ -32,8 +27,29 @@ export function createToolHandlers(router, options) {
32
27
  };
33
28
  });
34
29
  return {
35
- names: [...tools.keys()],
36
- list: () => ({ tools: descriptions }),
30
+ router,
31
+ names: tools.map((tool) => tool.name),
32
+ tools,
33
+ descriptions
34
+ };
35
+ }
36
+ /** Default protocol result: JSON/text. Provide formatResult for other MCP content. */
37
+ export function formatMcpResult(value) {
38
+ const text = typeof value === 'string'
39
+ ? value
40
+ : (JSON.stringify(value, (_key, item) => typeof item === 'bigint' ? item.toString() : item) ?? 'null');
41
+ return { content: [{ type: 'text', text }] };
42
+ }
43
+ const errorResult = (text) => ({
44
+ content: [{ type: 'text', text }],
45
+ isError: true
46
+ });
47
+ /** @internal SDK-independent `tools/list` and `tools/call` behind both adapters. */
48
+ export function createToolHandlers(prepared, options) {
49
+ const tools = new Map(prepared.tools.map((tool) => [tool.name, tool]));
50
+ return {
51
+ names: prepared.names,
52
+ list: () => ({ tools: [...prepared.descriptions] }),
37
53
  call: async (name, args, signal) => {
38
54
  const tool = tools.get(name);
39
55
  if (!tool)
@@ -42,7 +58,10 @@ export function createToolHandlers(router, options) {
42
58
  signal.throwIfAborted();
43
59
  const validation = await tool.config.inputSchema?.['~standard'].validate(args ?? {});
44
60
  if (validation?.issues)
45
- return errorResult(validation.issues.map((issue) => issue.message).join('; '));
61
+ return errorResult([
62
+ 'BAD_REQUEST: Invalid tool input',
63
+ ...validation.issues.map(formatIssue)
64
+ ].join('\n'));
46
65
  const context = await options.context({ name: tool.name, signal });
47
66
  const result = await call(tool.procedure, validation?.value, {
48
67
  context,
@@ -53,7 +72,7 @@ export function createToolHandlers(router, options) {
53
72
  catch (error) {
54
73
  if (signal.aborted)
55
74
  throw error;
56
- return errorResult(error instanceof Error ? error.message : String(error));
75
+ return errorResult(toolErrorText(error, tool.name, options));
57
76
  }
58
77
  }
59
78
  };
@@ -1 +1 @@
1
- {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/mcp/core.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype AnyRouter,\n\ttype InferRouterInitialContext,\n\tcall\n} from '@orpc/server'\nimport { isObjectJsonSchema } from '../json-schema.js'\nimport { listTools, type McpTool, type ProcedureFilter } from '../mcp.js'\n\n/** Text-only tool result: the shape both MCP SDK majors accept. */\nexport type TextToolResult = {\n\tcontent: { type: 'text'; text: string }[]\n\tisError?: boolean\n}\n\nexport type RegisterMcpToolsOptions<R extends AnyRouter, TResult> = {\n\t/** Tool exposure must be an explicit application choice. */\n\tfilter: ProcedureFilter\n\tcontext: (request: {\n\t\tname: string\n\t\tsignal: AbortSignal\n\t}) => InferRouterInitialContext<R> | Promise<InferRouterInitialContext<R>>\n\treadOnly?: ProcedureFilter\n\tname?: NonNullable<Parameters<typeof listTools>[1]>['name']\n\tformatResult?: (value: unknown, tool: McpTool) => TResult | Promise<TResult>\n}\n\n/** Default protocol result: JSON/text. Provide formatResult for other MCP content. */\nexport function formatMcpResult(value: unknown): TextToolResult {\n\tconst text =\n\t\ttypeof value === 'string'\n\t\t\t? value\n\t\t\t: (JSON.stringify(value, (_key, item) =>\n\t\t\t\t\ttypeof item === 'bigint' ? item.toString() : item\n\t\t\t\t) ?? 'null')\n\treturn { content: [{ type: 'text', text }] }\n}\n\nconst errorResult = (text: string): TextToolResult => ({\n\tcontent: [{ type: 'text', text }],\n\tisError: true\n})\n\n/** @internal SDK-independent `tools/list` and `tools/call` behind both adapters. */\nexport function createToolHandlers<R extends AnyRouter, TResult>(\n\trouter: R,\n\toptions: RegisterMcpToolsOptions<R, TResult>\n) {\n\tconst definitions = listTools(router, {\n\t\tfilter: options.filter,\n\t\t...(options.readOnly ? { readOnly: options.readOnly } : {}),\n\t\t...(options.name ? { name: options.name } : {})\n\t})\n\tconst tools = new Map(definitions.map((tool) => [tool.name, tool]))\n\tconst descriptions = definitions.map((tool) => {\n\t\tconst inputSchema = tool.config.inputSchema?.[\n\t\t\t'~standard'\n\t\t].jsonSchema.input() ?? { type: 'object', properties: {} }\n\t\tif (!isObjectJsonSchema(inputSchema))\n\t\t\tthrow new TypeError(\n\t\t\t\t`orpc-fn: MCP tool ${tool.name} needs an object input`\n\t\t\t)\n\t\t// MCP requires a top-level object; object unions retain their constraints.\n\t\treturn {\n\t\t\tname: tool.name,\n\t\t\t...tool.config,\n\t\t\tinputSchema: { ...inputSchema, type: 'object' as const }\n\t\t}\n\t})\n\treturn {\n\t\tnames: [...tools.keys()] as readonly string[],\n\t\tlist: () => ({ tools: descriptions }),\n\t\tcall: async (\n\t\t\tname: string,\n\t\t\targs: Record<string, unknown> | undefined,\n\t\t\tsignal: AbortSignal\n\t\t): Promise<TResult | TextToolResult> => {\n\t\t\tconst tool = tools.get(name)\n\t\t\tif (!tool) return errorResult(`Unknown tool: ${name}`)\n\t\t\ttry {\n\t\t\t\tsignal.throwIfAborted()\n\t\t\t\tconst validation = await tool.config.inputSchema?.[\n\t\t\t\t\t'~standard'\n\t\t\t\t].validate(args ?? {})\n\t\t\t\tif (validation?.issues)\n\t\t\t\t\treturn errorResult(\n\t\t\t\t\t\tvalidation.issues.map((issue) => issue.message).join('; ')\n\t\t\t\t\t)\n\t\t\t\tconst context = await options.context({ name: tool.name, signal })\n\t\t\t\tconst result = await call(tool.procedure, validation?.value, {\n\t\t\t\t\tcontext,\n\t\t\t\t\tsignal\n\t\t\t\t})\n\t\t\t\treturn await (options.formatResult ?? formatMcpResult)(result, tool)\n\t\t\t} catch (error) {\n\t\t\t\tif (signal.aborted) throw error\n\t\t\t\treturn errorResult(\n\t\t\t\t\terror instanceof Error ? error.message : String(error)\n\t\t\t\t)\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../../src/mcp/core.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype AnyRouter,\n\ttype InferRouterInitialContext,\n\tcall\n} from '@orpc/server'\nimport { formatIssue } from '../issues.js'\nimport { isObjectJsonSchema } from '../json-schema.js'\nimport { listTools, type McpTool, type ProcedureFilter } from '../mcp.js'\nimport { type ToolErrorOptions, toolErrorText } from '../tool-errors.js'\n\n/** Text-only tool result: the shape both MCP SDK majors accept. */\nexport type TextToolResult = {\n\tcontent: { type: 'text'; text: string }[]\n\tisError?: boolean\n}\n\n/** Which procedures become tools, and their names and hints. */\nexport type McpToolSelection = {\n\t/** Tool exposure must be an explicit application choice. */\n\tfilter: ProcedureFilter\n\treadOnly?: ProcedureFilter\n\tname?: NonNullable<Parameters<typeof listTools>[1]>['name']\n}\n\n/** How a registered tool is called and how its outcome reads. */\nexport type McpCallOptions<R extends AnyRouter, TResult> = ToolErrorOptions & {\n\tcontext: (request: {\n\t\tname: string\n\t\tsignal: AbortSignal\n\t}) => InferRouterInitialContext<R> | Promise<InferRouterInitialContext<R>>\n\tformatResult?: (value: unknown, tool: McpTool) => TResult | Promise<TResult>\n}\n\nexport type RegisterMcpToolsOptions<\n\tR extends AnyRouter,\n\tTResult\n> = McpToolSelection & McpCallOptions<R, TResult>\n\ntype ToolDescription = Omit<McpTool['config'], 'inputSchema'> & {\n\tname: string\n\tinputSchema: Record<string, unknown> & { type: 'object' }\n}\n\n/** Tools listed and converted once, then registered per server or request. */\nexport type PreparedMcpTools<R extends AnyRouter> = {\n\treadonly router: R\n\treadonly names: readonly string[]\n\treadonly tools: readonly McpTool[]\n\t/** @internal The `tools/list` answer. */\n\treadonly descriptions: readonly ToolDescription[]\n}\n\n/**\n * List and convert a router's tools once, for an app that builds an MCP\n * server per request: `registerPreparedMcpTools(server, prepared, { context })`.\n */\nexport function prepareMcpTools<R extends AnyRouter>(\n\trouter: R,\n\toptions: McpToolSelection\n): PreparedMcpTools<R> {\n\tif (typeof options.filter !== 'function')\n\t\tthrow new TypeError(\n\t\t\t'orpc-fn: MCP tools need a filter; tool exposure is an explicit choice'\n\t\t)\n\tconst tools = listTools(router, {\n\t\tfilter: options.filter,\n\t\t...(options.readOnly ? { readOnly: options.readOnly } : {}),\n\t\t...(options.name ? { name: options.name } : {})\n\t})\n\tconst descriptions = tools.map((tool) => {\n\t\tconst inputSchema = tool.config.inputSchema?.[\n\t\t\t'~standard'\n\t\t].jsonSchema.input() ?? { type: 'object', properties: {} }\n\t\tif (!isObjectJsonSchema(inputSchema))\n\t\t\tthrow new TypeError(\n\t\t\t\t`orpc-fn: MCP tool ${tool.name} needs an object input`\n\t\t\t)\n\t\t// MCP requires a top-level object; object unions retain their constraints.\n\t\treturn {\n\t\t\tname: tool.name,\n\t\t\t...tool.config,\n\t\t\tinputSchema: { ...inputSchema, type: 'object' as const }\n\t\t}\n\t})\n\treturn {\n\t\trouter,\n\t\tnames: tools.map((tool) => tool.name),\n\t\ttools,\n\t\tdescriptions\n\t}\n}\n\n/** Default protocol result: JSON/text. Provide formatResult for other MCP content. */\nexport function formatMcpResult(value: unknown): TextToolResult {\n\tconst text =\n\t\ttypeof value === 'string'\n\t\t\t? value\n\t\t\t: (JSON.stringify(value, (_key, item) =>\n\t\t\t\t\ttypeof item === 'bigint' ? item.toString() : item\n\t\t\t\t) ?? 'null')\n\treturn { content: [{ type: 'text', text }] }\n}\n\nconst errorResult = (text: string): TextToolResult => ({\n\tcontent: [{ type: 'text', text }],\n\tisError: true\n})\n\n/** @internal SDK-independent `tools/list` and `tools/call` behind both adapters. */\nexport function createToolHandlers<R extends AnyRouter, TResult>(\n\tprepared: PreparedMcpTools<R>,\n\toptions: McpCallOptions<R, TResult>\n) {\n\tconst tools = new Map(prepared.tools.map((tool) => [tool.name, tool]))\n\treturn {\n\t\tnames: prepared.names,\n\t\tlist: () => ({ tools: [...prepared.descriptions] }),\n\t\tcall: async (\n\t\t\tname: string,\n\t\t\targs: Record<string, unknown> | undefined,\n\t\t\tsignal: AbortSignal\n\t\t): Promise<TResult | TextToolResult> => {\n\t\t\tconst tool = tools.get(name)\n\t\t\tif (!tool) return errorResult(`Unknown tool: ${name}`)\n\t\t\ttry {\n\t\t\t\tsignal.throwIfAborted()\n\t\t\t\tconst validation = await tool.config.inputSchema?.[\n\t\t\t\t\t'~standard'\n\t\t\t\t].validate(args ?? {})\n\t\t\t\tif (validation?.issues)\n\t\t\t\t\treturn errorResult(\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\t'BAD_REQUEST: Invalid tool input',\n\t\t\t\t\t\t\t...validation.issues.map(formatIssue)\n\t\t\t\t\t\t].join('\\n')\n\t\t\t\t\t)\n\t\t\t\tconst context = await options.context({ name: tool.name, signal })\n\t\t\t\tconst result = await call(tool.procedure, validation?.value, {\n\t\t\t\t\tcontext,\n\t\t\t\t\tsignal\n\t\t\t\t})\n\t\t\t\treturn await (options.formatResult ?? formatMcpResult)(result, tool)\n\t\t\t} catch (error) {\n\t\t\t\tif (signal.aborted) throw error\n\t\t\t\treturn errorResult(toolErrorText(error, tool.name, options))\n\t\t\t}\n\t\t}\n\t}\n}\n"]}
package/dist/mcp/sdk.d.ts CHANGED
@@ -1,12 +1,16 @@
1
1
  import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { type CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
3
  import type { AnyRouter } from '@orpc/server';
4
- import { formatMcpResult, type RegisterMcpToolsOptions as CoreOptions } from './core.js';
5
- export { formatMcpResult };
4
+ import { formatMcpResult, type McpCallOptions, type PreparedMcpTools, prepareMcpTools, type RegisterMcpToolsOptions as CoreOptions } from './core.js';
5
+ export { formatMcpResult, prepareMcpTools, type PreparedMcpTools };
6
6
  export type RegisterMcpToolsOptions<R extends AnyRouter> = CoreOptions<R, CallToolResult>;
7
+ /** Options for registering tools from {@link prepareMcpTools}. */
8
+ export type RegisterPreparedMcpToolsOptions<R extends AnyRouter> = McpCallOptions<R, CallToolResult>;
7
9
  /**
8
10
  * Register on the SDK v1 low-level Server, or on McpServer.server. For SDK v2
9
11
  * (`@modelcontextprotocol/server`) use `orpc-fn/mcp/server`.
10
12
  */
11
13
  export declare function registerMcpTools<R extends AnyRouter>(server: Pick<Server, 'setRequestHandler'>, router: R, options: RegisterMcpToolsOptions<R>): readonly string[];
14
+ /** Register tools listed once with {@link prepareMcpTools}, e.g. on a server built per request. */
15
+ export declare function registerPreparedMcpTools<R extends AnyRouter>(server: Pick<Server, 'setRequestHandler'>, tools: PreparedMcpTools<R>, options: RegisterPreparedMcpToolsOptions<R>): readonly string[];
12
16
  //# sourceMappingURL=sdk.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/mcp/sdk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AACvE,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAEN,eAAe,EACf,KAAK,uBAAuB,IAAI,WAAW,EAC3C,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,eAAe,EAAE,CAAA;AAE1B,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,SAAS,IAAI,WAAW,CACrE,CAAC,EACD,cAAc,CACd,CAAA;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,SAAS,EACnD,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACzC,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GACjC,SAAS,MAAM,EAAE,CAOnB"}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/mcp/sdk.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAA;AACvE,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,oCAAoC,CAAA;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAEN,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,eAAe,EACf,KAAK,uBAAuB,IAAI,WAAW,EAC3C,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,CAAA;AAElE,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,SAAS,IAAI,WAAW,CACrE,CAAC,EACD,cAAc,CACd,CAAA;AAED,kEAAkE;AAClE,MAAM,MAAM,+BAA+B,CAAC,CAAC,SAAS,SAAS,IAC9D,cAAc,CAAC,CAAC,EAAE,cAAc,CAAC,CAAA;AAElC;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,SAAS,EACnD,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACzC,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GACjC,SAAS,MAAM,EAAE,CAKnB;AAED,mGAAmG;AACnG,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,SAAS,EAC3D,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACzC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC1B,OAAO,EAAE,+BAA+B,CAAC,CAAC,CAAC,GACzC,SAAS,MAAM,EAAE,CAEnB"}
package/dist/mcp/sdk.js CHANGED
@@ -1,12 +1,18 @@
1
1
  import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
2
- import { createToolHandlers, formatMcpResult } from './core.js';
3
- export { formatMcpResult };
2
+ import { createToolHandlers, formatMcpResult, prepareMcpTools } from './core.js';
3
+ export { formatMcpResult, prepareMcpTools };
4
4
  /**
5
5
  * Register on the SDK v1 low-level Server, or on McpServer.server. For SDK v2
6
6
  * (`@modelcontextprotocol/server`) use `orpc-fn/mcp/server`.
7
7
  */
8
8
  export function registerMcpTools(server, router, options) {
9
- const handlers = createToolHandlers(router, options);
9
+ return install(server, createToolHandlers(prepareMcpTools(router, options), options));
10
+ }
11
+ /** Register tools listed once with {@link prepareMcpTools}, e.g. on a server built per request. */
12
+ export function registerPreparedMcpTools(server, tools, options) {
13
+ return install(server, createToolHandlers(tools, options));
14
+ }
15
+ function install(server, handlers) {
10
16
  server.setRequestHandler(ListToolsRequestSchema, handlers.list);
11
17
  server.setRequestHandler(CallToolRequestSchema, (request, extra) => handlers.call(request.params.name, request.params.arguments, extra.signal));
12
18
  return handlers.names;
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.js","sourceRoot":"","sources":["../../src/mcp/sdk.ts"],"names":[],"mappings":"AACA,OAAO,EACN,qBAAqB,EACrB,sBAAsB,EAEtB,MAAM,oCAAoC,CAAA;AAE3C,OAAO,EACN,kBAAkB,EAClB,eAAe,EAEf,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,eAAe,EAAE,CAAA;AAO1B;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC/B,MAAyC,EACzC,MAAS,EACT,OAAmC;IAEnC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACpD,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC/D,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAClE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAC1E,CAAA;IACD,OAAO,QAAQ,CAAC,KAAK,CAAA;AACtB,CAAC","sourcesContent":["import type { Server } from '@modelcontextprotocol/sdk/server/index.js'\nimport {\n\tCallToolRequestSchema,\n\tListToolsRequestSchema,\n\ttype CallToolResult\n} from '@modelcontextprotocol/sdk/types.js'\nimport type { AnyRouter } from '@orpc/server'\nimport {\n\tcreateToolHandlers,\n\tformatMcpResult,\n\ttype RegisterMcpToolsOptions as CoreOptions\n} from './core.js'\n\nexport { formatMcpResult }\n\nexport type RegisterMcpToolsOptions<R extends AnyRouter> = CoreOptions<\n\tR,\n\tCallToolResult\n>\n\n/**\n * Register on the SDK v1 low-level Server, or on McpServer.server. For SDK v2\n * (`@modelcontextprotocol/server`) use `orpc-fn/mcp/server`.\n */\nexport function registerMcpTools<R extends AnyRouter>(\n\tserver: Pick<Server, 'setRequestHandler'>,\n\trouter: R,\n\toptions: RegisterMcpToolsOptions<R>\n): readonly string[] {\n\tconst handlers = createToolHandlers(router, options)\n\tserver.setRequestHandler(ListToolsRequestSchema, handlers.list)\n\tserver.setRequestHandler(CallToolRequestSchema, (request, extra) =>\n\t\thandlers.call(request.params.name, request.params.arguments, extra.signal)\n\t)\n\treturn handlers.names\n}\n"]}
1
+ {"version":3,"file":"sdk.js","sourceRoot":"","sources":["../../src/mcp/sdk.ts"],"names":[],"mappings":"AACA,OAAO,EACN,qBAAqB,EACrB,sBAAsB,EAEtB,MAAM,oCAAoC,CAAA;AAE3C,OAAO,EACN,kBAAkB,EAClB,eAAe,EAGf,eAAe,EAEf,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAyB,CAAA;AAWlE;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC/B,MAAyC,EACzC,MAAS,EACT,OAAmC;IAEnC,OAAO,OAAO,CACb,MAAM,EACN,kBAAkB,CAAC,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,CAC7D,CAAA;AACF,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,wBAAwB,CACvC,MAAyC,EACzC,KAA0B,EAC1B,OAA2C;IAE3C,OAAO,OAAO,CAAC,MAAM,EAAE,kBAAkB,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAA;AAC3D,CAAC;AAED,SAAS,OAAO,CACf,MAAyC,EACzC,QAA0E;IAE1E,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC/D,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAClE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,MAAM,CAAC,CAC1E,CAAA;IACD,OAAO,QAAQ,CAAC,KAAK,CAAA;AACtB,CAAC","sourcesContent":["import type { Server } from '@modelcontextprotocol/sdk/server/index.js'\nimport {\n\tCallToolRequestSchema,\n\tListToolsRequestSchema,\n\ttype CallToolResult\n} from '@modelcontextprotocol/sdk/types.js'\nimport type { AnyRouter } from '@orpc/server'\nimport {\n\tcreateToolHandlers,\n\tformatMcpResult,\n\ttype McpCallOptions,\n\ttype PreparedMcpTools,\n\tprepareMcpTools,\n\ttype RegisterMcpToolsOptions as CoreOptions\n} from './core.js'\n\nexport { formatMcpResult, prepareMcpTools, type PreparedMcpTools }\n\nexport type RegisterMcpToolsOptions<R extends AnyRouter> = CoreOptions<\n\tR,\n\tCallToolResult\n>\n\n/** Options for registering tools from {@link prepareMcpTools}. */\nexport type RegisterPreparedMcpToolsOptions<R extends AnyRouter> =\n\tMcpCallOptions<R, CallToolResult>\n\n/**\n * Register on the SDK v1 low-level Server, or on McpServer.server. For SDK v2\n * (`@modelcontextprotocol/server`) use `orpc-fn/mcp/server`.\n */\nexport function registerMcpTools<R extends AnyRouter>(\n\tserver: Pick<Server, 'setRequestHandler'>,\n\trouter: R,\n\toptions: RegisterMcpToolsOptions<R>\n): readonly string[] {\n\treturn install(\n\t\tserver,\n\t\tcreateToolHandlers(prepareMcpTools(router, options), options)\n\t)\n}\n\n/** Register tools listed once with {@link prepareMcpTools}, e.g. on a server built per request. */\nexport function registerPreparedMcpTools<R extends AnyRouter>(\n\tserver: Pick<Server, 'setRequestHandler'>,\n\ttools: PreparedMcpTools<R>,\n\toptions: RegisterPreparedMcpToolsOptions<R>\n): readonly string[] {\n\treturn install(server, createToolHandlers(tools, options))\n}\n\nfunction install(\n\tserver: Pick<Server, 'setRequestHandler'>,\n\thandlers: ReturnType<typeof createToolHandlers<AnyRouter, CallToolResult>>\n): readonly string[] {\n\tserver.setRequestHandler(ListToolsRequestSchema, handlers.list)\n\tserver.setRequestHandler(CallToolRequestSchema, (request, extra) =>\n\t\thandlers.call(request.params.name, request.params.arguments, extra.signal)\n\t)\n\treturn handlers.names\n}\n"]}
@@ -1,11 +1,15 @@
1
1
  import type { CallToolResult, Server } from '@modelcontextprotocol/server';
2
2
  import type { AnyRouter } from '@orpc/server';
3
- import { formatMcpResult, type RegisterMcpToolsOptions as CoreOptions } from './core.js';
4
- export { formatMcpResult };
3
+ import { formatMcpResult, type McpCallOptions, type PreparedMcpTools, prepareMcpTools, type RegisterMcpToolsOptions as CoreOptions } from './core.js';
4
+ export { formatMcpResult, prepareMcpTools, type PreparedMcpTools };
5
5
  export type RegisterMcpToolsOptions<R extends AnyRouter> = CoreOptions<R, CallToolResult>;
6
+ /** Options for registering tools from {@link prepareMcpTools}. */
7
+ export type RegisterPreparedMcpToolsOptions<R extends AnyRouter> = McpCallOptions<R, CallToolResult>;
6
8
  /**
7
9
  * Register on the SDK v2 (`@modelcontextprotocol/server`) low-level Server, or
8
10
  * on McpServer.server. Declare `capabilities: { tools: {} }` on the Server.
9
11
  */
10
12
  export declare function registerMcpTools<R extends AnyRouter>(server: Pick<Server, 'setRequestHandler'>, router: R, options: RegisterMcpToolsOptions<R>): readonly string[];
13
+ /** Register tools listed once with {@link prepareMcpTools}, e.g. on a server built per request. */
14
+ export declare function registerPreparedMcpTools<R extends AnyRouter>(server: Pick<Server, 'setRequestHandler'>, tools: PreparedMcpTools<R>, options: RegisterPreparedMcpToolsOptions<R>): readonly string[];
11
15
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAEN,eAAe,EACf,KAAK,uBAAuB,IAAI,WAAW,EAC3C,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,eAAe,EAAE,CAAA;AAE1B,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,SAAS,IAAI,WAAW,CACrE,CAAC,EACD,cAAc,CACd,CAAA;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,SAAS,EACnD,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACzC,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GACjC,SAAS,MAAM,EAAE,CAWnB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,8BAA8B,CAAA;AAC1E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAEN,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,gBAAgB,EACrB,eAAe,EACf,KAAK,uBAAuB,IAAI,WAAW,EAC3C,MAAM,WAAW,CAAA;AAElB,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,KAAK,gBAAgB,EAAE,CAAA;AAElE,MAAM,MAAM,uBAAuB,CAAC,CAAC,SAAS,SAAS,IAAI,WAAW,CACrE,CAAC,EACD,cAAc,CACd,CAAA;AAED,kEAAkE;AAClE,MAAM,MAAM,+BAA+B,CAAC,CAAC,SAAS,SAAS,IAC9D,cAAc,CAAC,CAAC,EAAE,cAAc,CAAC,CAAA;AAElC;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,SAAS,EACnD,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACzC,MAAM,EAAE,CAAC,EACT,OAAO,EAAE,uBAAuB,CAAC,CAAC,CAAC,GACjC,SAAS,MAAM,EAAE,CAKnB;AAED,mGAAmG;AACnG,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,SAAS,EAC3D,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACzC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC1B,OAAO,EAAE,+BAA+B,CAAC,CAAC,CAAC,GACzC,SAAS,MAAM,EAAE,CAEnB"}
@@ -1,11 +1,17 @@
1
- import { createToolHandlers, formatMcpResult } from './core.js';
2
- export { formatMcpResult };
1
+ import { createToolHandlers, formatMcpResult, prepareMcpTools } from './core.js';
2
+ export { formatMcpResult, prepareMcpTools };
3
3
  /**
4
4
  * Register on the SDK v2 (`@modelcontextprotocol/server`) low-level Server, or
5
5
  * on McpServer.server. Declare `capabilities: { tools: {} }` on the Server.
6
6
  */
7
7
  export function registerMcpTools(server, router, options) {
8
- const handlers = createToolHandlers(router, options);
8
+ return install(server, createToolHandlers(prepareMcpTools(router, options), options));
9
+ }
10
+ /** Register tools listed once with {@link prepareMcpTools}, e.g. on a server built per request. */
11
+ export function registerPreparedMcpTools(server, tools, options) {
12
+ return install(server, createToolHandlers(tools, options));
13
+ }
14
+ function install(server, handlers) {
9
15
  server.setRequestHandler('tools/list', handlers.list);
10
16
  server.setRequestHandler('tools/call', (request, ctx) => handlers.call(request.params.name, request.params.arguments, ctx.mcpReq.signal));
11
17
  return handlers.names;