@bevel-software/platform-mcp-core 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 -0
- package/THIRD-PARTY-NOTICES.md +3065 -0
- package/dist/code-mode-names.d.ts +40 -0
- package/dist/code-mode-names.d.ts.map +1 -0
- package/dist/code-mode-names.js +93 -0
- package/dist/code-mode-names.js.map +1 -0
- package/dist/dispatch.d.ts +44 -0
- package/dist/dispatch.d.ts.map +1 -0
- package/dist/dispatch.js +72 -0
- package/dist/dispatch.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/meta-tools.d.ts +27 -0
- package/dist/meta-tools.d.ts.map +1 -0
- package/dist/meta-tools.js +161 -0
- package/dist/meta-tools.js.map +1 -0
- package/dist/proxied-tool.d.ts +49 -0
- package/dist/proxied-tool.d.ts.map +1 -0
- package/dist/proxied-tool.js +183 -0
- package/dist/proxied-tool.js.map +1 -0
- package/dist/results.d.ts +19 -0
- package/dist/results.d.ts.map +1 -0
- package/dist/results.js +130 -0
- package/dist/results.js.map +1 -0
- package/dist/skills.d.ts +22 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +20 -0
- package/dist/skills.js.map +1 -0
- package/dist/utcp-namespace.d.ts +30 -0
- package/dist/utcp-namespace.d.ts.map +1 -0
- package/dist/utcp-namespace.js +66 -0
- package/dist/utcp-namespace.js.map +1 -0
- package/package.json +50 -0
- package/src/code-mode-names.ts +107 -0
- package/src/dispatch.ts +88 -0
- package/src/index.ts +71 -0
- package/src/meta-tools.ts +186 -0
- package/src/proxied-tool.ts +200 -0
- package/src/results.ts +131 -0
- package/src/skills.ts +34 -0
- package/src/utcp-namespace.ts +70 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Tool } from '@utcp/sdk';
|
|
2
|
+
import type { CodeModeUtcpClient } from '@utcp/code-mode';
|
|
3
|
+
/**
|
|
4
|
+
* Pure helpers for mapping UTCP tool names to their code-mode (TypeScript)
|
|
5
|
+
* accessible form. Kept Mastra-free so the agent's Mastra tools
|
|
6
|
+
* (`code-mode.tool.ts`), the hosted MCP proxy and the local MCP server can all
|
|
7
|
+
* share them.
|
|
8
|
+
*/
|
|
9
|
+
export declare function sanitizeIdentifier(name: string): string;
|
|
10
|
+
/** `MANUAL.tool` → the `manual.tool` form the code-mode runtime exposes. */
|
|
11
|
+
export declare function utcpNameToTsInterfaceName(utcpName: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* The TYPED half of the ambiguity contract: callers that contain ambiguity
|
|
14
|
+
* per-name while letting real failures (an outage, a broken repository)
|
|
15
|
+
* fail the call need something sturdier to branch on than the message text
|
|
16
|
+
* of a bare Error thrown in another package.
|
|
17
|
+
*/
|
|
18
|
+
export declare class AmbiguousToolNameError extends Error {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a tool by its raw UTCP name or its sanitized TS-accessible name.
|
|
23
|
+
* Throws when the sanitized name is claimed by more than one tool (an exact
|
|
24
|
+
* UTCP name always wins and can't be ambiguous).
|
|
25
|
+
*/
|
|
26
|
+
export declare function findToolByName(client: CodeModeUtcpClient, name: string): Promise<{
|
|
27
|
+
tool: Tool;
|
|
28
|
+
utcpName: string;
|
|
29
|
+
} | null>;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a batch of names against ONE catalog fetch (`tools_info` takes a
|
|
32
|
+
* list, and a per-name `getTools()` re-fetch scales with the list). Names that
|
|
33
|
+
* resolve are in the map; missing ones are simply absent; an ambiguous
|
|
34
|
+
* sanitized name throws, as in {@link findToolByName}.
|
|
35
|
+
*/
|
|
36
|
+
export declare function findToolsByNames(client: CodeModeUtcpClient, names: string[]): Promise<Map<string, {
|
|
37
|
+
tool: Tool;
|
|
38
|
+
utcpName: string;
|
|
39
|
+
}>>;
|
|
40
|
+
//# sourceMappingURL=code-mode-names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-mode-names.d.ts","sourceRoot":"","sources":["../src/code-mode-names.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAE1D;;;;;GAKG;AAEH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,4EAA4E;AAC5E,wBAAgB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMlE;AAkCD;;;;;GAKG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAIlD;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,MAAM,EAAE,GACd,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAexD"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for mapping UTCP tool names to their code-mode (TypeScript)
|
|
3
|
+
* accessible form. Kept Mastra-free so the agent's Mastra tools
|
|
4
|
+
* (`code-mode.tool.ts`), the hosted MCP proxy and the local MCP server can all
|
|
5
|
+
* share them.
|
|
6
|
+
*/
|
|
7
|
+
export function sanitizeIdentifier(name) {
|
|
8
|
+
return name.replace(/[^a-zA-Z0-9_]/g, '_').replace(/^[0-9]/, '_$&');
|
|
9
|
+
}
|
|
10
|
+
/** `MANUAL.tool` → the `manual.tool` form the code-mode runtime exposes. */
|
|
11
|
+
export function utcpNameToTsInterfaceName(utcpName) {
|
|
12
|
+
if (utcpName.includes('.')) {
|
|
13
|
+
const [manualName, ...toolParts] = utcpName.split('.');
|
|
14
|
+
return `${sanitizeIdentifier(manualName)}.${toolParts.map(sanitizeIdentifier).join('_')}`;
|
|
15
|
+
}
|
|
16
|
+
return sanitizeIdentifier(utcpName);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Sanitized TS name → every UTCP tool that claims it, from ONE catalog fetch.
|
|
20
|
+
* Collisions are kept, not collapsed: sanitization is lossy (`a-b` and `a_b`
|
|
21
|
+
* both become `a_b`), and a lookup that silently picked one would describe —
|
|
22
|
+
* or dispatch — the wrong tool.
|
|
23
|
+
*/
|
|
24
|
+
async function tsNameIndex(client) {
|
|
25
|
+
const index = new Map();
|
|
26
|
+
for (const tool of await client.config.tool_repository.getTools()) {
|
|
27
|
+
const tsName = utcpNameToTsInterfaceName(tool.name);
|
|
28
|
+
const bucket = index.get(tsName);
|
|
29
|
+
if (bucket)
|
|
30
|
+
bucket.push(tool);
|
|
31
|
+
else
|
|
32
|
+
index.set(tsName, [tool]);
|
|
33
|
+
}
|
|
34
|
+
return index;
|
|
35
|
+
}
|
|
36
|
+
function resolveSanitized(index, name) {
|
|
37
|
+
const bucket = index.get(name);
|
|
38
|
+
if (!bucket || bucket.length === 0)
|
|
39
|
+
return null;
|
|
40
|
+
if (bucket.length > 1) {
|
|
41
|
+
throw new AmbiguousToolNameError(`Tool name "${name}" is ambiguous: ${bucket.map((t) => `"${t.name}"`).join(', ')} ` +
|
|
42
|
+
'all sanitize to it. Call the tool by its exact UTCP name instead.');
|
|
43
|
+
}
|
|
44
|
+
return { tool: bucket[0], utcpName: bucket[0].name };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The TYPED half of the ambiguity contract: callers that contain ambiguity
|
|
48
|
+
* per-name while letting real failures (an outage, a broken repository)
|
|
49
|
+
* fail the call need something sturdier to branch on than the message text
|
|
50
|
+
* of a bare Error thrown in another package.
|
|
51
|
+
*/
|
|
52
|
+
export class AmbiguousToolNameError extends Error {
|
|
53
|
+
constructor(message) {
|
|
54
|
+
super(message);
|
|
55
|
+
this.name = 'AmbiguousToolNameError';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Resolve a tool by its raw UTCP name or its sanitized TS-accessible name.
|
|
60
|
+
* Throws when the sanitized name is claimed by more than one tool (an exact
|
|
61
|
+
* UTCP name always wins and can't be ambiguous).
|
|
62
|
+
*/
|
|
63
|
+
export async function findToolByName(client, name) {
|
|
64
|
+
const direct = await client.config.tool_repository.getTool(name);
|
|
65
|
+
if (direct)
|
|
66
|
+
return { tool: direct, utcpName: name };
|
|
67
|
+
return resolveSanitized(await tsNameIndex(client), name);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolve a batch of names against ONE catalog fetch (`tools_info` takes a
|
|
71
|
+
* list, and a per-name `getTools()` re-fetch scales with the list). Names that
|
|
72
|
+
* resolve are in the map; missing ones are simply absent; an ambiguous
|
|
73
|
+
* sanitized name throws, as in {@link findToolByName}.
|
|
74
|
+
*/
|
|
75
|
+
export async function findToolsByNames(client, names) {
|
|
76
|
+
const out = new Map();
|
|
77
|
+
let index;
|
|
78
|
+
for (const name of names) {
|
|
79
|
+
if (out.has(name))
|
|
80
|
+
continue;
|
|
81
|
+
const direct = await client.config.tool_repository.getTool(name);
|
|
82
|
+
if (direct) {
|
|
83
|
+
out.set(name, { tool: direct, utcpName: name });
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
index ??= await tsNameIndex(client);
|
|
87
|
+
const resolved = resolveSanitized(index, name);
|
|
88
|
+
if (resolved)
|
|
89
|
+
out.set(name, resolved);
|
|
90
|
+
}
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=code-mode-names.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-mode-names.js","sourceRoot":"","sources":["../src/code-mode-names.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import type { CodeModeUtcpClient } from '@utcp/code-mode';
|
|
3
|
+
import type { CallTemplate } from '@utcp/sdk';
|
|
4
|
+
import type { ProxiedTool } from './proxied-tool.js';
|
|
5
|
+
/**
|
|
6
|
+
* Register one manual on a client, reduced to a verdict.
|
|
7
|
+
*
|
|
8
|
+
* Deliberately NOT a loop over every manual: the two surfaces disagree about
|
|
9
|
+
* what a failure means. The hosted proxy memoizes per (user, manual) so a
|
|
10
|
+
* broken credential doesn't re-dial its provider on every session rebuild; the
|
|
11
|
+
* local server has one user and one process and just logs. Sharing the
|
|
12
|
+
* per-manual mechanics without sharing the retry policy keeps both honest.
|
|
13
|
+
*
|
|
14
|
+
* Never throws: a registration failure is a runtime problem (network, dead
|
|
15
|
+
* credential), not a schema one — the templates were validated before they got
|
|
16
|
+
* here — so it comes back as `{ ok: false }` for the caller to police.
|
|
17
|
+
*/
|
|
18
|
+
export declare function registerManual(client: CodeModeUtcpClient, manual: CallTemplate): Promise<{
|
|
19
|
+
ok: true;
|
|
20
|
+
} | {
|
|
21
|
+
ok: false;
|
|
22
|
+
error: string;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Run one tool call through `callToolStreaming` with a one-chunk lookahead:
|
|
26
|
+
* every chunk except the last becomes a progress notification, the last is the
|
|
27
|
+
* result.
|
|
28
|
+
*
|
|
29
|
+
* Dispatch always uses `callToolStreaming`, which is uniform across tool kinds:
|
|
30
|
+
* a plain `http` tool yields exactly one chunk (its final result, emitted as
|
|
31
|
+
* the tool result with no progress), while a `streamable_http` tool yields many.
|
|
32
|
+
* Because of that, adding — or later upgrading a tool to streaming — never
|
|
33
|
+
* touches this function.
|
|
34
|
+
*
|
|
35
|
+
* Continuity is the caller's: a tool that supports it (e.g. `ask`) returns its
|
|
36
|
+
* `sessionId` in the result verbatim, and the caller echoes it back per the
|
|
37
|
+
* tool's own schema — nothing here rewrites args. Args pass through to UTCP
|
|
38
|
+
* verbatim; each communication protocol does its own serialization (http reads
|
|
39
|
+
* the template's `body_field` out of the args, mcp forwards them untouched as
|
|
40
|
+
* MCP `arguments`), and the advertised schema is the tool's UTCP `inputs`
|
|
41
|
+
* verbatim too, so any reshaping here would be wrong for at least one protocol.
|
|
42
|
+
*/
|
|
43
|
+
export declare function dispatchToolCall(client: CodeModeUtcpClient, tool: ProxiedTool, args: Record<string, unknown>, onProgress?: (progress: number, message: string) => Promise<void>): Promise<CallToolResult>;
|
|
44
|
+
//# sourceMappingURL=dispatch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAGrD;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,kBAAkB,EAC1B,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAWtD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAChE,OAAO,CAAC,cAAc,CAAC,CA4BzB"}
|
package/dist/dispatch.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { toCallToolResult, toolError, describeToolFailure, renderProgress } from './results.js';
|
|
2
|
+
/**
|
|
3
|
+
* Register one manual on a client, reduced to a verdict.
|
|
4
|
+
*
|
|
5
|
+
* Deliberately NOT a loop over every manual: the two surfaces disagree about
|
|
6
|
+
* what a failure means. The hosted proxy memoizes per (user, manual) so a
|
|
7
|
+
* broken credential doesn't re-dial its provider on every session rebuild; the
|
|
8
|
+
* local server has one user and one process and just logs. Sharing the
|
|
9
|
+
* per-manual mechanics without sharing the retry policy keeps both honest.
|
|
10
|
+
*
|
|
11
|
+
* Never throws: a registration failure is a runtime problem (network, dead
|
|
12
|
+
* credential), not a schema one — the templates were validated before they got
|
|
13
|
+
* here — so it comes back as `{ ok: false }` for the caller to police.
|
|
14
|
+
*/
|
|
15
|
+
export async function registerManual(client, manual) {
|
|
16
|
+
try {
|
|
17
|
+
const result = await client.registerManual(manual);
|
|
18
|
+
if (result && result.success === false) {
|
|
19
|
+
const errors = Array.isArray(result.errors) ? result.errors.join('; ') : 'unknown error';
|
|
20
|
+
return { ok: false, error: errors };
|
|
21
|
+
}
|
|
22
|
+
return { ok: true };
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Run one tool call through `callToolStreaming` with a one-chunk lookahead:
|
|
30
|
+
* every chunk except the last becomes a progress notification, the last is the
|
|
31
|
+
* result.
|
|
32
|
+
*
|
|
33
|
+
* Dispatch always uses `callToolStreaming`, which is uniform across tool kinds:
|
|
34
|
+
* a plain `http` tool yields exactly one chunk (its final result, emitted as
|
|
35
|
+
* the tool result with no progress), while a `streamable_http` tool yields many.
|
|
36
|
+
* Because of that, adding — or later upgrading a tool to streaming — never
|
|
37
|
+
* touches this function.
|
|
38
|
+
*
|
|
39
|
+
* Continuity is the caller's: a tool that supports it (e.g. `ask`) returns its
|
|
40
|
+
* `sessionId` in the result verbatim, and the caller echoes it back per the
|
|
41
|
+
* tool's own schema — nothing here rewrites args. Args pass through to UTCP
|
|
42
|
+
* verbatim; each communication protocol does its own serialization (http reads
|
|
43
|
+
* the template's `body_field` out of the args, mcp forwards them untouched as
|
|
44
|
+
* MCP `arguments`), and the advertised schema is the tool's UTCP `inputs`
|
|
45
|
+
* verbatim too, so any reshaping here would be wrong for at least one protocol.
|
|
46
|
+
*/
|
|
47
|
+
export async function dispatchToolCall(client, tool, args, onProgress) {
|
|
48
|
+
let prev;
|
|
49
|
+
let hasPrev = false;
|
|
50
|
+
let progress = 0;
|
|
51
|
+
try {
|
|
52
|
+
for await (const chunk of client.callToolStreaming(tool.utcpName, args)) {
|
|
53
|
+
if (hasPrev && onProgress) {
|
|
54
|
+
progress += 1;
|
|
55
|
+
await onProgress(progress, renderProgress(prev)).catch((err) => console.warn('[mcp] progress notification failed:', err));
|
|
56
|
+
}
|
|
57
|
+
prev = chunk;
|
|
58
|
+
hasPrev = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
return toolError(`The "${tool.mcpName}" tool failed: ${describeToolFailure(err)}`);
|
|
63
|
+
}
|
|
64
|
+
// A stream that closes without ever yielding is a transport fault, not an
|
|
65
|
+
// empty result — every tool kind yields at least its final value (see above).
|
|
66
|
+
// Say so, instead of serializing the never-assigned `prev` into a "null".
|
|
67
|
+
if (!hasPrev) {
|
|
68
|
+
return toolError(`The "${tool.mcpName}" tool produced no output: its stream ended without a result.`);
|
|
69
|
+
}
|
|
70
|
+
return toCallToolResult(prev);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=dispatch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,mBAAmB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEhG;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,MAA0B,EAC1B,MAAoB;IAEpB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QACnD,IAAI,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,KAAK,EAAE,CAAC;YACvC,MAAM,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC;YACzF,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QACtC,CAAC;QACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAChF,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAA0B,EAC1B,IAAiB,EACjB,IAA6B,EAC7B,UAAiE;IAEjE,IAAI,IAAa,CAAC;IAClB,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,IAAI,QAAQ,GAAG,CAAC,CAAC;IAEjB,IAAI,CAAC;QACH,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,MAAM,CAAC,iBAAiB,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,CAAC;YACxE,IAAI,OAAO,IAAI,UAAU,EAAE,CAAC;gBAC1B,QAAQ,IAAI,CAAC,CAAC;gBACd,MAAM,UAAU,CAAC,QAAQ,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAC7D,OAAO,CAAC,IAAI,CAAC,qCAAqC,EAAE,GAAG,CAAC,CACzD,CAAC;YACJ,CAAC;YACD,IAAI,GAAG,KAAK,CAAC;YACb,OAAO,GAAG,IAAI,CAAC;QACjB,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,SAAS,CAAC,QAAQ,IAAI,CAAC,OAAO,kBAAkB,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrF,CAAC;IAED,0EAA0E;IAC1E,8EAA8E;IAC9E,0EAA0E;IAC1E,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,SAAS,CAAC,QAAQ,IAAI,CAAC,OAAO,+DAA+D,CAAC,CAAC;IACxG,CAAC;IAED,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAC;AAChC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bevel-software/platform-mcp-core — the transport-agnostic half of Bevel's
|
|
3
|
+
* MCP surface.
|
|
4
|
+
*
|
|
5
|
+
* Two surfaces re-expose the same UTCP tool catalog over MCP and differ only in
|
|
6
|
+
* where they run and how they reach it:
|
|
7
|
+
*
|
|
8
|
+
* - the HOSTED proxy (`platform-core-backend`) registers the KB manual over
|
|
9
|
+
* loopback plus each `.tool` the caller can read, resolves `${VAR}` from
|
|
10
|
+
* the Secrets Vault, and speaks streamable HTTP;
|
|
11
|
+
* - the LOCAL server (`@bevel-software/hexis-mcp`) registers the deployment's
|
|
12
|
+
* own MCP endpoint as one `mcp` manual plus the `remote: false` manuals the
|
|
13
|
+
* hosted endpoint cannot serve, resolves `${VAR}` from the process env, and
|
|
14
|
+
* speaks stdio.
|
|
15
|
+
*
|
|
16
|
+
* Everything between "a UTCP client with manuals registered" and "an MCP result"
|
|
17
|
+
* is identical, and lives here: name flattening, the tool-name/schema guards
|
|
18
|
+
* that stop one bad tool blanking a client's whole toolset, streaming dispatch,
|
|
19
|
+
* and the code-mode meta-tools.
|
|
20
|
+
*
|
|
21
|
+
* What is NOT here, on purpose: manual DISCOVERY (who may see which manual is
|
|
22
|
+
* an access-control question the hosted REST surface answers), credential
|
|
23
|
+
* resolution (a vault loader server-side, `process.env` locally), and retry
|
|
24
|
+
* policy (see `registerManual`).
|
|
25
|
+
*/
|
|
26
|
+
export { type ProxiedTool, toListedTool, sanitizeInputSchema, flattenManualTool, flattenDiscoveredTool, } from './proxied-tool.js';
|
|
27
|
+
export { describeToolFailure, toCallToolResult, renderProgress, toolError, needsAuthorizationResult, } from './results.js';
|
|
28
|
+
export { CODE_MODE_META_TOOLS, META_TOOL_NAMES, CALL_TOOL_CHAIN_MAX_OUTPUT, type SpillPort, dispatchMetaTool, } from './meta-tools.js';
|
|
29
|
+
export { registerManual, dispatchToolCall } from './dispatch.js';
|
|
30
|
+
export { type SkillSummary, type LoadedSkill, skillPromptText, } from './skills.js';
|
|
31
|
+
export { utcpNamespacePrefix, utcpNamespacedKey, seedBevelHostedManualVars, } from './utcp-namespace.js';
|
|
32
|
+
export { sanitizeIdentifier, utcpNameToTsInterfaceName, findToolByName, findToolsByNames, AmbiguousToolNameError, } from './code-mode-names.js';
|
|
33
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EACL,KAAK,WAAW,EAChB,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,SAAS,EACT,wBAAwB,GACzB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,0BAA0B,EAC1B,KAAK,SAAS,EACd,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,eAAe,GAChB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @bevel-software/platform-mcp-core — the transport-agnostic half of Bevel's
|
|
3
|
+
* MCP surface.
|
|
4
|
+
*
|
|
5
|
+
* Two surfaces re-expose the same UTCP tool catalog over MCP and differ only in
|
|
6
|
+
* where they run and how they reach it:
|
|
7
|
+
*
|
|
8
|
+
* - the HOSTED proxy (`platform-core-backend`) registers the KB manual over
|
|
9
|
+
* loopback plus each `.tool` the caller can read, resolves `${VAR}` from
|
|
10
|
+
* the Secrets Vault, and speaks streamable HTTP;
|
|
11
|
+
* - the LOCAL server (`@bevel-software/hexis-mcp`) registers the deployment's
|
|
12
|
+
* own MCP endpoint as one `mcp` manual plus the `remote: false` manuals the
|
|
13
|
+
* hosted endpoint cannot serve, resolves `${VAR}` from the process env, and
|
|
14
|
+
* speaks stdio.
|
|
15
|
+
*
|
|
16
|
+
* Everything between "a UTCP client with manuals registered" and "an MCP result"
|
|
17
|
+
* is identical, and lives here: name flattening, the tool-name/schema guards
|
|
18
|
+
* that stop one bad tool blanking a client's whole toolset, streaming dispatch,
|
|
19
|
+
* and the code-mode meta-tools.
|
|
20
|
+
*
|
|
21
|
+
* What is NOT here, on purpose: manual DISCOVERY (who may see which manual is
|
|
22
|
+
* an access-control question the hosted REST surface answers), credential
|
|
23
|
+
* resolution (a vault loader server-side, `process.env` locally), and retry
|
|
24
|
+
* policy (see `registerManual`).
|
|
25
|
+
*/
|
|
26
|
+
export { toListedTool, sanitizeInputSchema, flattenManualTool, flattenDiscoveredTool, } from './proxied-tool.js';
|
|
27
|
+
export { describeToolFailure, toCallToolResult, renderProgress, toolError, needsAuthorizationResult, } from './results.js';
|
|
28
|
+
export { CODE_MODE_META_TOOLS, META_TOOL_NAMES, CALL_TOOL_CHAIN_MAX_OUTPUT, dispatchMetaTool, } from './meta-tools.js';
|
|
29
|
+
export { registerManual, dispatchToolCall } from './dispatch.js';
|
|
30
|
+
export { skillPromptText, } from './skills.js';
|
|
31
|
+
export { utcpNamespacePrefix, utcpNamespacedKey, seedBevelHostedManualVars, } from './utcp-namespace.js';
|
|
32
|
+
export { sanitizeIdentifier, utcpNameToTsInterfaceName, findToolByName, findToolsByNames, AmbiguousToolNameError, } from './code-mode-names.js';
|
|
33
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAEL,YAAY,EACZ,mBAAmB,EACnB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,cAAc,EACd,SAAS,EACT,wBAAwB,GACzB,MAAM,cAAc,CAAC;AAEtB,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,0BAA0B,EAE1B,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEjE,OAAO,EAGL,eAAe,GAChB,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,yBAAyB,GAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EACL,kBAAkB,EAClB,yBAAyB,EACzB,cAAc,EACd,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Tool as McpTool, CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import type { CodeModeUtcpClient } from '@utcp/code-mode';
|
|
3
|
+
export declare const CODE_MODE_META_TOOLS: McpTool[];
|
|
4
|
+
export declare const META_TOOL_NAMES: ReadonlySet<string>;
|
|
5
|
+
/** Default cap on a `call_tool_chain` result's stringified size before it spills. */
|
|
6
|
+
export declare const CALL_TOOL_CHAIN_MAX_OUTPUT = 200000;
|
|
7
|
+
/**
|
|
8
|
+
* Where an oversized `call_tool_chain` payload goes. The hosted proxy hands in
|
|
9
|
+
* the shared workspace spill store, whose refs `read_file` can read back. A
|
|
10
|
+
* surface with nowhere to put it (the local server has no server-side store of
|
|
11
|
+
* its own) passes nothing and gets a truncation notice instead — the caller is
|
|
12
|
+
* told to narrow the chain rather than handed a ref that resolves nowhere.
|
|
13
|
+
*/
|
|
14
|
+
export interface SpillPort {
|
|
15
|
+
write(json: string): Promise<{
|
|
16
|
+
ref: string;
|
|
17
|
+
bytes: number;
|
|
18
|
+
}>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Handle a code-mode meta-tool. `list_tools`/`tools_info` reflect on the
|
|
22
|
+
* client's discovered catalog; `call_tool_chain` runs the caller's JavaScript in
|
|
23
|
+
* the client's isolated-vm, where every registered tool is reachable as
|
|
24
|
+
* `<manual>.tool(...)`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function dispatchMetaTool(client: CodeModeUtcpClient, name: string, args: Record<string, unknown>, spill?: SpillPort): Promise<CallToolResult>;
|
|
27
|
+
//# sourceMappingURL=meta-tools.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-tools.d.ts","sourceRoot":"","sources":["../src/meta-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,IAAI,OAAO,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAC1F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AA4B1D,eAAO,MAAM,oBAAoB,EAAE,OAAO,EAkCzC,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,WAAW,CAAC,MAAM,CAAoD,CAAC;AAErG,qFAAqF;AACrF,eAAO,MAAM,0BAA0B,SAAU,CAAC;AAgBlD;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC9D;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,kBAAkB,EAC1B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,KAAK,CAAC,EAAE,SAAS,GAChB,OAAO,CAAC,cAAc,CAAC,CA+EzB"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { utcpNameToTsInterfaceName, findToolsByNames } from './code-mode-names.js';
|
|
2
|
+
import { toCallToolResult, toolError, describeToolFailure } from './results.js';
|
|
3
|
+
/**
|
|
4
|
+
* Code-mode meta-tools exposed ALONGSIDE the direct tools. They let an external
|
|
5
|
+
* agent batch many Bevel calls into one isolated-vm run (`call_tool_chain`)
|
|
6
|
+
* instead of one MCP round-trip per call — the same efficiency our own agent
|
|
7
|
+
* gets. `call_tool_chain`'s description carries the code-mode protocol (there is
|
|
8
|
+
* no system prompt over MCP), so the client learns the convention from the tool
|
|
9
|
+
* itself; `list_tools`/`tools_info` are how it discovers what to call.
|
|
10
|
+
*
|
|
11
|
+
* Security is identical to the direct surface: the chain runs in an isolated-vm
|
|
12
|
+
* but calls tools with the CALLER's credentials against the external catalog —
|
|
13
|
+
* internal-only tools aren't in that catalog, so a chain can't reach them either.
|
|
14
|
+
*
|
|
15
|
+
* These three belong to whichever client holds the registry. A surface that
|
|
16
|
+
* registers ANOTHER Bevel MCP endpoint as one of its manuals therefore has to
|
|
17
|
+
* drop that endpoint's copies from the passthrough (see {@link META_TOOL_NAMES}):
|
|
18
|
+
* the remote trio describes the remote registry, and locally they must describe
|
|
19
|
+
* the merged one.
|
|
20
|
+
*/
|
|
21
|
+
const CALL_TOOL_CHAIN_DESCRIPTION = [
|
|
22
|
+
'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).',
|
|
23
|
+
'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.',
|
|
24
|
+
'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.',
|
|
25
|
+
].join('\n\n');
|
|
26
|
+
export const CODE_MODE_META_TOOLS = [
|
|
27
|
+
{
|
|
28
|
+
name: 'list_tools',
|
|
29
|
+
description: 'List every UTCP tool currently registered, in TypeScript-accessible form (e.g. `KNOWLEDGE_BASE.read_file`) for use inside `call_tool_chain`.',
|
|
30
|
+
inputSchema: { type: 'object', properties: {}, additionalProperties: false },
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'tools_info',
|
|
34
|
+
description: 'Get full TypeScript interface definitions for named tools (names from `list_tools`). The schemas are the source of truth — do not guess shapes.',
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: 'object',
|
|
37
|
+
properties: {
|
|
38
|
+
tool_names: { type: 'array', items: { type: 'string' }, minItems: 1, description: 'Tool names to describe.' },
|
|
39
|
+
},
|
|
40
|
+
required: ['tool_names'],
|
|
41
|
+
additionalProperties: false,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'call_tool_chain',
|
|
46
|
+
description: CALL_TOOL_CHAIN_DESCRIPTION,
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
code: { type: 'string', minLength: 1, description: 'JavaScript to execute against the registered tools.' },
|
|
51
|
+
timeout: { type: 'integer', minimum: 1000, maximum: 120000, description: 'Timeout in ms (default 30000).' },
|
|
52
|
+
max_output_size: { type: 'integer', minimum: 1000, maximum: 1000000, description: 'Max result+logs size in chars before spilling (default 200000, max 1000000).' },
|
|
53
|
+
},
|
|
54
|
+
required: ['code'],
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
export const META_TOOL_NAMES = new Set(CODE_MODE_META_TOOLS.map((t) => t.name));
|
|
60
|
+
/** Default cap on a `call_tool_chain` result's stringified size before it spills. */
|
|
61
|
+
export const CALL_TOOL_CHAIN_MAX_OUTPUT = 200_000;
|
|
62
|
+
/**
|
|
63
|
+
* UTF-8 byte length without Node's `Buffer` — this module stays free of
|
|
64
|
+
* runtime-specific globals. Matches `Buffer.byteLength`: a lone surrogate
|
|
65
|
+
* encodes as the 3-byte replacement character.
|
|
66
|
+
*/
|
|
67
|
+
function utf8ByteLength(s) {
|
|
68
|
+
let bytes = 0;
|
|
69
|
+
for (const ch of s) {
|
|
70
|
+
const cp = ch.codePointAt(0);
|
|
71
|
+
bytes += cp <= 0x7f ? 1 : cp <= 0x7ff ? 2 : cp <= 0xffff ? 3 : 4;
|
|
72
|
+
}
|
|
73
|
+
return bytes;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Handle a code-mode meta-tool. `list_tools`/`tools_info` reflect on the
|
|
77
|
+
* client's discovered catalog; `call_tool_chain` runs the caller's JavaScript in
|
|
78
|
+
* the client's isolated-vm, where every registered tool is reachable as
|
|
79
|
+
* `<manual>.tool(...)`.
|
|
80
|
+
*/
|
|
81
|
+
export async function dispatchMetaTool(client, name, args, spill) {
|
|
82
|
+
try {
|
|
83
|
+
if (name === 'list_tools') {
|
|
84
|
+
const tools = await client.config.tool_repository.getTools();
|
|
85
|
+
return toCallToolResult({ tools: tools.map((t) => utcpNameToTsInterfaceName(t.name)) });
|
|
86
|
+
}
|
|
87
|
+
if (name === 'tools_info') {
|
|
88
|
+
// The schema is advisory over a raw JSON-RPC call: a missing array or a
|
|
89
|
+
// non-string entry must be a named validation error here, not a generic
|
|
90
|
+
// failure out of a repository lookup it was never valid input for.
|
|
91
|
+
const rawNames = args.tool_names;
|
|
92
|
+
// Empty included — the schema says minItems 1, and an empty success
|
|
93
|
+
// payload for invalid input would read as "no tools exist".
|
|
94
|
+
if (!Array.isArray(rawNames) || rawNames.length === 0 || rawNames.some((n) => typeof n !== 'string')) {
|
|
95
|
+
return toolError('The "tools_info" tool requires "tool_names": a non-empty array of tool name strings.');
|
|
96
|
+
}
|
|
97
|
+
const names = rawNames;
|
|
98
|
+
const interfaces = [];
|
|
99
|
+
const notFound = [];
|
|
100
|
+
const resolved = await findToolsByNames(client, names);
|
|
101
|
+
for (const n of names) {
|
|
102
|
+
const found = resolved.get(n);
|
|
103
|
+
if (found)
|
|
104
|
+
interfaces.push(client.toolToTypeScriptInterface(found.tool));
|
|
105
|
+
else
|
|
106
|
+
notFound.push(n);
|
|
107
|
+
}
|
|
108
|
+
return toCallToolResult({ interfaces: interfaces.join('\n\n'), not_found: notFound });
|
|
109
|
+
}
|
|
110
|
+
// call_tool_chain
|
|
111
|
+
// Same advisory-schema rule as above: a missing or non-string `code` must
|
|
112
|
+
// not silently execute an empty program and report success.
|
|
113
|
+
const code = args.code;
|
|
114
|
+
if (typeof code !== 'string' || code.length === 0) {
|
|
115
|
+
return toolError('The "call_tool_chain" tool requires a non-empty "code" string.');
|
|
116
|
+
}
|
|
117
|
+
// Clamp both knobs to their schema bounds — the schema is advisory over a
|
|
118
|
+
// raw JSON-RPC call, and an unclamped `timeout` would let one chain hold
|
|
119
|
+
// the isolate far past the documented 120s cap.
|
|
120
|
+
const timeout = typeof args.timeout === 'number' && Number.isFinite(args.timeout)
|
|
121
|
+
? Math.min(120_000, Math.max(1_000, Math.trunc(args.timeout)))
|
|
122
|
+
: 30_000;
|
|
123
|
+
// Clamp to [1000, 1_000_000] so a caller can't force oversized inline
|
|
124
|
+
// output past the spill.
|
|
125
|
+
const maxOutputSize = typeof args.max_output_size === 'number' && Number.isFinite(args.max_output_size)
|
|
126
|
+
? Math.min(1_000_000, Math.max(1_000, Math.trunc(args.max_output_size)))
|
|
127
|
+
: CALL_TOOL_CHAIN_MAX_OUTPUT;
|
|
128
|
+
const { result, logs } = await client.callToolChain(code, timeout);
|
|
129
|
+
// Bound the payload: an external session has no ambient workspace, so an
|
|
130
|
+
// oversized result spills to the shared store and we return only a ref —
|
|
131
|
+
// parity with the in-process agent's `call_tool_chain`.
|
|
132
|
+
if (JSON.stringify({ success: true, result, logs }).length <= maxOutputSize) {
|
|
133
|
+
return toCallToolResult({ success: true, result, logs });
|
|
134
|
+
}
|
|
135
|
+
const fullJson = JSON.stringify({ result, logs }, null, 2);
|
|
136
|
+
if (!spill) {
|
|
137
|
+
return toCallToolResult({
|
|
138
|
+
success: true,
|
|
139
|
+
truncated: true,
|
|
140
|
+
// Bytes, not chars: the spill branch reports the store's byte count,
|
|
141
|
+
// and `result_bytes` must mean one thing across both paths.
|
|
142
|
+
result_bytes: utf8ByteLength(fullJson),
|
|
143
|
+
message: `Result+logs payload was ${fullJson.length} characters (exceeded max_output_size of ${maxOutputSize}), ` +
|
|
144
|
+
'and this server has no spill store to park it in. Re-run a narrower chain that returns only what you ' +
|
|
145
|
+
'need, or raise max_output_size.',
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const { ref, bytes } = await spill.write(fullJson);
|
|
149
|
+
return toCallToolResult({
|
|
150
|
+
success: true,
|
|
151
|
+
truncated: true,
|
|
152
|
+
result_ref: ref,
|
|
153
|
+
result_bytes: bytes,
|
|
154
|
+
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.`,
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
catch (err) {
|
|
158
|
+
return toolError(`The "${name}" tool failed: ${describeToolFailure(err)}`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=meta-tools.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meta-tools.js","sourceRoot":"","sources":["../src/meta-tools.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Tool as McpTool } from '@modelcontextprotocol/sdk/types.js';
|
|
2
|
+
import type { JsonSchema, Tool as UtcpTool } from '@utcp/sdk';
|
|
3
|
+
/** A tool discovered from a UTCP manual, flattened into what an MCP surface advertises. */
|
|
4
|
+
export interface ProxiedTool {
|
|
5
|
+
utcpName: string;
|
|
6
|
+
mcpName: string;
|
|
7
|
+
description: string;
|
|
8
|
+
inputSchema: JsonSchema;
|
|
9
|
+
/** The UTCP manual this tool came from (the `<manual>` in `<manual>.<tool>`),
|
|
10
|
+
* used to look up the manual's declared per-user credentials before dispatch. */
|
|
11
|
+
manualName: string;
|
|
12
|
+
}
|
|
13
|
+
/** A discovered tool as an MCP listing entry, or null if its name can't be listed. */
|
|
14
|
+
export declare function toListedTool(tool: ProxiedTool): McpTool | null;
|
|
15
|
+
/**
|
|
16
|
+
* Make a remote server's JSON Schema safe for the Anthropic tool validator:
|
|
17
|
+
* - inline local `$ref` pointers (`#/$defs/...`, `#/definitions/...`) and drop
|
|
18
|
+
* the now-unreferenced `$defs`/`definitions` blocks (the API restricts `$ref`
|
|
19
|
+
* and MCP clients converting our schemas reject it outright);
|
|
20
|
+
* - drop non-standard `format` values (OpenAPI's `int32`/`byte`/… — only the
|
|
21
|
+
* JSON-Schema-standard formats above are accepted; `format` is advisory, so
|
|
22
|
+
* dropping it doesn't change tool behavior).
|
|
23
|
+
* Depth-bounded so a recursive schema degrades to a permissive `{}` node instead
|
|
24
|
+
* of hanging or emitting the unsupported recursion; non-local/external refs
|
|
25
|
+
* degrade the same way. Exported for direct testing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sanitizeInputSchema(schema: unknown): unknown;
|
|
28
|
+
/**
|
|
29
|
+
* Flatten a discovered UTCP tool (`<manual>.<tool>`) into the advertised shape
|
|
30
|
+
* when MULTIPLE manuals are registered. Tools from `kbManualName` keep their
|
|
31
|
+
* bare name (so existing agents still call `read_file`); every other manual's
|
|
32
|
+
* tool is namespaced as `<manual>_<tool>` to guarantee a unique, dot-free MCP
|
|
33
|
+
* name.
|
|
34
|
+
*
|
|
35
|
+
* `kbManualName` is a parameter rather than a constant because the two surfaces
|
|
36
|
+
* that call this reach the KB through different manuals: the hosted proxy
|
|
37
|
+
* registers it over loopback, the local server registers the deployment's MCP
|
|
38
|
+
* endpoint. Whichever manual carries the core toolset is the one whose names
|
|
39
|
+
* must stay bare.
|
|
40
|
+
*/
|
|
41
|
+
export declare function flattenManualTool(tool: UtcpTool, kbManualName: string): ProxiedTool;
|
|
42
|
+
/**
|
|
43
|
+
* Flatten one discovered UTCP tool into the advertised shape: strip the
|
|
44
|
+
* `<manual>.` namespace prefix for the MCP name and keep the UTCP input schema
|
|
45
|
+
* verbatim (Bevel-hosted HTTP tools show their `{body}` envelope, exactly as in
|
|
46
|
+
* `call_tool_chain`).
|
|
47
|
+
*/
|
|
48
|
+
export declare function flattenDiscoveredTool(prefix: string, tool: UtcpTool): ProxiedTool;
|
|
49
|
+
//# sourceMappingURL=proxied-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxied-tool.d.ts","sourceRoot":"","sources":["../src/proxied-tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,IAAI,OAAO,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,IAAI,IAAI,QAAQ,EAAE,MAAM,WAAW,CAAC;AAE9D,2FAA2F;AAC3F,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB;qFACiF;IACjF,UAAU,EAAE,MAAM,CAAC;CACpB;AAoBD,sFAAsF;AACtF,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,GAAG,OAAO,GAAG,IAAI,CA4C9D;AAgBD;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,OAAO,GAAG,OAAO,CAkD5D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,GAAG,WAAW,CAYnF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,WAAW,CASjF"}
|