bare-agent 0.10.4 → 0.12.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/bin/cli.d.ts +4 -0
- package/bin/cli.js +70 -12
- package/bin/test-provider.d.ts +2 -0
- package/bin/test-provider.js +5 -1
- package/index.d.ts +20 -0
- package/package.json +44 -10
- package/src/bareguard-adapter.d.ts +118 -0
- package/src/bareguard-adapter.js +75 -3
- package/src/checkpoint.d.ts +61 -0
- package/src/checkpoint.js +17 -8
- package/src/circuit-breaker.d.ts +70 -0
- package/src/circuit-breaker.js +20 -4
- package/src/errors.d.ts +106 -0
- package/src/errors.js +50 -1
- package/src/loop.d.ts +135 -0
- package/src/loop.js +80 -18
- package/src/mcp-bridge.d.ts +133 -0
- package/src/mcp-bridge.js +199 -26
- package/src/mcp.d.ts +4 -0
- package/src/memory.d.ts +50 -0
- package/src/memory.js +22 -2
- package/src/planner.d.ts +62 -0
- package/src/planner.js +26 -7
- package/src/provider-anthropic.d.ts +55 -0
- package/src/provider-anthropic.js +34 -10
- package/src/provider-clipipe.d.ts +86 -0
- package/src/provider-clipipe.js +28 -18
- package/src/provider-fallback.d.ts +44 -0
- package/src/provider-fallback.js +18 -8
- package/src/provider-ollama.d.ts +41 -0
- package/src/provider-ollama.js +29 -7
- package/src/provider-openai.d.ts +57 -0
- package/src/provider-openai.js +34 -7
- package/src/providers.d.ts +6 -0
- package/src/retry.d.ts +44 -0
- package/src/retry.js +15 -1
- package/src/run-plan.d.ts +126 -0
- package/src/run-plan.js +46 -13
- package/src/scheduler.d.ts +102 -0
- package/src/scheduler.js +32 -4
- package/src/state.d.ts +45 -0
- package/src/state.js +18 -2
- package/src/store-jsonfile.d.ts +85 -0
- package/src/store-jsonfile.js +50 -8
- package/src/store-sqlite.d.ts +90 -0
- package/src/store-sqlite.js +31 -7
- package/src/stores.d.ts +3 -0
- package/src/stream.d.ts +79 -0
- package/src/stream.js +32 -0
- package/src/tools.d.ts +8 -0
- package/src/transport-jsonl.d.ts +30 -0
- package/src/transport-jsonl.js +13 -0
- package/src/transports.d.ts +2 -0
- package/tools/browse.d.ts +10 -0
- package/tools/browse.js +2 -0
- package/tools/defer.d.ts +33 -0
- package/tools/defer.js +12 -3
- package/tools/mobile.d.ts +34 -0
- package/tools/mobile.js +28 -15
- package/tools/shell.d.ts +31 -0
- package/tools/shell.js +83 -6
- package/tools/spawn.d.ts +107 -0
- package/tools/spawn.js +24 -5
- package/types/index.d.ts +66 -0
- package/types/shims.d.ts +16 -0
package/src/loop.js
CHANGED
|
@@ -2,8 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
const { ToolError, HaltError } = require('./errors');
|
|
4
4
|
|
|
5
|
+
/** @typedef {import('../types').Provider} Provider */
|
|
6
|
+
/** @typedef {import('../types').Message} Message */
|
|
7
|
+
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
8
|
+
/** @typedef {import('../types').ToolCall} ToolCall */
|
|
9
|
+
/** @typedef {import('../types').Usage} Usage */
|
|
10
|
+
/** @typedef {import('../types').GenerateResult} GenerateResult */
|
|
11
|
+
/** @typedef {import('../types').Store} Store */
|
|
12
|
+
/** @typedef {import('./checkpoint').Checkpoint} Checkpoint */
|
|
13
|
+
/** @typedef {import('./retry').Retry} Retry */
|
|
14
|
+
/** @typedef {import('./stream').Stream} Stream */
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {object} LoopOptions
|
|
18
|
+
* @property {Provider} provider
|
|
19
|
+
* @property {string} [system]
|
|
20
|
+
* @property {Checkpoint} [checkpoint]
|
|
21
|
+
* @property {Retry} [retry]
|
|
22
|
+
* @property {Stream} [stream]
|
|
23
|
+
* @property {Store} [store]
|
|
24
|
+
* @property {Function} [onToolCall]
|
|
25
|
+
* @property {Function} [onText]
|
|
26
|
+
* @property {Function} [onError]
|
|
27
|
+
* @property {boolean} [throwOnError]
|
|
28
|
+
* @property {Function} [policy]
|
|
29
|
+
* @property {Function} [onLlmResult]
|
|
30
|
+
* @property {Function} [onToolResult]
|
|
31
|
+
* @property {number} [maxRounds] - Removed in v0.8; presence throws a migration error.
|
|
32
|
+
*/
|
|
33
|
+
|
|
5
34
|
// Average pricing per 1K tokens (USD). Adjust these to match your provider's rates.
|
|
6
35
|
// Last updated: 2026-05-18. Source: public provider pricing pages.
|
|
36
|
+
/** @type {Record<string, {in: number, out: number}>} */
|
|
7
37
|
const COST_PER_1K = {
|
|
8
38
|
// OpenAI
|
|
9
39
|
'gpt-4o': { in: 0.0025, out: 0.01 },
|
|
@@ -33,6 +63,10 @@ const HARD_ROUND_LIMIT = 100;
|
|
|
33
63
|
// synthetic `role:'tool'` reply for every tool_call_id that has no matching
|
|
34
64
|
// reply. Halt-path only — keeps msgs a valid OpenAI transcript when the loop
|
|
35
65
|
// exits between pushing assistant.tool_calls and finishing the per-tool loop.
|
|
66
|
+
/**
|
|
67
|
+
* @param {Message[]} msgs
|
|
68
|
+
* @param {string} rule
|
|
69
|
+
*/
|
|
36
70
|
function sealDanglingToolCalls(msgs, rule) {
|
|
37
71
|
for (let i = msgs.length - 1; i >= 0; i--) {
|
|
38
72
|
const m = msgs[i];
|
|
@@ -50,6 +84,11 @@ function sealDanglingToolCalls(msgs, rule) {
|
|
|
50
84
|
}
|
|
51
85
|
}
|
|
52
86
|
|
|
87
|
+
/**
|
|
88
|
+
* @param {string|null} model
|
|
89
|
+
* @param {Usage|null} usage
|
|
90
|
+
* @returns {number|null}
|
|
91
|
+
*/
|
|
53
92
|
function estimateCost(model, usage) {
|
|
54
93
|
if (!usage || !model) return null;
|
|
55
94
|
const rates = COST_PER_1K[model] || COST_PER_1K['_default'];
|
|
@@ -61,19 +100,15 @@ function estimateCost(model, usage) {
|
|
|
61
100
|
|
|
62
101
|
class Loop {
|
|
63
102
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* @param {
|
|
70
|
-
* @param {object} [options.store] - Store instance for validate() health check.
|
|
71
|
-
* @param {Function} [options.policy] - Async (toolName, args, ctx) => true | string. Recommended wiring: closure that delegates to a bareguard Gate (`require('bare-agent/bareguard').wireGate(gate).policy`). Anything other than `true` denies; a string is fed to the LLM verbatim as the deny reason. A throw of `HaltError` exits the loop cleanly. All policy/budget/audit decisions live in bareguard — Loop just calls the closure and respects the verdict.
|
|
72
|
-
* @param {Function} [options.onLlmResult] - Async ({model, provider, usage, costUsd, durationMs, ctx}) called after every successful provider.generate. Wire via `wireGate(gate).onLlmResult` so `budget.maxCostUsd` covers token-only workloads. Errors route through `_reportError` but never kill the loop.
|
|
73
|
-
* @param {Function} [options.onToolResult] - Async ({name, args, result, error, durationMs, ctx}) called after every tool.execute (success and failure). Wire via `wireGate(gate).onToolResult` so `gate.record` sees `ctx`. Errors route through `_reportError` but never kill the loop.
|
|
103
|
+
* `policy` is async `(toolName, args, ctx) => true | string`. Recommended wiring: a closure
|
|
104
|
+
* that delegates to a bareguard Gate (`require('bare-agent/bareguard').wireGate(gate).policy`).
|
|
105
|
+
* Anything other than `true` denies; a string is fed to the LLM verbatim as the deny reason.
|
|
106
|
+
* A throw of `HaltError` exits the loop cleanly. `onLlmResult`/`onToolResult` forward usage and
|
|
107
|
+
* tool outcomes to `gate.record` (via wireGate) and never kill the loop on error.
|
|
108
|
+
* @param {LoopOptions} options
|
|
74
109
|
* @throws {Error} `[Loop] requires a provider` — when options.provider is missing.
|
|
75
110
|
*/
|
|
76
|
-
constructor(options = {}) {
|
|
111
|
+
constructor(options = /** @type {LoopOptions} */ ({})) {
|
|
77
112
|
if (!options.provider) throw new Error('[Loop] requires a provider');
|
|
78
113
|
if (options.maxRounds !== undefined) {
|
|
79
114
|
throw new Error(
|
|
@@ -106,12 +141,18 @@ class Loop {
|
|
|
106
141
|
this.onLlmResult = options.onLlmResult || null;
|
|
107
142
|
this.onToolResult = options.onToolResult || null;
|
|
108
143
|
this._stopped = false;
|
|
144
|
+
/** @type {Message[]} */
|
|
109
145
|
this._history = []; // for chat() stateful mode
|
|
110
146
|
}
|
|
111
147
|
|
|
112
148
|
// Unified error emitter — every silent-ish failure path routes through here so
|
|
113
149
|
// operators see callback throws, checkpoint timeouts, stream listener errors
|
|
114
150
|
// in one place: loop:error stream event + onError callback.
|
|
151
|
+
/**
|
|
152
|
+
* @param {string} source
|
|
153
|
+
* @param {any} err
|
|
154
|
+
* @param {Record<string, any>} [extra]
|
|
155
|
+
*/
|
|
115
156
|
_reportError(source, err, extra = {}) {
|
|
116
157
|
const message = err?.message || String(err);
|
|
117
158
|
this._safeEmit({ type: 'loop:error', data: { source, error: message, ...extra } });
|
|
@@ -125,6 +166,7 @@ class Loop {
|
|
|
125
166
|
}
|
|
126
167
|
|
|
127
168
|
// Swallow-proof stream emit: a throwing listener must not corrupt Loop state.
|
|
169
|
+
/** @param {{type: string, data?: any, ts?: string}} event */
|
|
128
170
|
_safeEmit(event) {
|
|
129
171
|
if (!this.stream) return;
|
|
130
172
|
try {
|
|
@@ -138,6 +180,11 @@ class Loop {
|
|
|
138
180
|
}
|
|
139
181
|
|
|
140
182
|
// Fire a user callback without letting its throw kill the loop.
|
|
183
|
+
/**
|
|
184
|
+
* @param {string} name
|
|
185
|
+
* @param {Function|null} fn
|
|
186
|
+
* @param {...any} args
|
|
187
|
+
*/
|
|
141
188
|
_safeCall(name, fn, ...args) {
|
|
142
189
|
if (!fn) return;
|
|
143
190
|
try {
|
|
@@ -149,10 +196,10 @@ class Loop {
|
|
|
149
196
|
|
|
150
197
|
/**
|
|
151
198
|
* Run the think/act/observe loop.
|
|
152
|
-
* @param {
|
|
153
|
-
* @param {
|
|
154
|
-
* @param {
|
|
155
|
-
* @returns {Promise<{text: string, toolCalls:
|
|
199
|
+
* @param {Message[]} messages - Conversation messages in OpenAI format.
|
|
200
|
+
* @param {ToolDef[]} [tools=[]] - Tool definitions with name, execute, description, parameters.
|
|
201
|
+
* @param {Record<string, any>} [options={}] - Per-run overrides (system, temperature, ctx, etc.).
|
|
202
|
+
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, msgs: Message[]}>}
|
|
156
203
|
* On halt the returned `error` is `halt:<rule>` (or `halt:unknown` if the
|
|
157
204
|
* thrown HaltError carried no `rule`), and `msgs` is sanitized so any
|
|
158
205
|
* dangling assistant `tool_calls` from the halted round are paired with
|
|
@@ -244,7 +291,7 @@ class Loop {
|
|
|
244
291
|
msgs.push({
|
|
245
292
|
role: 'assistant',
|
|
246
293
|
content: result.text || null,
|
|
247
|
-
tool_calls: result.toolCalls.map(tc => ({
|
|
294
|
+
tool_calls: result.toolCalls.map((/** @type {ToolCall} */ tc) => ({
|
|
248
295
|
id: tc.id,
|
|
249
296
|
type: 'function',
|
|
250
297
|
function: { name: tc.name, arguments: JSON.stringify(tc.arguments) },
|
|
@@ -279,7 +326,13 @@ class Loop {
|
|
|
279
326
|
continue;
|
|
280
327
|
}
|
|
281
328
|
this._safeEmit({ type: 'checkpoint:reply', data: { reply } });
|
|
282
|
-
|
|
329
|
+
// Fail-closed: approve ONLY on an explicit affirmative. Any other reply —
|
|
330
|
+
// an unrecognized string ("denied", "wait"), empty, or a non-string — denies.
|
|
331
|
+
// A human approval gate must never approve on ambiguous input, and reading
|
|
332
|
+
// .toLowerCase() off a non-string here used to throw out of run().
|
|
333
|
+
const approved = typeof reply === 'string'
|
|
334
|
+
&& ['yes', 'y', 'approve', 'approved'].includes(reply.trim().toLowerCase());
|
|
335
|
+
if (!approved) {
|
|
283
336
|
msgs.push({ role: 'tool', tool_call_id: tc.id, content: 'User denied this action.' });
|
|
284
337
|
continue;
|
|
285
338
|
}
|
|
@@ -375,11 +428,12 @@ class Loop {
|
|
|
375
428
|
|
|
376
429
|
/**
|
|
377
430
|
* Health check — validates provider, store, and tools without throwing.
|
|
378
|
-
* @param {
|
|
431
|
+
* @param {ToolDef[]} [tools=[]] - Tool definitions to validate.
|
|
379
432
|
* @returns {Promise<{provider: {ok: boolean, error?: string}, store: {ok: boolean, error?: string, skipped: boolean}, tools: {ok: boolean, errors?: string[]}}>}
|
|
380
433
|
* Never throws — all failures captured in return value.
|
|
381
434
|
*/
|
|
382
435
|
async validate(tools = []) {
|
|
436
|
+
/** @type {{provider: {ok: boolean, error?: string}, store: {ok: boolean, error?: string, skipped: boolean}, tools: {ok: boolean, errors?: string[]}}} */
|
|
383
437
|
const result = {
|
|
384
438
|
provider: { ok: false },
|
|
385
439
|
store: { ok: false, skipped: false },
|
|
@@ -415,6 +469,7 @@ class Loop {
|
|
|
415
469
|
}
|
|
416
470
|
|
|
417
471
|
// Tools check
|
|
472
|
+
/** @type {string[]} */
|
|
418
473
|
const toolErrors = [];
|
|
419
474
|
for (const tool of tools) {
|
|
420
475
|
if (typeof tool.name !== 'string' || !tool.name) {
|
|
@@ -436,6 +491,13 @@ class Loop {
|
|
|
436
491
|
return result;
|
|
437
492
|
}
|
|
438
493
|
|
|
494
|
+
/**
|
|
495
|
+
* Stateful single-turn chat that maintains conversation history across calls.
|
|
496
|
+
* @param {string} text - User message.
|
|
497
|
+
* @param {ToolDef[]} [tools=[]] - Tool definitions.
|
|
498
|
+
* @param {Record<string, any>} [options={}] - Per-run overrides.
|
|
499
|
+
* @returns {Promise<{text: string, toolCalls: ToolCall[], usage: Usage, cost: number, error: string|null, msgs: Message[]}>}
|
|
500
|
+
*/
|
|
439
501
|
async chat(text, tools = [], options = {}) {
|
|
440
502
|
this._history.push({ role: 'user', content: text });
|
|
441
503
|
const result = await this.run(this._history, tools, options);
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export type ToolDef = import("../types").ToolDef;
|
|
2
|
+
/**
|
|
3
|
+
* A server definition as found in an IDE/MCP config file.
|
|
4
|
+
*/
|
|
5
|
+
export type ServerDef = {
|
|
6
|
+
command: string;
|
|
7
|
+
args?: string[] | undefined;
|
|
8
|
+
env?: Record<string, string> | undefined;
|
|
9
|
+
cwd?: string | undefined;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Raw tool descriptor as returned by an MCP server's tools/list.
|
|
13
|
+
*/
|
|
14
|
+
export type McpTool = {
|
|
15
|
+
name: string;
|
|
16
|
+
description?: string | undefined;
|
|
17
|
+
inputSchema?: Record<string, any> | undefined;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Per-server entry persisted in .mcp-bridge.json.
|
|
21
|
+
*/
|
|
22
|
+
export type BridgeServerEntry = {
|
|
23
|
+
command: string;
|
|
24
|
+
args: string[];
|
|
25
|
+
env?: Record<string, string> | undefined;
|
|
26
|
+
cwd?: string | undefined;
|
|
27
|
+
/**
|
|
28
|
+
* - tool name -> "allow" | "deny"
|
|
29
|
+
*/
|
|
30
|
+
tools: Record<string, string>;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Persisted bridge config (.mcp-bridge.json).
|
|
34
|
+
*/
|
|
35
|
+
export type BridgeConfig = {
|
|
36
|
+
/**
|
|
37
|
+
* - ISO timestamp
|
|
38
|
+
*/
|
|
39
|
+
discovered: string;
|
|
40
|
+
ttl: string;
|
|
41
|
+
servers: Record<string, BridgeServerEntry>;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* A denied-tool descriptor surfaced to the LLM.
|
|
45
|
+
*/
|
|
46
|
+
export type DeniedTool = {
|
|
47
|
+
server: string;
|
|
48
|
+
tool: string;
|
|
49
|
+
description: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* JSON-RPC stdio client over a spawned MCP server.
|
|
53
|
+
*/
|
|
54
|
+
export type RpcClient = {
|
|
55
|
+
rpc: (method: string, params?: object) => Promise<any>;
|
|
56
|
+
notify: (method: string, params?: object) => void;
|
|
57
|
+
child: import("node:child_process").ChildProcessWithoutNullStreams;
|
|
58
|
+
stderr: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Create an MCP bridge. On first run, discovers MCP servers from IDE configs,
|
|
62
|
+
* connects, lists tools, and writes .mcp-bridge.json with all tools set to "allow".
|
|
63
|
+
* On subsequent runs, reads .mcp-bridge.json and respects allow/deny per tool.
|
|
64
|
+
* Re-discovers when TTL expires (default: 24h).
|
|
65
|
+
*
|
|
66
|
+
* Returns BOTH surfaces (v0.9+):
|
|
67
|
+
* - `tools` — bulk-loaded array of name-prefixed tools (small catalogs;
|
|
68
|
+
* LLM sees them upfront).
|
|
69
|
+
* - `metaTools` — [mcp_discover, mcp_invoke] LLM-callable pair (large catalogs;
|
|
70
|
+
* LLM picks tools dynamically). Shares the same RPC connections.
|
|
71
|
+
*
|
|
72
|
+
* Wire one or the other into Loop's tool array; never both (the LLM would see
|
|
73
|
+
* the same MCP tool twice). Pick by catalog size and token budget.
|
|
74
|
+
*
|
|
75
|
+
* @param {object} [opts]
|
|
76
|
+
* @param {string} [opts.bridgePath] - Path to .mcp-bridge.json. Default: .mcp-bridge.json in cwd.
|
|
77
|
+
* @param {string[]} [opts.configPaths] - IDE config paths for discovery.
|
|
78
|
+
* @param {string[]} [opts.servers] - Limit to these server names.
|
|
79
|
+
* @param {number} [opts.timeout=15000] - Per-server init timeout in ms.
|
|
80
|
+
* @param {boolean} [opts.refresh=false] - Force re-discovery regardless of TTL.
|
|
81
|
+
* @param {(name: string, def: ServerDef) => boolean | Promise<boolean>} [opts.confirmServer]
|
|
82
|
+
* Vet each discovered server BEFORE its `command` is spawned. Connecting to an
|
|
83
|
+
* MCP server runs its command, and discovery reads configs from the cwd (a
|
|
84
|
+
* `.mcp.json` in an untrusted repo) as well as the user's home/IDE configs.
|
|
85
|
+
* Return false to skip a server (its command is never executed). A throw is
|
|
86
|
+
* treated as a deny (fail-closed). Default: every discovered server is trusted
|
|
87
|
+
* (unchanged behavior) — pass this to gate command execution.
|
|
88
|
+
* @returns {Promise<{tools: ToolDef[], metaTools?: ToolDef[], servers: string[], systemContext: string, denied: DeniedTool[], errors?: Array<{server: string, error: string}>, close: Function}>}
|
|
89
|
+
*/
|
|
90
|
+
export function createMCPBridge(opts?: {
|
|
91
|
+
bridgePath?: string | undefined;
|
|
92
|
+
configPaths?: string[] | undefined;
|
|
93
|
+
servers?: string[] | undefined;
|
|
94
|
+
timeout?: number | undefined;
|
|
95
|
+
refresh?: boolean | undefined;
|
|
96
|
+
confirmServer?: ((name: string, def: ServerDef) => boolean | Promise<boolean>) | undefined;
|
|
97
|
+
}): Promise<{
|
|
98
|
+
tools: ToolDef[];
|
|
99
|
+
metaTools?: ToolDef[];
|
|
100
|
+
servers: string[];
|
|
101
|
+
systemContext: string;
|
|
102
|
+
denied: DeniedTool[];
|
|
103
|
+
errors?: Array<{
|
|
104
|
+
server: string;
|
|
105
|
+
error: string;
|
|
106
|
+
}>;
|
|
107
|
+
close: Function;
|
|
108
|
+
}>;
|
|
109
|
+
/**
|
|
110
|
+
* @param {string[]} [configPaths]
|
|
111
|
+
* @returns {Map<string, ServerDef>}
|
|
112
|
+
*/
|
|
113
|
+
export function discoverServers(configPaths?: string[]): Map<string, ServerDef>;
|
|
114
|
+
/**
|
|
115
|
+
* Build the LLM-callable meta-tool surface from a fully-connected bridge.
|
|
116
|
+
* Shares the underlying tool array and RPC clients with the bulk surface —
|
|
117
|
+
* one set of connections, one factory, two output forms. The user picks
|
|
118
|
+
* `bridge.tools` (bulk) for small catalogs the LLM should see upfront, or
|
|
119
|
+
* `bridge.metaTools` for large catalogs the LLM should discover on demand.
|
|
120
|
+
*
|
|
121
|
+
* Gov shape: when the LLM calls mcp_invoke, the action sent to gate.check
|
|
122
|
+
* is `{ type: 'mcp_invoke', args: { name, args }, _ctx }` — bareguard sees
|
|
123
|
+
* `mcp_invoke` as the type. To deny specific MCP tools, use bareguard's
|
|
124
|
+
* `tools.denyArgPatterns: { mcp_invoke: [/"name":"linear_admin_.*"/] }`
|
|
125
|
+
* or `content.denyPatterns` over the JSON-serialized form. The inner MCP
|
|
126
|
+
* tool name doesn't travel as `action.type` — that's a deliberate v0.9
|
|
127
|
+
* trade for one consistent gate-check call per LLM tool invocation.
|
|
128
|
+
*
|
|
129
|
+
* @param {ToolDef[]} tools - The bulk-loaded, name-prefixed tools array.
|
|
130
|
+
* @param {string} [discoveredAt] - ISO timestamp from .mcp-bridge.json.
|
|
131
|
+
* @returns {ToolDef[]} [mcp_discover, mcp_invoke]
|
|
132
|
+
*/
|
|
133
|
+
export function buildMetaTools(tools: ToolDef[], discoveredAt?: string): ToolDef[];
|