@bevel-software/platform-core-backend 0.7.4 → 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.
- package/LICENSE +202 -202
- package/THIRD-PARTY-NOTICES.md +428 -454
- package/dist/core/core-ports.d.ts +1 -1
- package/dist/core/create-core-server.d.ts.map +1 -1
- package/dist/core/create-core-server.js +51 -7
- package/dist/core/create-core-server.js.map +1 -1
- package/dist/core/create-core-services.d.ts +5 -3
- package/dist/core/create-core-services.d.ts.map +1 -1
- package/dist/core/create-core-services.js +38 -21
- package/dist/core/create-core-services.js.map +1 -1
- package/dist/modules/access/access-control.interface.d.ts +8 -7
- package/dist/modules/access/access-control.interface.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.d.ts +1 -1
- package/dist/modules/access/access-control.service.d.ts.map +1 -1
- package/dist/modules/access/access-control.service.js +6 -6
- package/dist/modules/access/access-control.service.js.map +1 -1
- package/dist/modules/access/access-declarations.d.ts +5 -5
- package/dist/modules/access/access-declarations.js +3 -3
- package/dist/modules/access/access-mutation.service.d.ts +3 -3
- package/dist/modules/access/access-mutation.service.d.ts.map +1 -1
- package/dist/modules/access/access-mutation.service.js +3 -3
- package/dist/modules/access/access-mutation.service.js.map +1 -1
- package/dist/modules/access/access-splice.js +4 -4
- package/dist/modules/access/access-splice.js.map +1 -1
- package/dist/modules/access/access.routes.js +19 -19
- package/dist/modules/access/access.routes.js.map +1 -1
- package/dist/modules/access/creator-access.d.ts +2 -2
- package/dist/modules/access/creator-access.js +5 -5
- package/dist/modules/access/creator-access.js.map +1 -1
- package/dist/modules/access/roles-admin.service.d.ts +18 -4
- package/dist/modules/access/roles-admin.service.d.ts.map +1 -1
- package/dist/modules/access/roles-admin.service.js +14 -4
- package/dist/modules/access/roles-admin.service.js.map +1 -1
- package/dist/modules/code-mode/code-mode-names.d.ts +5 -13
- package/dist/modules/code-mode/code-mode-names.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode-names.js +5 -27
- package/dist/modules/code-mode/code-mode-names.js.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.d.ts.map +1 -1
- package/dist/modules/code-mode/code-mode.tool.js +29 -7
- package/dist/modules/code-mode/code-mode.tool.js.map +1 -1
- package/dist/modules/mcp/mcp.service.d.ts +11 -52
- package/dist/modules/mcp/mcp.service.d.ts.map +1 -1
- package/dist/modules/mcp/mcp.service.js +33 -395
- package/dist/modules/mcp/mcp.service.js.map +1 -1
- package/dist/modules/plugins/index.d.ts +7 -0
- package/dist/modules/plugins/index.d.ts.map +1 -0
- package/dist/modules/plugins/index.js +6 -0
- package/dist/modules/plugins/index.js.map +1 -0
- package/dist/modules/plugins/join-proposals.d.ts +53 -0
- package/dist/modules/plugins/join-proposals.d.ts.map +1 -0
- package/dist/modules/plugins/join-proposals.js +67 -0
- package/dist/modules/plugins/join-proposals.js.map +1 -0
- package/dist/modules/plugins/join-requests.service.d.ts +81 -0
- package/dist/modules/plugins/join-requests.service.d.ts.map +1 -0
- package/dist/modules/plugins/join-requests.service.js +135 -0
- package/dist/modules/plugins/join-requests.service.js.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts +134 -0
- package/dist/modules/plugins/plugin-provision.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugin-provision.service.js +344 -0
- package/dist/modules/plugins/plugin-provision.service.js.map +1 -0
- package/dist/modules/plugins/plugins.contract.d.ts +106 -0
- package/dist/modules/plugins/plugins.contract.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.contract.js +36 -0
- package/dist/modules/plugins/plugins.contract.js.map +1 -0
- package/dist/modules/plugins/plugins.routes.d.ts +42 -0
- package/dist/modules/plugins/plugins.routes.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.routes.js +379 -0
- package/dist/modules/plugins/plugins.routes.js.map +1 -0
- package/dist/modules/plugins/plugins.service.d.ts +60 -0
- package/dist/modules/plugins/plugins.service.d.ts.map +1 -0
- package/dist/modules/plugins/plugins.service.js +172 -0
- package/dist/modules/plugins/plugins.service.js.map +1 -0
- package/dist/modules/skills/pending-skills.service.d.ts +2 -2
- package/dist/modules/skills/pending-skills.service.js +7 -7
- package/dist/modules/skills/pending-skills.service.js.map +1 -1
- package/dist/modules/skills/skills.contract.d.ts +4 -4
- package/dist/modules/skills/skills.contract.d.ts.map +1 -1
- package/dist/modules/skills/skills.contract.js +1 -1
- package/dist/modules/skills/skills.service.js +5 -5
- package/dist/modules/skills/skills.service.js.map +1 -1
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts +65 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.d.ts.map +1 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.js +276 -0
- package/dist/modules/tool-manuals/mcp-json-discovery.js.map +1 -0
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts +92 -0
- package/dist/modules/tool-manuals/mcp-server-edit.service.d.ts.map +1 -0
- package/dist/modules/tool-manuals/mcp-server-edit.service.js +328 -0
- package/dist/modules/tool-manuals/mcp-server-edit.service.js.map +1 -0
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts +38 -12
- package/dist/modules/tool-manuals/tool-manuals.contract.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.contract.js +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts +13 -2
- package/dist/modules/tool-manuals/tool-manuals.routes.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.routes.js +233 -2
- package/dist/modules/tool-manuals/tool-manuals.routes.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.d.ts.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.service.js +74 -37
- package/dist/modules/tool-manuals/tool-manuals.service.js.map +1 -1
- package/dist/modules/tool-manuals/tool-manuals.tools.js +6 -3
- package/dist/modules/tool-manuals/tool-manuals.tools.js.map +1 -1
- package/dist/modules/workflow/git/git.service.js +2 -2
- package/dist/modules/workflow/git/git.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.service.d.ts +11 -0
- package/dist/modules/workflow/pending-commits.service.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.service.js +37 -9
- package/dist/modules/workflow/pending-commits.service.js.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.d.ts +17 -4
- package/dist/modules/workflow/pending-commits.worker.d.ts.map +1 -1
- package/dist/modules/workflow/pending-commits.worker.js +63 -9
- package/dist/modules/workflow/pending-commits.worker.js.map +1 -1
- package/dist/modules/workflow/workflow.service.d.ts +1 -0
- package/dist/modules/workflow/workflow.service.d.ts.map +1 -1
- package/dist/modules/workflow/workflow.service.js +6 -1
- package/dist/modules/workflow/workflow.service.js.map +1 -1
- package/dist/modules/workspace/kb-seed.service.d.ts +2 -2
- package/dist/modules/workspace/kb-seed.service.d.ts.map +1 -1
- package/dist/modules/workspace/kb-seed.service.js +43 -10
- package/dist/modules/workspace/kb-seed.service.js.map +1 -1
- package/dist/modules/workspace/plugins-migration.d.ts +50 -0
- package/dist/modules/workspace/plugins-migration.d.ts.map +1 -0
- package/dist/modules/workspace/plugins-migration.js +379 -0
- package/dist/modules/workspace/plugins-migration.js.map +1 -0
- package/dist/modules/workspace/workspace.routes.js +3 -3
- package/dist/modules/workspace/workspace.routes.js.map +1 -1
- package/dist/modules/workspace/workspace.service.d.ts +26 -0
- package/dist/modules/workspace/workspace.service.d.ts.map +1 -1
- package/dist/modules/workspace/workspace.service.js +83 -12
- package/dist/modules/workspace/workspace.service.js.map +1 -1
- package/dist/shared/kb-layout.test.js +3 -3
- package/dist/shared/kb-layout.test.js.map +1 -1
- package/dist/shared/utcp-namespace.d.ts +6 -27
- package/dist/shared/utcp-namespace.d.ts.map +1 -1
- package/dist/shared/utcp-namespace.js +6 -63
- package/dist/shared/utcp-namespace.js.map +1 -1
- package/dist/shared/variable-refs.d.ts +42 -0
- package/dist/shared/variable-refs.d.ts.map +1 -0
- package/dist/shared/variable-refs.js +60 -0
- package/dist/shared/variable-refs.js.map +1 -0
- package/kb-template/.bevelignore +1 -1
- package/kb-template/AGENTS.md +88 -35
- package/kb-template/KnowledgeBase/How to get started.md +10 -10
- package/kb-template/access.md +36 -36
- package/migrations/meta/0000_snapshot.json +1479 -1479
- package/package.json +5 -4
- package/src/assets.ts +25 -25
- package/src/core/core-ports.ts +106 -106
- package/src/core/create-core-server.ts +55 -9
- package/src/core/create-core-services.ts +48 -24
- package/src/index.ts +69 -69
- package/src/modules/access/__tests__/access-control.atref-batch.test.ts +98 -98
- package/src/modules/access/__tests__/access-declarations.test.ts +28 -28
- package/src/modules/access/__tests__/access-md-format.test.ts +18 -18
- package/src/modules/access/__tests__/access-mutation.service.test.ts +5 -5
- package/src/modules/access/__tests__/access-splice.test.ts +2 -2
- package/src/modules/access/__tests__/access.routes.overrides.test.ts +16 -16
- package/src/modules/access/__tests__/grant-sources.test.ts +12 -12
- package/src/modules/access/__tests__/roles-admin.service.test.ts +13 -1
- package/src/modules/access/access-control.interface.ts +8 -7
- package/src/modules/access/access-control.service.ts +7 -7
- package/src/modules/access/access-declarations.ts +5 -5
- package/src/modules/access/access-mutation.service.ts +3 -3
- package/src/modules/access/access-splice.ts +4 -4
- package/src/modules/access/access.routes.ts +20 -20
- package/src/modules/access/creator-access.ts +5 -5
- package/src/modules/access/roles-admin.service.ts +13 -2
- package/src/modules/admin/admin-access.routes.ts +29 -29
- package/src/modules/auth/__tests__/auth.routes.test.ts +91 -91
- package/src/modules/auth/__tests__/rate-limit.test.ts +36 -36
- package/src/modules/auth/rate-limit.ts +45 -45
- package/src/modules/code-mode/__tests__/code-mode.tool.test.ts +67 -0
- package/src/modules/code-mode/code-mode-names.ts +10 -36
- package/src/modules/code-mode/code-mode.tool.ts +27 -7
- package/src/modules/database/connection.ts +15 -15
- package/src/modules/database/schema.ts +11 -11
- package/src/modules/diff/__tests__/diff.routes.rejectPathsLocked.test.ts +150 -150
- package/src/modules/mcp/mcp.service.ts +57 -435
- package/src/modules/{groups → plugins}/__tests__/join-proposals.test.ts +1 -1
- package/src/modules/{groups → plugins}/__tests__/join-requests.service.test.ts +7 -7
- package/src/modules/{groups/__tests__/group-index.service.test.ts → plugins/__tests__/plugin-index.service.test.ts} +41 -41
- package/src/modules/plugins/__tests__/plugin-provision.service.test.ts +312 -0
- package/src/modules/{groups/__tests__/groups.routes.test.ts → plugins/__tests__/plugins.routes.test.ts} +100 -100
- package/src/modules/plugins/index.ts +17 -0
- package/src/modules/{groups → plugins}/join-proposals.ts +2 -2
- package/src/modules/{groups → plugins}/join-requests.service.ts +8 -8
- package/src/modules/{groups/group-provision.service.ts → plugins/plugin-provision.service.ts} +139 -69
- package/src/modules/{groups/groups.contract.ts → plugins/plugins.contract.ts} +26 -26
- package/src/modules/{groups/groups.routes.ts → plugins/plugins.routes.ts} +102 -102
- package/src/modules/{groups/groups.service.ts → plugins/plugins.service.ts} +43 -43
- package/src/modules/secrets-vault/__tests__/tool-owner-gate.route.test.ts +143 -143
- package/src/modules/skills/__tests__/pending-skills.service.test.ts +14 -14
- package/src/modules/skills/__tests__/skills.service.test.ts +13 -13
- package/src/modules/skills/pending-skills.service.ts +7 -7
- package/src/modules/skills/skills.contract.ts +4 -4
- package/src/modules/skills/skills.service.ts +5 -5
- package/src/modules/tool-auth/llm-usage-meter.ts +19 -19
- package/src/modules/tool-manuals/__tests__/mcp-json-discovery.test.ts +198 -0
- package/src/modules/tool-manuals/__tests__/mcp-server-edit.service.test.ts +346 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.archive.route.test.ts +101 -0
- package/src/modules/tool-manuals/__tests__/tool-manuals.detail.route.test.ts +3 -3
- package/src/modules/tool-manuals/__tests__/tool-manuals.mcp-oauth.test.ts +2 -2
- package/src/modules/tool-manuals/__tests__/tool-manuals.service.test.ts +27 -27
- package/src/modules/tool-manuals/__tests__/tool-manuals.tools.test.ts +3 -3
- package/src/modules/tool-manuals/mcp-json-discovery.ts +328 -0
- package/src/modules/tool-manuals/mcp-server-edit.service.ts +434 -0
- package/src/modules/tool-manuals/tool-manuals.contract.ts +35 -12
- package/src/modules/tool-manuals/tool-manuals.routes.ts +222 -1
- package/src/modules/tool-manuals/tool-manuals.service.ts +82 -42
- package/src/modules/tool-manuals/tool-manuals.tools.ts +6 -3
- package/src/modules/workflow/__tests__/pending-commits.worker.test.ts +118 -0
- package/src/modules/workflow/__tests__/workflow.service.facade.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/branch-name.test.ts +3 -3
- package/src/modules/workflow/git/__tests__/git.service.accessGating.test.ts +3 -3
- package/src/modules/workflow/git/__tests__/git.service.commitFile.test.ts +13 -8
- package/src/modules/workflow/git/__tests__/git.service.deleteBranch.test.ts +1 -1
- package/src/modules/workflow/git/__tests__/pull-request.service.test.ts +1 -1
- package/src/modules/workflow/git/git.service.ts +2 -2
- package/src/modules/workflow/pending-commits.service.ts +48 -12
- package/src/modules/workflow/pending-commits.worker.ts +64 -8
- package/src/modules/workflow/review-workflow/__tests__/approval-states.test.ts +1 -1
- package/src/modules/workflow/review-workflow/__tests__/cancel-pr.test.ts +1 -1
- package/src/modules/workflow/workflow-hooks.ts +101 -101
- package/src/modules/workflow/workflow.service.ts +13 -2
- package/src/modules/workspace/__tests__/kb-seed.service.test.ts +81 -13
- package/src/modules/workspace/__tests__/plugins-migration.test.ts +427 -0
- package/src/modules/workspace/__tests__/session-ontology.gate.test.ts +237 -237
- package/src/modules/workspace/__tests__/workspace.routes.create-grant.test.ts +236 -236
- package/src/modules/workspace/__tests__/workspace.routes.delete.test.ts +179 -179
- package/src/modules/workspace/__tests__/workspace.routes.download.test.ts +320 -320
- package/src/modules/workspace/__tests__/workspace.routes.read-gate.test.ts +337 -337
- package/src/modules/workspace/__tests__/workspace.service.test.ts +116 -0
- package/src/modules/workspace/bevel-ignore.ts +66 -66
- package/src/modules/workspace/kb-seed.service.ts +38 -9
- package/src/modules/workspace/plugins-migration.ts +479 -0
- package/src/modules/workspace/session-sink.ts +25 -25
- package/src/modules/workspace/workspace.routes.ts +3 -3
- package/src/modules/workspace/workspace.service.ts +85 -14
- package/src/modules/workspace/workspace.tools.ts +922 -922
- package/src/shared/__tests__/join-request.test.ts +13 -13
- package/src/shared/__tests__/kb-layout.plugin.test.ts +45 -0
- package/src/shared/kb-layout.test.ts +3 -3
- package/src/shared/utcp-namespace.ts +10 -68
- package/src/shared/variable-refs.ts +64 -0
- package/src/modules/groups/__tests__/group-provision.service.test.ts +0 -247
- package/src/modules/groups/index.ts +0 -17
- package/src/shared/__tests__/kb-layout.group.test.ts +0 -45
- /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.
|
|
156
|
-
* session
|
|
157
|
-
*
|
|
158
|
-
*
|
|
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
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
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
|
|
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":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,6BAA6B,EAAE,MAAM,oDAAoD,CAAC;
|
|
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 {
|
|
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
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
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
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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.
|
|
594
|
-
* session
|
|
595
|
-
*
|
|
596
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
*
|
|
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
|