@bevel-software/platform-core-backend 0.7.5 → 0.8.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 (230) hide show
  1. package/LICENSE +202 -202
  2. package/THIRD-PARTY-NOTICES.md +428 -454
  3. package/dist/core/core-ports.d.ts +1 -1
  4. package/dist/core/create-core-server.d.ts.map +1 -1
  5. package/dist/core/create-core-server.js +51 -7
  6. package/dist/core/create-core-server.js.map +1 -1
  7. package/dist/core/create-core-services.d.ts +5 -3
  8. package/dist/core/create-core-services.d.ts.map +1 -1
  9. package/dist/core/create-core-services.js +30 -18
  10. package/dist/core/create-core-services.js.map +1 -1
  11. package/dist/modules/access/access-control.interface.d.ts +8 -7
  12. package/dist/modules/access/access-control.interface.d.ts.map +1 -1
  13. package/dist/modules/access/access-control.service.d.ts +1 -1
  14. package/dist/modules/access/access-control.service.d.ts.map +1 -1
  15. package/dist/modules/access/access-control.service.js +6 -6
  16. package/dist/modules/access/access-control.service.js.map +1 -1
  17. package/dist/modules/access/access-declarations.d.ts +5 -5
  18. package/dist/modules/access/access-declarations.js +3 -3
  19. package/dist/modules/access/access-mutation.service.d.ts +3 -3
  20. package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
  21. package/dist/modules/access/access-mutation.service.js +3 -3
  22. package/dist/modules/access/access-mutation.service.js.map +1 -1
  23. package/dist/modules/access/access-splice.js +4 -4
  24. package/dist/modules/access/access-splice.js.map +1 -1
  25. package/dist/modules/access/access.routes.js +19 -19
  26. package/dist/modules/access/access.routes.js.map +1 -1
  27. package/dist/modules/access/creator-access.d.ts +2 -2
  28. package/dist/modules/access/creator-access.js +5 -5
  29. package/dist/modules/access/creator-access.js.map +1 -1
  30. package/dist/modules/access/roles-admin.service.d.ts +18 -4
  31. package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
  32. package/dist/modules/access/roles-admin.service.js +14 -4
  33. package/dist/modules/access/roles-admin.service.js.map +1 -1
  34. package/dist/modules/code-mode/code-mode-names.d.ts +5 -13
  35. package/dist/modules/code-mode/code-mode-names.d.ts.map +1 -1
  36. package/dist/modules/code-mode/code-mode-names.js +5 -27
  37. package/dist/modules/code-mode/code-mode-names.js.map +1 -1
  38. package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
  39. package/dist/modules/code-mode/code-mode.tool.js +29 -7
  40. package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
  41. package/dist/modules/mcp/mcp.service.d.ts +11 -52
  42. package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
  43. package/dist/modules/mcp/mcp.service.js +33 -395
  44. package/dist/modules/mcp/mcp.service.js.map +1 -1
  45. package/dist/modules/plugins/index.d.ts +7 -0
  46. package/dist/modules/plugins/index.d.ts.map +1 -0
  47. package/dist/modules/plugins/index.js +6 -0
  48. package/dist/modules/plugins/index.js.map +1 -0
  49. package/dist/modules/plugins/join-proposals.d.ts +53 -0
  50. package/dist/modules/plugins/join-proposals.d.ts.map +1 -0
  51. package/dist/modules/plugins/join-proposals.js +67 -0
  52. package/dist/modules/plugins/join-proposals.js.map +1 -0
  53. package/dist/modules/plugins/join-requests.service.d.ts +81 -0
  54. package/dist/modules/plugins/join-requests.service.d.ts.map +1 -0
  55. package/dist/modules/plugins/join-requests.service.js +135 -0
  56. package/dist/modules/plugins/join-requests.service.js.map +1 -0
  57. package/dist/modules/plugins/plugin-provision.service.d.ts +134 -0
  58. package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -0
  59. package/dist/modules/plugins/plugin-provision.service.js +344 -0
  60. package/dist/modules/plugins/plugin-provision.service.js.map +1 -0
  61. package/dist/modules/plugins/plugins.contract.d.ts +106 -0
  62. package/dist/modules/plugins/plugins.contract.d.ts.map +1 -0
  63. package/dist/modules/plugins/plugins.contract.js +36 -0
  64. package/dist/modules/plugins/plugins.contract.js.map +1 -0
  65. package/dist/modules/plugins/plugins.routes.d.ts +42 -0
  66. package/dist/modules/plugins/plugins.routes.d.ts.map +1 -0
  67. package/dist/modules/plugins/plugins.routes.js +379 -0
  68. package/dist/modules/plugins/plugins.routes.js.map +1 -0
  69. package/dist/modules/plugins/plugins.service.d.ts +60 -0
  70. package/dist/modules/plugins/plugins.service.d.ts.map +1 -0
  71. package/dist/modules/plugins/plugins.service.js +172 -0
  72. package/dist/modules/plugins/plugins.service.js.map +1 -0
  73. package/dist/modules/skills/pending-skills.service.d.ts +2 -2
  74. package/dist/modules/skills/pending-skills.service.js +7 -7
  75. package/dist/modules/skills/pending-skills.service.js.map +1 -1
  76. package/dist/modules/skills/skills.contract.d.ts +4 -4
  77. package/dist/modules/skills/skills.contract.d.ts.map +1 -1
  78. package/dist/modules/skills/skills.contract.js +1 -1
  79. package/dist/modules/skills/skills.service.js +5 -5
  80. package/dist/modules/skills/skills.service.js.map +1 -1
  81. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +65 -0
  82. package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -0
  83. package/dist/modules/tool-manuals/mcp-json-discovery.js +276 -0
  84. package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -0
  85. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +92 -0
  86. package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -0
  87. package/dist/modules/tool-manuals/mcp-server-edit.service.js +328 -0
  88. package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -0
  89. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +38 -12
  90. package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
  91. package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
  92. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +13 -2
  93. package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
  94. package/dist/modules/tool-manuals/tool-manuals.routes.js +233 -2
  95. package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
  96. package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
  97. package/dist/modules/tool-manuals/tool-manuals.service.js +74 -37
  98. package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
  99. package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -3
  100. package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
  101. package/dist/modules/workflow/git/git.service.js +2 -2
  102. package/dist/modules/workflow/git/git.service.js.map +1 -1
  103. package/dist/modules/workspace/kb-seed.service.d.ts +2 -2
  104. package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
  105. package/dist/modules/workspace/kb-seed.service.js +43 -10
  106. package/dist/modules/workspace/kb-seed.service.js.map +1 -1
  107. package/dist/modules/workspace/plugins-migration.d.ts +50 -0
  108. package/dist/modules/workspace/plugins-migration.d.ts.map +1 -0
  109. package/dist/modules/workspace/plugins-migration.js +379 -0
  110. package/dist/modules/workspace/plugins-migration.js.map +1 -0
  111. package/dist/modules/workspace/workspace.routes.js +3 -3
  112. package/dist/modules/workspace/workspace.routes.js.map +1 -1
  113. package/dist/modules/workspace/workspace.service.d.ts +26 -0
  114. package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
  115. package/dist/modules/workspace/workspace.service.js +83 -12
  116. package/dist/modules/workspace/workspace.service.js.map +1 -1
  117. package/dist/shared/kb-layout.test.js +3 -3
  118. package/dist/shared/kb-layout.test.js.map +1 -1
  119. package/dist/shared/utcp-namespace.d.ts +6 -27
  120. package/dist/shared/utcp-namespace.d.ts.map +1 -1
  121. package/dist/shared/utcp-namespace.js +6 -63
  122. package/dist/shared/utcp-namespace.js.map +1 -1
  123. package/dist/shared/variable-refs.d.ts +42 -0
  124. package/dist/shared/variable-refs.d.ts.map +1 -0
  125. package/dist/shared/variable-refs.js +60 -0
  126. package/dist/shared/variable-refs.js.map +1 -0
  127. package/kb-template/.bevelignore +1 -1
  128. package/kb-template/AGENTS.md +88 -35
  129. package/kb-template/KnowledgeBase/How to get started.md +10 -10
  130. package/kb-template/access.md +36 -36
  131. package/migrations/meta/0000_snapshot.json +1479 -1479
  132. package/package.json +5 -4
  133. package/src/assets.ts +25 -25
  134. package/src/core/core-ports.ts +106 -106
  135. package/src/core/create-core-server.ts +55 -9
  136. package/src/core/create-core-services.ts +40 -20
  137. package/src/index.ts +69 -69
  138. package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -98
  139. package/src/modules/access/__tests__/access-declarations.test.ts +28 -28
  140. package/src/modules/access/__tests__/access-md-format.test.ts +18 -18
  141. package/src/modules/access/__tests__/access-mutation.service.test.ts +5 -5
  142. package/src/modules/access/__tests__/access-splice.test.ts +2 -2
  143. package/src/modules/access/__tests__/access.routes.overrides.test.ts +16 -16
  144. package/src/modules/access/__tests__/grant-sources.test.ts +12 -12
  145. package/src/modules/access/__tests__/roles-admin.service.test.ts +13 -1
  146. package/src/modules/access/access-control.interface.ts +8 -7
  147. package/src/modules/access/access-control.service.ts +7 -7
  148. package/src/modules/access/access-declarations.ts +5 -5
  149. package/src/modules/access/access-mutation.service.ts +3 -3
  150. package/src/modules/access/access-splice.ts +4 -4
  151. package/src/modules/access/access.routes.ts +20 -20
  152. package/src/modules/access/creator-access.ts +5 -5
  153. package/src/modules/access/roles-admin.service.ts +13 -2
  154. package/src/modules/admin/admin-access.routes.ts +29 -29
  155. package/src/modules/auth/__tests__/auth.routes.test.ts +91 -91
  156. package/src/modules/auth/__tests__/rate-limit.test.ts +36 -36
  157. package/src/modules/auth/rate-limit.ts +45 -45
  158. package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +67 -0
  159. package/src/modules/code-mode/code-mode-names.ts +10 -36
  160. package/src/modules/code-mode/code-mode.tool.ts +27 -7
  161. package/src/modules/database/connection.ts +15 -15
  162. package/src/modules/database/schema.ts +11 -11
  163. package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -150
  164. package/src/modules/mcp/mcp.service.ts +57 -435
  165. package/src/modules/{groups → plugins}/__tests__/join-proposals.test.ts +1 -1
  166. package/src/modules/{groups → plugins}/__tests__/join-requests.service.test.ts +7 -7
  167. package/src/modules/{groups/__tests__/group-index.service.test.ts → plugins/__tests__/plugin-index.service.test.ts} +41 -41
  168. package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +312 -0
  169. package/src/modules/{groups/__tests__/groups.routes.test.ts → plugins/__tests__/plugins.routes.test.ts} +100 -100
  170. package/src/modules/plugins/index.ts +17 -0
  171. package/src/modules/{groups → plugins}/join-proposals.ts +2 -2
  172. package/src/modules/{groups → plugins}/join-requests.service.ts +8 -8
  173. package/src/modules/{groups/group-provision.service.ts → plugins/plugin-provision.service.ts} +139 -69
  174. package/src/modules/{groups/groups.contract.ts → plugins/plugins.contract.ts} +26 -26
  175. package/src/modules/{groups/groups.routes.ts → plugins/plugins.routes.ts} +102 -102
  176. package/src/modules/{groups/groups.service.ts → plugins/plugins.service.ts} +43 -43
  177. package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -143
  178. package/src/modules/skills/__tests__/pending-skills.service.test.ts +14 -14
  179. package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
  180. package/src/modules/skills/pending-skills.service.ts +7 -7
  181. package/src/modules/skills/skills.contract.ts +4 -4
  182. package/src/modules/skills/skills.service.ts +5 -5
  183. package/src/modules/tool-auth/llm-usage-meter.ts +19 -19
  184. package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +198 -0
  185. package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +346 -0
  186. package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +101 -0
  187. package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +3 -3
  188. package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
  189. package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +27 -27
  190. package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
  191. package/src/modules/tool-manuals/mcp-json-discovery.ts +328 -0
  192. package/src/modules/tool-manuals/mcp-server-edit.service.ts +434 -0
  193. package/src/modules/tool-manuals/tool-manuals.contract.ts +35 -12
  194. package/src/modules/tool-manuals/tool-manuals.routes.ts +222 -1
  195. package/src/modules/tool-manuals/tool-manuals.service.ts +82 -42
  196. package/src/modules/tool-manuals/tool-manuals.tools.ts +6 -3
  197. package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
  198. package/src/modules/workflow/git/__tests__/branch-name.test.ts +3 -3
  199. package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +3 -3
  200. package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +13 -8
  201. package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
  202. package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
  203. package/src/modules/workflow/git/git.service.ts +2 -2
  204. package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +1 -1
  205. package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
  206. package/src/modules/workflow/workflow-hooks.ts +101 -101
  207. package/src/modules/workspace/__tests__/kb-seed.service.test.ts +81 -13
  208. package/src/modules/workspace/__tests__/plugins-migration.test.ts +427 -0
  209. package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -237
  210. package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -236
  211. package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -179
  212. package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -320
  213. package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -337
  214. package/src/modules/workspace/__tests__/workspace.service.test.ts +116 -0
  215. package/src/modules/workspace/bevel-ignore.ts +66 -66
  216. package/src/modules/workspace/kb-seed.service.ts +38 -9
  217. package/src/modules/workspace/plugins-migration.ts +479 -0
  218. package/src/modules/workspace/session-sink.ts +25 -25
  219. package/src/modules/workspace/workspace.routes.ts +3 -3
  220. package/src/modules/workspace/workspace.service.ts +85 -14
  221. package/src/modules/workspace/workspace.tools.ts +922 -922
  222. package/src/shared/__tests__/join-request.test.ts +13 -13
  223. package/src/shared/__tests__/kb-layout.plugin.test.ts +45 -0
  224. package/src/shared/kb-layout.test.ts +3 -3
  225. package/src/shared/utcp-namespace.ts +10 -68
  226. package/src/shared/variable-refs.ts +64 -0
  227. package/src/modules/groups/__tests__/group-provision.service.test.ts +0 -247
  228. package/src/modules/groups/index.ts +0 -17
  229. package/src/shared/__tests__/kb-layout.group.test.ts +0 -45
  230. /package/kb-template/{Groups → Plugins}/.gitkeep +0 -0
@@ -1,9 +1,7 @@
1
1
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
2
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
- import { type CallToolResult, type Tool as McpTool } from '@modelcontextprotocol/sdk/types.js';
4
3
  import '@utcp/http';
5
4
  import '@utcp/mcp';
6
- import { type JsonSchema, type Tool as UtcpTool } from '@utcp/sdk';
7
5
  import { type ISecretsVaultService } from '../secrets-vault/secrets-vault.contract.js';
8
6
  import type { IToolManualService } from '../tool-manuals/tool-manuals.contract.js';
9
7
  import type { SpillStore } from '../workspace/spill-store.js';
@@ -152,59 +150,20 @@ export declare class McpService {
152
150
  private missingUserSecrets;
153
151
  private dispatch;
154
152
  /**
155
- * Handle a code-mode meta-tool. `list_tools`/`tools_info` reflect on the
156
- * session client's discovered catalog; `call_tool_chain` runs the caller's
157
- * JavaScript in the client's isolated-vm, where every tool is reachable as
158
- * `<manual>.tool(...)` and resolves over loopback with the caller's key.
153
+ * Handle a code-mode meta-tool. The shared implementation reflects on this
154
+ * session's client, so `list_tools`/`tools_info` describe exactly the catalog
155
+ * this session discovered and `call_tool_chain` runs in that client's
156
+ * isolated-vm — resolving over loopback with the caller's key. The workspace
157
+ * spill store is passed in so an oversized chain result comes back as a
158
+ * `read_file`-able ref rather than a wall of JSON.
159
159
  */
160
160
  private dispatchMetaTool;
161
161
  }
162
- /** A tool discovered from the manual, flattened into what the proxy advertises. */
163
- export interface ProxiedTool {
164
- utcpName: string;
165
- mcpName: string;
166
- description: string;
167
- inputSchema: JsonSchema;
168
- /** The UTCP manual this tool came from (the `<manual>` in `<manual>.<tool>`),
169
- * used to look up the manual's declared per-user credentials before dispatch. */
170
- manualName: string;
171
- }
172
- /** A discovered tool as an MCP listing entry, or null if its name can't be listed. */
173
- export declare function toListedTool(tool: ProxiedTool): McpTool | null;
174
- /**
175
- * Make a remote server's JSON Schema safe for the Anthropic tool validator:
176
- * - inline local `$ref` pointers (`#/$defs/...`, `#/definitions/...`) and drop
177
- * the now-unreferenced `$defs`/`definitions` blocks (the API restricts `$ref`
178
- * and MCP clients converting our schemas reject it outright);
179
- * - drop non-standard `format` values (OpenAPI's `int32`/`byte`/… — only the
180
- * JSON-Schema-standard formats above are accepted; `format` is advisory, so
181
- * dropping it doesn't change tool behavior).
182
- * Depth-bounded so a recursive schema degrades to a permissive `{}` node instead
183
- * of hanging or emitting the unsupported recursion; non-local/external refs
184
- * degrade the same way. Exported for direct testing.
185
- */
186
- export declare function sanitizeInputSchema(schema: unknown): unknown;
187
- export declare function flattenManualTool(tool: UtcpTool): ProxiedTool;
188
- /**
189
- * Flatten one discovered UTCP tool into the proxy's advertised shape: strip the
190
- * `<manual>.` namespace prefix for the MCP name and keep the UTCP input schema
191
- * verbatim (Bevel-hosted HTTP tools show their `{body}` envelope, exactly as in
192
- * `call_tool_chain`).
193
- */
194
- export declare function flattenDiscoveredTool(prefix: string, tool: UtcpTool): ProxiedTool;
195
- /**
196
- * Extract a human-meaningful failure message from a tool-call error. UTCP's
197
- * HTTP protocol surfaces a non-2xx as an axios-style error whose `.response.data`
198
- * is the REST endpoint's JSON body (`{ error: "..." }`). Pull that out so the
199
- * MCP caller sees the tool's real message instead of a bare "status code 500".
200
- */
201
- export declare function describeToolFailure(err: unknown): string;
202
- export declare function toCallToolResult(value: unknown): CallToolResult;
203
162
  /**
204
- * The result returned when the caller is missing personal credentials a tool
205
- * needs. Marked `isError` so the external agent surfaces it to the person rather
206
- * than treating it as tool output. Names the tool, lists the missing items, and
207
- * gives the absolute setup-page URL so the person can provide them and retry.
163
+ * The pieces of the proxy that are shared with the local MCP server now live in
164
+ * `@bevel-software/platform-mcp-core`. Re-exported here because this module is
165
+ * where they have always been imported from — inside this package and by its
166
+ * tests — and moving a file is not a reason to churn every call site.
208
167
  */
209
- export declare function needsAuthorizationResult(toolName: string, missing: string[], connectUrl: string): CallToolResult;
168
+ export { type ProxiedTool, toListedTool, sanitizeInputSchema, flattenManualTool, flattenDiscoveredTool, describeToolFailure, toCallToolResult, needsAuthorizationResult, } from '@bevel-software/platform-mcp-core';
210
169
  //# sourceMappingURL=mcp.service.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.service.d.ts","sourceRoot":"","sources":["../../../src/modules/mcp/mcp.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mcp.service.d.ts","sourceRoot":"","sources":["../../../src/modules/mcp/mcp.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;AAanG,OAAO,YAAY,CAAC;AACpB,OAAO,WAAW,CAAC;AAwBnB,OAAO,EAAiB,KAAK,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAEtG,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0CAA0C,CAAC;AACnF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wCAAwC,CAAC;AAGnF;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,qGAAqG;IACrG,UAAU,EAAE,UAAU,CAAC;IACvB,kFAAkF;IAClF,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAqBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBAAa,UAAU;IAenB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,IAAI;IAIrB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAI7B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC;IAKhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAC;IA5BrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2B;IAE1D;;;;OAIG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;gBAM7B,YAAY,EAAE,eAAe,EAC7B,IAAI,EAAE,eAAe,EAIrB,YAAY,CAAC,EAAE,oBAAoB,YAAA,EACnC,WAAW,CAAC,EAAE,kBAAkB,YAAA,EAIhC,cAAc,CAAC,EAAE,oBAAoB,YAAA,EAKrC,iBAAiB,CAAC,GAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,aAAA;IAGxE;;;OAGG;IACH,gBAAgB,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GAAG,MAAM,IAAI;IAInE;;;;;OAKG;IACG,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GAAG,IAAI,EACtB,MAAM,EAAE,MAAM,EACd,oBAAoB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,GAChD,OAAO,CAAC;QAAE,SAAS,EAAE,6BAA6B,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAuKxE,qFAAqF;YACvE,cAAc;IAM5B,wFAAwF;YAC1E,UAAU;IAQxB;;;;;;;;OAQG;YACW,YAAY;IAqB1B,8FAA8F;YAChF,YAAY;IAI1B;;;;;;;OAOG;YACW,oBAAoB;IA0BlC,uFAAuF;IACvF,OAAO,CAAC,gBAAgB;IAWxB;;;;;;;;OAQG;YACW,WAAW;IAgBzB;;;;;;;;;;OAUG;YACW,aAAa;IAmC3B;;;;;;OAMG;IACH;;;;;;;;OAQG;YACW,gBAAgB;IAoB9B;;;;;;OAMG;YACW,kBAAkB;YA0ClB,QAAQ;IAsBtB;;;;;;;OAOG;YACW,gBAAgB;CAO/B;AAED;;;;;GAKG;AACH,OAAO,EACL,KAAK,WAAW,EAChB,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,EACrB,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,mCAAmC,CAAC"}
@@ -6,14 +6,12 @@ import '@utcp/http'; // side effect: registers the 'http' UTCP communication pro
6
6
  import '@utcp/mcp'; // side effect: registers the 'mcp' protocol (native MCP-server `.tool` sources)
7
7
  import { UtcpClientConfigSerializer, CallTemplateSerializer, } from '@utcp/sdk';
8
8
  import { CodeModeUtcpClient } from '@utcp/code-mode';
9
- import { utcpNameToTsInterfaceName, findToolByName } from '../code-mode/code-mode-names.js';
9
+ import { CODE_MODE_META_TOOLS, META_TOOL_NAMES, dispatchMetaTool, dispatchToolCall, registerManual, flattenManualTool, toListedTool, toolError, needsAuthorizationResult, skillPromptText, } from '@bevel-software/platform-mcp-core';
10
10
  import { bevelSecretsLoaderConfig } from '../secrets-vault/index.js';
11
11
  import { scopesCovered } from '../secrets-vault/secrets-vault.contract.js';
12
12
  import { EXTERNAL_KB_MANUAL_NAME } from '../tool-manuals/tool-manuals.contract.js';
13
13
  import { seedBevelHostedManualVars } from '../../shared/utcp-namespace.js';
14
14
  import { ManualFailureMemo } from './manual-failure-memo.js';
15
- /** Default cap on a `call_tool_chain` result's stringified size before it spills. */
16
- const CALL_TOOL_CHAIN_MAX_OUTPUT = 200_000;
17
15
  /**
18
16
  * Upper bound on one `loopbackJson` round-trip (manual list, skill fetch) so a
19
17
  * hung loopback can't stall `createSession`. Generous: these endpoints answer
@@ -30,66 +28,6 @@ const LOOPBACK_TIMEOUT_MS = 15_000;
30
28
  */
31
29
  const MCP_LOOPBACK_TOKEN_TTL_MS = 5 * 60 * 60 * 1000;
32
30
  const callTemplateSerializer = new CallTemplateSerializer();
33
- /**
34
- * Code-mode meta-tools exposed ALONGSIDE the direct tools. They let an external
35
- * agent batch many Bevel calls into one isolated-vm run (`call_tool_chain`)
36
- * instead of one MCP round-trip per call — the same efficiency our own agent
37
- * gets. `call_tool_chain`'s description carries the code-mode protocol (there is
38
- * no system prompt over MCP), so the client learns the convention from the tool
39
- * itself; `list_tools`/`tools_info` are how it discovers what to call.
40
- *
41
- * Security is identical to the direct surface: the chain runs in our isolated-vm
42
- * but calls tools over loopback with the CALLER's key against the external
43
- * catalog — internal-only tools aren't in that catalog, so a chain can't reach
44
- * them either.
45
- */
46
- const CALL_TOOL_CHAIN_DESCRIPTION = [
47
- 'Execute a short JavaScript program with direct access to every registered UTCP tool as a synchronous function. Call tools as `KNOWLEDGE_BASE.<tool>({ body: { ...args } })` with NO `await` (results are already resolved), and `return` the final value. The runtime is plain JavaScript (no type annotations / no TypeScript-only syntax).',
48
- 'Discover first: `list_tools` lists every tool in callable form (e.g. `KNOWLEDGE_BASE.read_file`); `tools_info` returns their exact argument + return shapes — do not guess. Batch multiple tool calls into one chain to avoid a round-trip per call. The chain runs with your own connection key, so it can only reach the tools you can already call directly.',
49
- 'Large results: if the combined result+logs exceed `max_output_size` (default 200000 chars) the full JSON is spilled to a shared store and you get back a `__tool_chain_spill__/…` ref instead. Read it with `read_file` (pass that ref as `path` — `branch` is ignored — plus `offset`/`limit` to slice it), or better, re-run a narrower chain that returns only what you need.',
50
- ].join('\n\n');
51
- const CODE_MODE_META_TOOLS = [
52
- {
53
- name: 'list_tools',
54
- description: 'List every UTCP tool currently registered, in TypeScript-accessible form (e.g. `KNOWLEDGE_BASE.read_file`) for use inside `call_tool_chain`.',
55
- inputSchema: { type: 'object', properties: {}, additionalProperties: false },
56
- },
57
- {
58
- name: 'tools_info',
59
- description: 'Get full TypeScript interface definitions for named tools (names from `list_tools`). The schemas are the source of truth — do not guess shapes.',
60
- inputSchema: {
61
- type: 'object',
62
- properties: {
63
- tool_names: { type: 'array', items: { type: 'string' }, minItems: 1, description: 'Tool names to describe.' },
64
- },
65
- required: ['tool_names'],
66
- additionalProperties: false,
67
- },
68
- },
69
- {
70
- name: 'call_tool_chain',
71
- description: CALL_TOOL_CHAIN_DESCRIPTION,
72
- inputSchema: {
73
- type: 'object',
74
- properties: {
75
- code: { type: 'string', minLength: 1, description: 'JavaScript to execute against the registered tools.' },
76
- timeout: { type: 'integer', minimum: 1000, maximum: 120000, description: 'Timeout in ms (default 30000).' },
77
- max_output_size: { type: 'integer', minimum: 1000, maximum: 1000000, description: 'Max result+logs size in chars before spilling (default 200000, max 1000000).' },
78
- },
79
- required: ['code'],
80
- additionalProperties: false,
81
- },
82
- },
83
- ];
84
- const META_TOOL_NAMES = new Set(CODE_MODE_META_TOOLS.map((t) => t.name));
85
- /** The prompt message text for a loaded skill: its instructions body + a pointer to bundled files. */
86
- function skillPromptText(skill) {
87
- const rel = skill.files.map((f) => f.startsWith(`${skill.path}/`) ? f.slice(skill.path.length + 1) : f);
88
- const footer = rel.length
89
- ? `\n\n---\nSkill folder: ${skill.path}\nBundled files (fetch each with the get_skill tool: { name: "${skill.name}", file }): ${rel.join(', ')}`
90
- : '';
91
- return `${skill.body}${footer}`;
92
- }
93
31
  /**
94
32
  * The MCP server is a GENERIC proxy over the UTCP tool surface. It owns no tool
95
33
  * logic: per MCP session it stands up a `UtcpClient` pointed at the backend's
@@ -450,31 +388,22 @@ export class McpService {
450
388
  // memo while registration is in flight, the stale failure from the OLD
451
389
  // credential must not resurrect an entry the clear removed.
452
390
  const generation = this.manualFailures.currentGeneration;
453
- try {
454
- const result = await client.registerManual(m);
455
- if (result && result.success === false) {
456
- const errs = Array.isArray(result.errors) ? result.errors.join('; ') : 'unknown error';
457
- if (isKb)
458
- throw new Error(`Bevel tool discovery failed: ${errs}`);
459
- this.manualFailures.recordFailure(userId, name, errs, generation);
460
- console.warn(`[mcp] skipping manual "${name}": ${errs}`);
461
- }
462
- else if (!isKb) {
463
- this.manualFailures.clear(userId, name);
464
- }
465
- }
466
- catch (err) {
467
- // Registration (network/discovery) failure — the templates are already
468
- // validated, so this is a runtime, not a schema, problem.
391
+ // `registerManual` never throws: a discovery/network failure and a
392
+ // validation failure both come back as `{ ok: false }`, because the retry
393
+ // policy — this memo — is ours, not the shared layer's.
394
+ const result = await registerManual(client, m);
395
+ if (!result.ok) {
469
396
  if (isKb)
470
- throw err;
471
- const message = err instanceof Error ? err.message : String(err);
472
- this.manualFailures.recordFailure(userId, name, message, generation);
473
- console.warn(`[mcp] skipping manual "${name}": ${message}`);
397
+ throw new Error(`Bevel tool discovery failed: ${result.error}`);
398
+ this.manualFailures.recordFailure(userId, name, result.error, generation);
399
+ console.warn(`[mcp] skipping manual "${name}": ${result.error}`);
400
+ }
401
+ else if (!isKb) {
402
+ this.manualFailures.clear(userId, name);
474
403
  }
475
404
  }
476
405
  const utcpTools = await client.getTools();
477
- return utcpTools.map((tool) => flattenManualTool(tool));
406
+ return utcpTools.map((tool) => flattenManualTool(tool, EXTERNAL_KB_MANUAL_NAME));
478
407
  }
479
408
  /**
480
409
  * Run one tool call through `callToolStreaming` with a one-chunk lookahead:
@@ -554,324 +483,33 @@ export class McpService {
554
483
  // payload without a `progressToken` is accepted — same approach the prior
555
484
  // handler used; the strict ServerNotification type requires the token.
556
485
  extra) {
557
- const args = request.params.arguments ?? {};
558
486
  const progressToken = request.params._meta?.progressToken;
559
- let prev;
560
- let hasPrev = false;
561
- let progress = 0;
562
- try {
563
- // Args pass through to UTCP verbatim — each communication protocol does
564
- // its own serialization (http reads the template's `body_field` out of the
565
- // args, mcp forwards them untouched as MCP `arguments`). The advertised
566
- // schema is the tool's UTCP `inputs` verbatim too, so what the caller
567
- // sends is already in the shape the protocol expects; any reshaping here
568
- // would be wrong for at least one protocol.
569
- for await (const chunk of client.callToolStreaming(tool.utcpName, args)) {
570
- if (hasPrev) {
571
- progress += 1;
572
- await extra
573
- .sendNotification({
574
- method: 'notifications/progress',
575
- params: {
576
- ...(progressToken !== undefined ? { progressToken } : {}),
577
- progress,
578
- message: renderProgress(prev),
579
- },
580
- })
581
- .catch((err) => console.warn('[mcp] progress notification failed:', err));
582
- }
583
- prev = chunk;
584
- hasPrev = true;
585
- }
586
- }
587
- catch (err) {
588
- return toolError(`The "${tool.mcpName}" tool failed: ${describeToolFailure(err)}`);
589
- }
590
- return toCallToolResult(prev);
487
+ return dispatchToolCall(client, tool, request.params.arguments ?? {}, (progress, message) => extra.sendNotification({
488
+ method: 'notifications/progress',
489
+ params: {
490
+ ...(progressToken !== undefined ? { progressToken } : {}),
491
+ progress,
492
+ message,
493
+ },
494
+ }));
591
495
  }
592
496
  /**
593
- * Handle a code-mode meta-tool. `list_tools`/`tools_info` reflect on the
594
- * session client's discovered catalog; `call_tool_chain` runs the caller's
595
- * JavaScript in the client's isolated-vm, where every tool is reachable as
596
- * `<manual>.tool(...)` and resolves over loopback with the caller's key.
497
+ * Handle a code-mode meta-tool. The shared implementation reflects on this
498
+ * session's client, so `list_tools`/`tools_info` describe exactly the catalog
499
+ * this session discovered and `call_tool_chain` runs in that client's
500
+ * isolated-vm — resolving over loopback with the caller's key. The workspace
501
+ * spill store is passed in so an oversized chain result comes back as a
502
+ * `read_file`-able ref rather than a wall of JSON.
597
503
  */
598
504
  async dispatchMetaTool(client, name, args) {
599
- try {
600
- if (name === 'list_tools') {
601
- const tools = await client.config.tool_repository.getTools();
602
- return toCallToolResult({ tools: tools.map((t) => utcpNameToTsInterfaceName(t.name)) });
603
- }
604
- if (name === 'tools_info') {
605
- const names = Array.isArray(args.tool_names) ? args.tool_names : [];
606
- const interfaces = [];
607
- const notFound = [];
608
- for (const n of names) {
609
- const found = await findToolByName(client, n);
610
- if (found)
611
- interfaces.push(client.toolToTypeScriptInterface(found.tool));
612
- else
613
- notFound.push(n);
614
- }
615
- return toCallToolResult({ interfaces: interfaces.join('\n\n'), not_found: notFound });
616
- }
617
- // call_tool_chain
618
- const code = typeof args.code === 'string' ? args.code : '';
619
- const timeout = typeof args.timeout === 'number' ? args.timeout : 30_000;
620
- // Clamp to [1000, 1_000_000] so a caller can't force oversized inline
621
- // output past the spill (schema bounds are advisory over a raw JSON-RPC call).
622
- const maxOutputSize = typeof args.max_output_size === 'number'
623
- ? Math.min(1_000_000, Math.max(1_000, Math.trunc(args.max_output_size)))
624
- : CALL_TOOL_CHAIN_MAX_OUTPUT;
625
- const { result, logs } = await client.callToolChain(code, timeout);
626
- // Bound the payload: an external session has no ambient workspace, so an
627
- // oversized result spills to the shared store and we return only a ref —
628
- // parity with the in-process agent's `call_tool_chain`.
629
- if (JSON.stringify({ success: true, result, logs }).length <= maxOutputSize) {
630
- return toCallToolResult({ success: true, result, logs });
631
- }
632
- const fullJson = JSON.stringify({ result, logs }, null, 2);
633
- const { ref, bytes } = await this.opts.spillStore.write(fullJson);
634
- return toCallToolResult({
635
- success: true,
636
- truncated: true,
637
- result_ref: ref,
638
- result_bytes: bytes,
639
- message: `Result+logs payload was ${fullJson.length} characters (exceeded max_output_size of ${maxOutputSize}). Full JSON saved to the shared spill store as \`${ref}\`. Read it back with \`read_file\` (pass that ref as \`path\`, \`branch\` ignored, plus \`offset\`/\`limit\` to slice), or re-run a narrower chain that returns only what you need.`,
640
- });
641
- }
642
- catch (err) {
643
- return toolError(`The "${name}" tool failed: ${describeToolFailure(err)}`);
644
- }
505
+ return dispatchMetaTool(client, name, args, this.opts.spillStore);
645
506
  }
646
507
  }
647
508
  /**
648
- * Flatten a discovered UTCP tool (`<manual>.<tool>`) into the proxy's advertised
649
- * shape when MULTIPLE manuals are registered. KB tools keep their bare name (so
650
- * existing external agents still call `read_file`); a user `.tool`'s tool is
651
- * namespaced as `<manual>_<tool>` to guarantee a unique, dot-free MCP name.
509
+ * The pieces of the proxy that are shared with the local MCP server now live in
510
+ * `@bevel-software/platform-mcp-core`. Re-exported here because this module is
511
+ * where they have always been imported from — inside this package and by its
512
+ * tests — and moving a file is not a reason to churn every call site.
652
513
  */
653
- /**
654
- * MCP tool-name grammar (also the Anthropic API's), and a length bound. A
655
- * remote MCP server can expose a tool whose flattened name breaks this — too
656
- * long, or an illegal char the `<manual>_<name>` flattening didn't remove — and
657
- * an MCP client (or the model API behind it) rejects the ENTIRE `tools/list`
658
- * response when a single entry is non-conforming. That makes EVERY tool vanish
659
- * the moment one bad tool from a newly-added server enters the catalog, with no
660
- * server-side error (the rejection is the client's). `toListedTool` isolates it
661
- * per tool: drop the offender (logged), normalize an odd schema, keep the rest.
662
- */
663
- const MCP_TOOL_NAME_RE = /^[a-zA-Z0-9_-]+$/;
664
- // The Anthropic API caps a tool name at 128 chars — but the MCP CLIENT (Claude
665
- // Code, claude.ai) prepends `mcp__<server>__` (≈20+ chars) before sending it,
666
- // and that FULL name is what the 128 applies to. So budget for the prefix here,
667
- // or a long `googlecalendar_…` name we pass gets the whole request 400'd. This
668
- // is deliberately conservative; a dropped tool is logged so it's diagnosable.
669
- const MCP_TOOL_NAME_MAX = 100;
670
- /** A discovered tool as an MCP listing entry, or null if its name can't be listed. */
671
- export function toListedTool(tool) {
672
- if (!MCP_TOOL_NAME_RE.test(tool.mcpName) || tool.mcpName.length > MCP_TOOL_NAME_MAX) {
673
- console.warn(`[mcp] dropping tool "${tool.mcpName}" from the listing — not a valid MCP tool name ` +
674
- `(must match ${MCP_TOOL_NAME_RE} and be ≤${MCP_TOOL_NAME_MAX} chars). ` +
675
- 'One non-conforming tool would otherwise make the whole toolset disappear on the client.');
676
- return null;
677
- }
678
- // MCP requires an object inputSchema. A remote server's schema that isn't a
679
- // plain object (or omits `type: 'object'`) can invalidate the whole response,
680
- // so normalize it — keeping any declared properties — rather than pass it
681
- // through verbatim.
682
- const raw = tool.inputSchema;
683
- let inputSchema = raw && typeof raw === 'object' && !Array.isArray(raw)
684
- ? { type: 'object', ...raw }
685
- : { type: 'object', properties: {} };
686
- // Sanitize the schema into what the Anthropic tool validator accepts. A
687
- // remote server that emits a construct the validator rejects — Google's
688
- // gmail/calendar use `$ref`/`$defs` AND OpenAPI `format` values like
689
- // `int32`/`byte` — makes the CLIENT reject the ENTIRE tools/list response,
690
- // so all tools vanish and nothing registers. Sanitizing per-tool means one
691
- // odd server can't blank the whole toolset.
692
- inputSchema = sanitizeInputSchema(inputSchema);
693
- // The MCP/Anthropic validator requires the top-level `type` to be exactly
694
- // "object" and (for the Anthropic API) `properties` to be present. Force both
695
- // so a remote schema that declared something else — or a union like
696
- // `["object","null"]` — can't reject the whole tools/list.
697
- inputSchema.type = 'object';
698
- if (typeof inputSchema.properties !== 'object' || inputSchema.properties === null) {
699
- inputSchema.properties = {};
700
- }
701
- return {
702
- name: tool.mcpName,
703
- description: tool.description,
704
- inputSchema: inputSchema,
705
- };
706
- }
707
- /** JSON-Schema string `format` values the Anthropic tool validator accepts. */
708
- const SUPPORTED_SCHEMA_FORMATS = new Set([
709
- 'date-time',
710
- 'time',
711
- 'date',
712
- 'duration',
713
- 'email',
714
- 'hostname',
715
- 'uri',
716
- 'ipv4',
717
- 'ipv6',
718
- 'uuid',
719
- ]);
720
- /**
721
- * Make a remote server's JSON Schema safe for the Anthropic tool validator:
722
- * - inline local `$ref` pointers (`#/$defs/...`, `#/definitions/...`) and drop
723
- * the now-unreferenced `$defs`/`definitions` blocks (the API restricts `$ref`
724
- * and MCP clients converting our schemas reject it outright);
725
- * - drop non-standard `format` values (OpenAPI's `int32`/`byte`/… — only the
726
- * JSON-Schema-standard formats above are accepted; `format` is advisory, so
727
- * dropping it doesn't change tool behavior).
728
- * Depth-bounded so a recursive schema degrades to a permissive `{}` node instead
729
- * of hanging or emitting the unsupported recursion; non-local/external refs
730
- * degrade the same way. Exported for direct testing.
731
- */
732
- export function sanitizeInputSchema(schema) {
733
- const root = schema;
734
- const resolvePointer = (pointer) => {
735
- if (!pointer.startsWith('#/'))
736
- return undefined;
737
- let node = root;
738
- for (const partRaw of pointer.slice(2).split('/')) {
739
- const part = partRaw.replace(/~1/g, '/').replace(/~0/g, '~');
740
- if (!node || typeof node !== 'object')
741
- return undefined;
742
- node = node[part];
743
- }
744
- return node;
745
- };
746
- const walk = (node, depth) => {
747
- if (depth > 20)
748
- return {}; // recursion/cycle guard — permissive fallback
749
- if (Array.isArray(node))
750
- return node.map((item) => walk(item, depth + 1));
751
- if (!node || typeof node !== 'object')
752
- return node;
753
- const obj = node;
754
- if (typeof obj.$ref === 'string') {
755
- const target = resolvePointer(obj.$ref);
756
- // JSON Schema allows siblings next to $ref; keep them, target wins ties.
757
- const { $ref: _ref, ...siblings } = obj;
758
- const resolved = walk(target ?? {}, depth + 1);
759
- return resolved && typeof resolved === 'object' && !Array.isArray(resolved)
760
- ? { ...siblings, ...resolved }
761
- : Object.keys(siblings).length
762
- ? siblings
763
- : resolved ?? {};
764
- }
765
- const out = {};
766
- for (const [key, value] of Object.entries(obj)) {
767
- if (key === '$defs' || key === 'definitions')
768
- continue; // inlined above
769
- // Drop a non-standard `format` (OpenAPI `int32`/`byte`/…) — the validator
770
- // only allows the JSON-Schema-standard set; the annotation is non-load-bearing.
771
- if (key === 'format' && (typeof value !== 'string' || !SUPPORTED_SCHEMA_FORMATS.has(value))) {
772
- continue;
773
- }
774
- out[key] = walk(value, depth + 1);
775
- }
776
- return out;
777
- };
778
- return walk(schema, 0);
779
- }
780
- export function flattenManualTool(tool) {
781
- const dot = tool.name.indexOf('.');
782
- const manual = dot >= 0 ? tool.name.slice(0, dot) : '';
783
- const bare = dot >= 0 ? tool.name.slice(dot + 1) : tool.name;
784
- const mcpName = manual === EXTERNAL_KB_MANUAL_NAME ? bare : tool.name.replace(/\./g, '_');
785
- return {
786
- utcpName: tool.name,
787
- mcpName,
788
- description: tool.description,
789
- inputSchema: tool.inputs,
790
- manualName: manual,
791
- };
792
- }
793
- /**
794
- * Flatten one discovered UTCP tool into the proxy's advertised shape: strip the
795
- * `<manual>.` namespace prefix for the MCP name and keep the UTCP input schema
796
- * verbatim (Bevel-hosted HTTP tools show their `{body}` envelope, exactly as in
797
- * `call_tool_chain`).
798
- */
799
- export function flattenDiscoveredTool(prefix, tool) {
800
- return {
801
- utcpName: tool.name,
802
- mcpName: tool.name.startsWith(prefix) ? tool.name.slice(prefix.length) : tool.name,
803
- description: tool.description,
804
- inputSchema: tool.inputs,
805
- // `prefix` is `<manual>.`; the manual is that without the trailing dot.
806
- manualName: prefix.endsWith('.') ? prefix.slice(0, -1) : prefix,
807
- };
808
- }
809
- /**
810
- * Extract a human-meaningful failure message from a tool-call error. UTCP's
811
- * HTTP protocol surfaces a non-2xx as an axios-style error whose `.response.data`
812
- * is the REST endpoint's JSON body (`{ error: "..." }`). Pull that out so the
813
- * MCP caller sees the tool's real message instead of a bare "status code 500".
814
- */
815
- export function describeToolFailure(err) {
816
- const data = err?.response?.data;
817
- if (data && typeof data === 'object') {
818
- const inner = data.error;
819
- if (typeof inner === 'string' && inner.length > 0)
820
- return inner;
821
- }
822
- if (typeof data === 'string' && data.length > 0)
823
- return data;
824
- return err instanceof Error ? err.message : String(err);
825
- }
826
- /**
827
- * Turn a tool's final value into an MCP result:
828
- * - a tool that already returns the MCP agentic shape (`{ content: [...] }`,
829
- * each entry a real content block) is passed through unchanged;
830
- * - a bare string becomes the text content;
831
- * - anything else is JSON-stringified into one text block.
832
- *
833
- * Note we do NOT collapse a structured object down to its `text` field: doing
834
- * so silently dropped the other fields (e.g. `ask`'s `status` / `sessionId`,
835
- * the id a caller must echo back to poll or continue a conversation).
836
- * Stringifying the whole object keeps every field, so the caller always sees
837
- * the id it is responsible for echoing.
838
- *
839
- * The passthrough guard checks the entries, not just that `content` is an array:
840
- * a domain value that merely happens to carry a `content` array of non-blocks
841
- * (e.g. `{ content: ['a', 'b'] }`) is data, not an MCP result, so it falls
842
- * through to JSON-stringify and survives intact instead of being emitted as a
843
- * malformed result the client can't parse.
844
- */
845
- function isMcpContentBlock(entry) {
846
- return typeof entry === 'object' && entry !== null && typeof entry.type === 'string';
847
- }
848
- export function toCallToolResult(value) {
849
- // Already in MCP agentic format — pass through untouched, but only when every
850
- // `content` entry is a real content block (has a string `type`).
851
- const content = value?.content;
852
- if (value && typeof value === 'object' && Array.isArray(content) && content.every(isMcpContentBlock)) {
853
- return value;
854
- }
855
- const text = typeof value === 'string' ? value : JSON.stringify(value ?? null);
856
- return { content: [{ type: 'text', text: text || '(tool produced no output)' }] };
857
- }
858
- function renderProgress(chunk) {
859
- const s = typeof chunk === 'string' ? chunk : JSON.stringify(chunk);
860
- return s.length > 500 ? s.slice(0, 497) + '...' : s;
861
- }
862
- function toolError(message) {
863
- return { isError: true, content: [{ type: 'text', text: message }] };
864
- }
865
- /**
866
- * The result returned when the caller is missing personal credentials a tool
867
- * needs. Marked `isError` so the external agent surfaces it to the person rather
868
- * than treating it as tool output. Names the tool, lists the missing items, and
869
- * gives the absolute setup-page URL so the person can provide them and retry.
870
- */
871
- export function needsAuthorizationResult(toolName, missing, connectUrl) {
872
- const items = missing.join(', ');
873
- const text = `The "${toolName}" tool needs credentials you haven't set up yet: ${items}. ` +
874
- `Open ${connectUrl} to connect your accounts and enter your keys, then run the tool again.`;
875
- return { isError: true, content: [{ type: 'text', text }] };
876
- }
514
+ export { toListedTool, sanitizeInputSchema, flattenManualTool, flattenDiscoveredTool, describeToolFailure, toCallToolResult, needsAuthorizationResult, } from '@bevel-software/platform-mcp-core';
877
515
  //# sourceMappingURL=mcp.service.js.map