@zackbart/connecta 0.1.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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +249 -0
- package/SECURITY.md +25 -0
- package/assets/connecta-clay-hero.png +0 -0
- package/dist/activity.d.ts +82 -0
- package/dist/activity.d.ts.map +1 -0
- package/dist/activity.js +51 -0
- package/dist/activity.js.map +1 -0
- package/dist/auth/bearer.d.ts +10 -0
- package/dist/auth/bearer.d.ts.map +1 -0
- package/dist/auth/bearer.js +47 -0
- package/dist/auth/bearer.js.map +1 -0
- package/dist/auth/clerk.d.ts +20 -0
- package/dist/auth/clerk.d.ts.map +1 -0
- package/dist/auth/clerk.js +167 -0
- package/dist/auth/clerk.js.map +1 -0
- package/dist/auth/downstream-oauth.d.ts +78 -0
- package/dist/auth/downstream-oauth.d.ts.map +1 -0
- package/dist/auth/downstream-oauth.js +180 -0
- package/dist/auth/downstream-oauth.js.map +1 -0
- package/dist/catalog.d.ts +11 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +144 -0
- package/dist/catalog.js.map +1 -0
- package/dist/connectors/api.d.ts +34 -0
- package/dist/connectors/api.d.ts.map +1 -0
- package/dist/connectors/api.js +36 -0
- package/dist/connectors/api.js.map +1 -0
- package/dist/connectors/remote-mcp.d.ts +33 -0
- package/dist/connectors/remote-mcp.d.ts.map +1 -0
- package/dist/connectors/remote-mcp.js +255 -0
- package/dist/connectors/remote-mcp.js.map +1 -0
- package/dist/credentials.d.ts +33 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +177 -0
- package/dist/credentials.js.map +1 -0
- package/dist/execute.d.ts +40 -0
- package/dist/execute.d.ts.map +1 -0
- package/dist/execute.js +424 -0
- package/dist/execute.js.map +1 -0
- package/dist/executors/quickjs.d.ts +26 -0
- package/dist/executors/quickjs.d.ts.map +1 -0
- package/dist/executors/quickjs.js +267 -0
- package/dist/executors/quickjs.js.map +1 -0
- package/dist/favicon.d.ts +2 -0
- package/dist/favicon.d.ts.map +1 -0
- package/dist/favicon.js +49 -0
- package/dist/favicon.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +73 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-result.d.ts +9 -0
- package/dist/mcp-result.d.ts.map +1 -0
- package/dist/mcp-result.js +35 -0
- package/dist/mcp-result.js.map +1 -0
- package/dist/meta-tools.d.ts +100 -0
- package/dist/meta-tools.d.ts.map +1 -0
- package/dist/meta-tools.js +797 -0
- package/dist/meta-tools.js.map +1 -0
- package/dist/node.d.ts +9 -0
- package/dist/node.d.ts.map +1 -0
- package/dist/node.js +57 -0
- package/dist/node.js.map +1 -0
- package/dist/registry.d.ts +78 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +299 -0
- package/dist/registry.js.map +1 -0
- package/dist/server.d.ts +31 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +543 -0
- package/dist/server.js.map +1 -0
- package/dist/skills.d.ts +8 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +60 -0
- package/dist/skills.js.map +1 -0
- package/dist/storage/file.d.ts +8 -0
- package/dist/storage/file.d.ts.map +1 -0
- package/dist/storage/file.js +53 -0
- package/dist/storage/file.js.map +1 -0
- package/dist/storage/memory.d.ts +4 -0
- package/dist/storage/memory.d.ts.map +1 -0
- package/dist/storage/memory.js +29 -0
- package/dist/storage/memory.js.map +1 -0
- package/dist/types.d.ts +219 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +91 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +1031 -0
- package/dist/ui.js.map +1 -0
- package/package.json +96 -0
- package/src/activity.ts +144 -0
- package/src/auth/bearer.ts +54 -0
- package/src/auth/clerk.ts +226 -0
- package/src/auth/downstream-oauth.ts +202 -0
- package/src/catalog.ts +166 -0
- package/src/connectors/api.ts +80 -0
- package/src/connectors/remote-mcp.ts +324 -0
- package/src/credentials.ts +259 -0
- package/src/execute.ts +550 -0
- package/src/executors/quickjs.ts +321 -0
- package/src/favicon.ts +53 -0
- package/src/index.ts +197 -0
- package/src/mcp-result.ts +43 -0
- package/src/meta-tools.ts +1120 -0
- package/src/node.ts +62 -0
- package/src/registry.ts +400 -0
- package/src/server.ts +758 -0
- package/src/skills.ts +63 -0
- package/src/storage/file.ts +62 -0
- package/src/storage/memory.ts +34 -0
- package/src/types.ts +254 -0
- package/src/ui.ts +1121 -0
|
@@ -0,0 +1,797 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { compactSchema, rankTools, summarizeDescription } from "./catalog.js";
|
|
3
|
+
import { recordToolActivity, } from "./activity.js";
|
|
4
|
+
import { unwrapMcpResult } from "./mcp-result.js";
|
|
5
|
+
import { AVAILABLE_SKILLS } from "./skills.js";
|
|
6
|
+
const RESULT_TTL_SECONDS = 900;
|
|
7
|
+
export function jsonResult(obj) {
|
|
8
|
+
return {
|
|
9
|
+
content: [{ type: "text", text: JSON.stringify(obj, null, 2) }],
|
|
10
|
+
...(obj !== null && typeof obj === "object" && !Array.isArray(obj)
|
|
11
|
+
? { structuredContent: obj }
|
|
12
|
+
: {}),
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function errorResult(message) {
|
|
16
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
17
|
+
}
|
|
18
|
+
function msg(err) {
|
|
19
|
+
return err instanceof Error ? err.message : String(err);
|
|
20
|
+
}
|
|
21
|
+
const DEFAULT_SEARCH_LIMIT = 25;
|
|
22
|
+
const enc = new TextEncoder();
|
|
23
|
+
const dec = new TextDecoder();
|
|
24
|
+
function errorDetails(code, message) {
|
|
25
|
+
return {
|
|
26
|
+
code,
|
|
27
|
+
message,
|
|
28
|
+
retryable: /timeout|timed out|econnreset|econnrefused|temporar|rate.?limit|429|502|503|504|refcountedcanceler|different request/i.test(message),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/** True if `b` is a UTF-8 continuation byte (0b10xxxxxx). */
|
|
32
|
+
function isContinuationByte(b) {
|
|
33
|
+
return (b & 0xc0) === 0x80;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Move a byte `end` back to the nearest UTF-8 codepoint boundary in
|
|
37
|
+
* `(offset, total]`, so decoding `bytes[offset, end)` never splits a codepoint
|
|
38
|
+
* (which would emit U+FFFD and break byte-exact reassembly). If backing up
|
|
39
|
+
* would make no progress — a single codepoint wider than the window — extend
|
|
40
|
+
* forward to the end of that codepoint instead so paging always advances.
|
|
41
|
+
* Assumes `offset` is itself a codepoint boundary (offsets are the prior
|
|
42
|
+
* `nextOffset`, which this function guarantees, and 0 is always a boundary).
|
|
43
|
+
*/
|
|
44
|
+
function alignEndToCharBoundary(bytes, offset, end, total) {
|
|
45
|
+
if (end >= total)
|
|
46
|
+
return total;
|
|
47
|
+
let e = end;
|
|
48
|
+
while (e > offset && isContinuationByte(bytes[e]))
|
|
49
|
+
e--;
|
|
50
|
+
if (e === offset) {
|
|
51
|
+
// Window is narrower than the codepoint at `offset`; take the whole thing.
|
|
52
|
+
e = end;
|
|
53
|
+
while (e < total && isContinuationByte(bytes[e]))
|
|
54
|
+
e++;
|
|
55
|
+
}
|
|
56
|
+
return e;
|
|
57
|
+
}
|
|
58
|
+
// --- fields selection (feature 2) -----------------------------------------
|
|
59
|
+
/** Resolve a dot-path (segments) against a value; `key[]` maps the tail over an array. */
|
|
60
|
+
function resolvePath(value, segments) {
|
|
61
|
+
if (segments.length === 0)
|
|
62
|
+
return value;
|
|
63
|
+
const [seg, ...rest] = segments;
|
|
64
|
+
const isArr = seg.endsWith("[]");
|
|
65
|
+
const key = isArr ? seg.slice(0, -2) : seg;
|
|
66
|
+
let next = value;
|
|
67
|
+
if (key !== "") {
|
|
68
|
+
if (value === null || typeof value !== "object")
|
|
69
|
+
return undefined;
|
|
70
|
+
next = value[key];
|
|
71
|
+
}
|
|
72
|
+
if (isArr) {
|
|
73
|
+
if (!Array.isArray(next))
|
|
74
|
+
return undefined;
|
|
75
|
+
return next.map((el) => resolvePath(el, rest));
|
|
76
|
+
}
|
|
77
|
+
return resolvePath(next, rest);
|
|
78
|
+
}
|
|
79
|
+
/** Select the given dot-paths from a value → `{ "<path>": value }` (omitting misses). */
|
|
80
|
+
function applyFields(value, fields) {
|
|
81
|
+
const out = {};
|
|
82
|
+
for (const path of fields) {
|
|
83
|
+
const resolved = resolvePath(value, path.split("."));
|
|
84
|
+
if (resolved !== undefined)
|
|
85
|
+
out[path] = resolved;
|
|
86
|
+
}
|
|
87
|
+
return out;
|
|
88
|
+
}
|
|
89
|
+
/** Apply fields to each JSON-parseable text block; non-JSON blocks pass through. */
|
|
90
|
+
function applyFieldsToContent(content, fields) {
|
|
91
|
+
return content.map((b) => {
|
|
92
|
+
if (b.type !== "text")
|
|
93
|
+
return b;
|
|
94
|
+
let parsed;
|
|
95
|
+
try {
|
|
96
|
+
parsed = JSON.parse(b.text);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return b;
|
|
100
|
+
}
|
|
101
|
+
return { ...b, text: JSON.stringify(applyFields(parsed, fields), null, 2) };
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
// --- result-size guard + get_result (feature 1) ---------------------------
|
|
105
|
+
function contentBytes(content) {
|
|
106
|
+
let n = 0;
|
|
107
|
+
for (const b of content)
|
|
108
|
+
if (b.type === "text")
|
|
109
|
+
n += enc.encode(b.text).length;
|
|
110
|
+
return n;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Return `text` as a single content block; if it exceeds `cap` bytes, stash the
|
|
114
|
+
* full text under `result:<uuid>` (ttl 900s) and return the first `cap` bytes
|
|
115
|
+
* followed by a JSON truncation notice pointing at get_result.
|
|
116
|
+
*/
|
|
117
|
+
async function guardText(text, results, cap) {
|
|
118
|
+
const bytes = enc.encode(text);
|
|
119
|
+
if (bytes.length <= cap) {
|
|
120
|
+
return { content: [{ type: "text", text }] };
|
|
121
|
+
}
|
|
122
|
+
const id = crypto.randomUUID();
|
|
123
|
+
await results.set(`result:${id}`, text, { ttlSeconds: RESULT_TTL_SECONDS });
|
|
124
|
+
const head = dec.decode(bytes.slice(0, alignEndToCharBoundary(bytes, 0, cap, bytes.length)));
|
|
125
|
+
const notice = JSON.stringify({
|
|
126
|
+
truncated: true,
|
|
127
|
+
resultId: id,
|
|
128
|
+
totalBytes: bytes.length,
|
|
129
|
+
hint: "use get_result {id, offset} to page, or re-call with fields to select less",
|
|
130
|
+
});
|
|
131
|
+
return { content: [{ type: "text", text: `${head}\n${notice}` }] };
|
|
132
|
+
}
|
|
133
|
+
/** Store an oversized JSON value and replace it with a page handle. */
|
|
134
|
+
async function guardValue(value, results, cap) {
|
|
135
|
+
const text = JSON.stringify(value, null, 2) ?? String(value);
|
|
136
|
+
const bytes = enc.encode(text);
|
|
137
|
+
if (bytes.length <= cap)
|
|
138
|
+
return value;
|
|
139
|
+
const id = crypto.randomUUID();
|
|
140
|
+
await results.set(`result:${id}`, text, { ttlSeconds: RESULT_TTL_SECONDS });
|
|
141
|
+
return {
|
|
142
|
+
truncated: true,
|
|
143
|
+
resultId: id,
|
|
144
|
+
totalBytes: bytes.length,
|
|
145
|
+
hint: "use get_result {id, offset} to page, or re-call with fields to select less",
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* The nine meta-tool handlers over a registry. Exported for direct testing;
|
|
150
|
+
* registerMetaTools() wires them onto an McpServer. `opts.maxResultBytes`
|
|
151
|
+
* overrides the registry's default result-size cap. (execute_code, the optional
|
|
152
|
+
* tenth tool, is registered separately by registerExecuteTool.)
|
|
153
|
+
*/
|
|
154
|
+
export function createMetaTools(registry, baseUrl, opts = {}) {
|
|
155
|
+
const cap = opts.maxResultBytes ?? registry.maxResultBytes;
|
|
156
|
+
// createMetaTools() is called once per inbound MCP request. Sharing this
|
|
157
|
+
// identity lets remote connectors reuse one downstream client inside that
|
|
158
|
+
// request without leaking request-bound I/O into the next one.
|
|
159
|
+
const requestScope = {};
|
|
160
|
+
/** Shared call path used by call tools and batch_call: safety → fields → size guard. */
|
|
161
|
+
async function runCall(call, source, options = {}) {
|
|
162
|
+
const started = Date.now();
|
|
163
|
+
let catalogMs = 0;
|
|
164
|
+
let connectorMs = 0;
|
|
165
|
+
let backoffMs = 0;
|
|
166
|
+
let resultProcessingMs = 0;
|
|
167
|
+
let attempts = 0;
|
|
168
|
+
const timing = () => ({
|
|
169
|
+
catalogMs,
|
|
170
|
+
connectorMs,
|
|
171
|
+
backoffMs,
|
|
172
|
+
resultProcessingMs,
|
|
173
|
+
totalMs: Date.now() - started,
|
|
174
|
+
});
|
|
175
|
+
const resolved = registry.resolveAddress(call.address);
|
|
176
|
+
const record = (outcome, errorCode) => {
|
|
177
|
+
if (!resolved)
|
|
178
|
+
return;
|
|
179
|
+
recordToolActivity(opts.activity, {
|
|
180
|
+
connectorId: resolved.connector.id,
|
|
181
|
+
toolName: resolved.toolName,
|
|
182
|
+
address: `${resolved.connector.id}.${resolved.toolName}`,
|
|
183
|
+
source,
|
|
184
|
+
outcome,
|
|
185
|
+
durationMs: Date.now() - started,
|
|
186
|
+
attempts,
|
|
187
|
+
...(errorCode ? { errorCode } : {}),
|
|
188
|
+
});
|
|
189
|
+
};
|
|
190
|
+
const failed = (code, message) => {
|
|
191
|
+
const durationMs = Date.now() - started;
|
|
192
|
+
const error = errorDetails(code, message);
|
|
193
|
+
const diagnostics = timing();
|
|
194
|
+
record(code === "timeout" ? "timeout" : "error", code);
|
|
195
|
+
return {
|
|
196
|
+
toolResult: call.resultMode === "value"
|
|
197
|
+
? jsonResult({
|
|
198
|
+
ok: false,
|
|
199
|
+
error,
|
|
200
|
+
durationMs,
|
|
201
|
+
attempts,
|
|
202
|
+
...(call.diagnostics ? { timing: diagnostics } : {}),
|
|
203
|
+
})
|
|
204
|
+
: errorResult(message),
|
|
205
|
+
durationMs,
|
|
206
|
+
attempts,
|
|
207
|
+
timing: diagnostics,
|
|
208
|
+
error,
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
if (!resolved) {
|
|
212
|
+
return failed("unknown_address", `Unknown address "${call.address}"`);
|
|
213
|
+
}
|
|
214
|
+
const results = registry.resultsStorage();
|
|
215
|
+
const fields = call.fields && call.fields.length > 0 ? call.fields : null;
|
|
216
|
+
const timeoutMs = call.timeoutMs && call.timeoutMs > 0
|
|
217
|
+
? Math.max(1, Math.trunc(call.timeoutMs))
|
|
218
|
+
: undefined;
|
|
219
|
+
const maxRetries = Math.min(2, Math.max(0, Math.trunc(call.maxRetries ?? 0)));
|
|
220
|
+
const catalogStarted = Date.now();
|
|
221
|
+
let definition;
|
|
222
|
+
try {
|
|
223
|
+
definition = (await registry.getTools(resolved.connector.id, baseUrl, requestScope)).find((tool) => tool.name === resolved.toolName);
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
catalogMs += Date.now() - catalogStarted;
|
|
227
|
+
return failed("catalog_lookup_failed", msg(err));
|
|
228
|
+
}
|
|
229
|
+
catalogMs += Date.now() - catalogStarted;
|
|
230
|
+
if (!definition) {
|
|
231
|
+
return failed("unknown_tool", `Unknown tool "${resolved.toolName}" on connector "${resolved.connector.id}"`);
|
|
232
|
+
}
|
|
233
|
+
const explicitlyReadOnly = definition.annotations?.readOnlyHint === true &&
|
|
234
|
+
definition.annotations?.destructiveHint !== true;
|
|
235
|
+
if (!explicitlyReadOnly && !options.allowDestructive) {
|
|
236
|
+
return failed("destructive_tool_requires_approval", `Tool "${call.address}" is not explicitly read-only. Invoke it through call_destructive_tool so the MCP host can request explicit approval.`);
|
|
237
|
+
}
|
|
238
|
+
const retrySafe = definition.annotations?.readOnlyHint === true ||
|
|
239
|
+
definition.annotations?.idempotentHint === true;
|
|
240
|
+
let result;
|
|
241
|
+
while (true) {
|
|
242
|
+
attempts++;
|
|
243
|
+
const controller = timeoutMs ? new AbortController() : undefined;
|
|
244
|
+
let timer;
|
|
245
|
+
const ctx = registry.contextFor(resolved.connector.id, baseUrl, requestScope, { signal: controller?.signal, timeoutMs });
|
|
246
|
+
const connectorStarted = Date.now();
|
|
247
|
+
try {
|
|
248
|
+
const pending = resolved.connector.callTool(resolved.toolName, call.args ?? {}, ctx);
|
|
249
|
+
result = timeoutMs
|
|
250
|
+
? await Promise.race([
|
|
251
|
+
pending,
|
|
252
|
+
new Promise((_, reject) => {
|
|
253
|
+
timer = setTimeout(() => {
|
|
254
|
+
reject(new Error(`Tool call timed out after ${timeoutMs}ms`));
|
|
255
|
+
controller?.abort();
|
|
256
|
+
}, timeoutMs);
|
|
257
|
+
}),
|
|
258
|
+
])
|
|
259
|
+
: await pending;
|
|
260
|
+
connectorMs += Date.now() - connectorStarted;
|
|
261
|
+
const mcpResult = result;
|
|
262
|
+
if (resolved.connector.kind === "mcp" && mcpResult?.isError) {
|
|
263
|
+
throw new Error(mcpResult.content?.map((block) => block.text).join("") ||
|
|
264
|
+
"Downstream tool call failed");
|
|
265
|
+
}
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
catch (err) {
|
|
269
|
+
// Includes connector setup, downstream execution, and timeout wait.
|
|
270
|
+
connectorMs += Date.now() - connectorStarted;
|
|
271
|
+
const details = errorDetails("connector_call_failed", msg(err));
|
|
272
|
+
if (attempts <= maxRetries && retrySafe && details.retryable) {
|
|
273
|
+
const backoffStarted = Date.now();
|
|
274
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(250 * 2 ** (attempts - 1), 1_000)));
|
|
275
|
+
backoffMs += Date.now() - backoffStarted;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
registry.recordFailure(resolved.connector.id, Date.now() - started, err);
|
|
279
|
+
return failed(/timed out|timeout/i.test(msg(err))
|
|
280
|
+
? "timeout"
|
|
281
|
+
: "connector_call_failed", msg(err));
|
|
282
|
+
}
|
|
283
|
+
finally {
|
|
284
|
+
if (timer)
|
|
285
|
+
clearTimeout(timer);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
registry.recordSuccess(resolved.connector.id, Date.now() - started);
|
|
289
|
+
const processingStarted = Date.now();
|
|
290
|
+
try {
|
|
291
|
+
const mr = result;
|
|
292
|
+
if (call.resultMode === "value") {
|
|
293
|
+
let value = unwrapMcpResult(resolved.connector.kind, result);
|
|
294
|
+
if (fields)
|
|
295
|
+
value = applyFields(value, fields);
|
|
296
|
+
value = await guardValue(value, results, cap);
|
|
297
|
+
resultProcessingMs += Date.now() - processingStarted;
|
|
298
|
+
const durationMs = Date.now() - started;
|
|
299
|
+
const diagnostics = timing();
|
|
300
|
+
record("success");
|
|
301
|
+
return {
|
|
302
|
+
toolResult: jsonResult({
|
|
303
|
+
ok: true,
|
|
304
|
+
data: value,
|
|
305
|
+
durationMs,
|
|
306
|
+
attempts,
|
|
307
|
+
...(call.diagnostics ? { timing: diagnostics } : {}),
|
|
308
|
+
}),
|
|
309
|
+
durationMs,
|
|
310
|
+
attempts,
|
|
311
|
+
timing: diagnostics,
|
|
312
|
+
value,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
if (resolved.connector.kind === "mcp") {
|
|
316
|
+
let content = mr?.content ?? [];
|
|
317
|
+
if (fields)
|
|
318
|
+
content = applyFieldsToContent(content, fields);
|
|
319
|
+
let toolResult;
|
|
320
|
+
if (contentBytes(content) > cap) {
|
|
321
|
+
toolResult = await guardText(JSON.stringify(content, null, 2), results, cap);
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
toolResult = { content };
|
|
325
|
+
}
|
|
326
|
+
resultProcessingMs += Date.now() - processingStarted;
|
|
327
|
+
record("success");
|
|
328
|
+
return {
|
|
329
|
+
toolResult,
|
|
330
|
+
durationMs: Date.now() - started,
|
|
331
|
+
attempts,
|
|
332
|
+
timing: timing(),
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const value = fields ? applyFields(result, fields) : result;
|
|
336
|
+
const toolResult = await guardText(JSON.stringify(value, null, 2), results, cap);
|
|
337
|
+
resultProcessingMs += Date.now() - processingStarted;
|
|
338
|
+
record("success");
|
|
339
|
+
return {
|
|
340
|
+
toolResult,
|
|
341
|
+
durationMs: Date.now() - started,
|
|
342
|
+
attempts,
|
|
343
|
+
timing: timing(),
|
|
344
|
+
value,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
catch (err) {
|
|
348
|
+
resultProcessingMs += Date.now() - processingStarted;
|
|
349
|
+
return failed("result_processing_failed", msg(err));
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return {
|
|
353
|
+
async skills(args = {}) {
|
|
354
|
+
if (!args.name) {
|
|
355
|
+
return {
|
|
356
|
+
content: [
|
|
357
|
+
{
|
|
358
|
+
type: "text",
|
|
359
|
+
text: 'Available skills. Fetch one with skills({ name: "<name>" }).\n\n' +
|
|
360
|
+
AVAILABLE_SKILLS.map((skill) => `- \`${skill.name}\` — ${skill.description}`).join("\n"),
|
|
361
|
+
},
|
|
362
|
+
],
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
const skill = AVAILABLE_SKILLS.find((item) => item.name === args.name);
|
|
366
|
+
if (!skill) {
|
|
367
|
+
return errorResult(`Unknown skill "${args.name}". Available: ${AVAILABLE_SKILLS.map((item) => item.name).join(", ")}.`);
|
|
368
|
+
}
|
|
369
|
+
return { content: [{ type: "text", text: skill.content }] };
|
|
370
|
+
},
|
|
371
|
+
async listConnectors(args = {}) {
|
|
372
|
+
const probe = args.probe ?? true;
|
|
373
|
+
const out = await Promise.all(registry.listConnectors().map(async (c) => {
|
|
374
|
+
const checkedAt = new Date().toISOString();
|
|
375
|
+
const statusStarted = Date.now();
|
|
376
|
+
const observed = registry.healthFor(c.id);
|
|
377
|
+
let status = probe
|
|
378
|
+
? await registry.statusFor(c.id, baseUrl, requestScope)
|
|
379
|
+
: {
|
|
380
|
+
state: observed?.consecutiveFailures &&
|
|
381
|
+
observed.consecutiveFailures > 0
|
|
382
|
+
? "error"
|
|
383
|
+
: observed?.lastSuccessAt || c.kind === "api"
|
|
384
|
+
? "ok"
|
|
385
|
+
: "unknown",
|
|
386
|
+
...(observed?.lastError ? { message: observed.lastError } : {}),
|
|
387
|
+
};
|
|
388
|
+
let tools = registry.peekTools(c.id);
|
|
389
|
+
// An auth_required status may have just started OAuth. A second
|
|
390
|
+
// listTools probe would overwrite its state/verifier while returning
|
|
391
|
+
// the first (now stale) authorization URL.
|
|
392
|
+
if (probe && status.state === "ok") {
|
|
393
|
+
try {
|
|
394
|
+
tools = await registry.refreshTools(c.id, baseUrl, requestScope);
|
|
395
|
+
registry.recordSuccess(c.id, Date.now() - statusStarted);
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
status = { state: "error", message: msg(err) };
|
|
399
|
+
registry.recordFailure(c.id, Date.now() - statusStarted, err);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const latencyMs = Date.now() - statusStarted;
|
|
403
|
+
const latestObserved = registry.healthFor(c.id);
|
|
404
|
+
return {
|
|
405
|
+
id: c.id,
|
|
406
|
+
...(c.title ? { title: c.title } : {}),
|
|
407
|
+
description: c.description,
|
|
408
|
+
toolCount: tools?.length ?? 0,
|
|
409
|
+
status: status.state,
|
|
410
|
+
checkedAt,
|
|
411
|
+
latencyMs,
|
|
412
|
+
probe,
|
|
413
|
+
...(latestObserved ?? observed ?? {}),
|
|
414
|
+
...("authorizationUrl" in status && status.authorizationUrl
|
|
415
|
+
? { authorizationUrl: status.authorizationUrl }
|
|
416
|
+
: {}),
|
|
417
|
+
...(status.message ? { message: status.message } : {}),
|
|
418
|
+
};
|
|
419
|
+
}));
|
|
420
|
+
return jsonResult({ connectors: out });
|
|
421
|
+
},
|
|
422
|
+
async searchTools(args) {
|
|
423
|
+
const q = args.query ?? "";
|
|
424
|
+
const limit = Math.max(1, Math.trunc(args.limit ?? DEFAULT_SEARCH_LIMIT));
|
|
425
|
+
const offset = Math.max(0, Math.trunc(args.offset ?? 0));
|
|
426
|
+
const conns = args.connector
|
|
427
|
+
? [registry.getConnector(args.connector)].filter((c) => Boolean(c))
|
|
428
|
+
: registry.listConnectors();
|
|
429
|
+
const matches = [];
|
|
430
|
+
const catalogs = await Promise.allSettled(conns.map((c) => registry.getTools(c.id, baseUrl, requestScope)));
|
|
431
|
+
let orderBase = 0;
|
|
432
|
+
catalogs.forEach((catalog, connectorIndex) => {
|
|
433
|
+
const c = conns[connectorIndex];
|
|
434
|
+
if (catalog.status === "fulfilled") {
|
|
435
|
+
for (const ranked of rankTools(catalog.value, q)) {
|
|
436
|
+
matches.push({
|
|
437
|
+
connectorId: c.id,
|
|
438
|
+
connectorTitle: c.title,
|
|
439
|
+
connectorDescription: c.description,
|
|
440
|
+
tool: ranked.tool,
|
|
441
|
+
score: ranked.score,
|
|
442
|
+
order: orderBase + ranked.order,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
orderBase += catalog.status === "fulfilled" ? catalog.value.length : 1;
|
|
447
|
+
});
|
|
448
|
+
matches.sort((a, b) => b.score - a.score || a.order - b.order);
|
|
449
|
+
const page = matches.slice(offset, offset + limit);
|
|
450
|
+
const groups = [];
|
|
451
|
+
const byConnector = new Map();
|
|
452
|
+
for (const match of page) {
|
|
453
|
+
let group = byConnector.get(match.connectorId);
|
|
454
|
+
if (!group) {
|
|
455
|
+
group = {
|
|
456
|
+
id: match.connectorId,
|
|
457
|
+
...(match.connectorTitle ? { title: match.connectorTitle } : {}),
|
|
458
|
+
description: match.connectorDescription,
|
|
459
|
+
tools: [],
|
|
460
|
+
};
|
|
461
|
+
byConnector.set(match.connectorId, group);
|
|
462
|
+
groups.push(group);
|
|
463
|
+
}
|
|
464
|
+
const schema = match.tool.inputSchema ?? { type: "object" };
|
|
465
|
+
group.tools.push({
|
|
466
|
+
name: match.tool.name,
|
|
467
|
+
address: `${match.connectorId}.${match.tool.name}`,
|
|
468
|
+
description: summarizeDescription(match.tool.description, args.fullDescriptions === true),
|
|
469
|
+
...(args.includeSchemas
|
|
470
|
+
? {
|
|
471
|
+
inputSchema: args.includeSchemas === "json"
|
|
472
|
+
? schema
|
|
473
|
+
: compactSchema(schema),
|
|
474
|
+
}
|
|
475
|
+
: {}),
|
|
476
|
+
...(args.includeSchemas && match.tool.outputSchema
|
|
477
|
+
? {
|
|
478
|
+
outputSchema: args.includeSchemas === "json"
|
|
479
|
+
? match.tool.outputSchema
|
|
480
|
+
: compactSchema(match.tool.outputSchema),
|
|
481
|
+
}
|
|
482
|
+
: {}),
|
|
483
|
+
...(match.tool.annotations
|
|
484
|
+
? { annotations: match.tool.annotations }
|
|
485
|
+
: {}),
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
const nextOffset = offset + page.length < matches.length
|
|
489
|
+
? offset + page.length
|
|
490
|
+
: undefined;
|
|
491
|
+
return jsonResult({
|
|
492
|
+
connectors: groups,
|
|
493
|
+
total: matches.length,
|
|
494
|
+
offset,
|
|
495
|
+
limit,
|
|
496
|
+
hasMore: nextOffset !== undefined,
|
|
497
|
+
...(nextOffset !== undefined ? { nextOffset } : {}),
|
|
498
|
+
});
|
|
499
|
+
},
|
|
500
|
+
async describeTools(args) {
|
|
501
|
+
const format = args.format ?? "compact";
|
|
502
|
+
const resolved = args.addresses.map((address) => ({
|
|
503
|
+
address,
|
|
504
|
+
resolved: registry.resolveAddress(address),
|
|
505
|
+
}));
|
|
506
|
+
const connectorIds = [
|
|
507
|
+
...new Set(resolved
|
|
508
|
+
.map((entry) => entry.resolved?.connector.id)
|
|
509
|
+
.filter((id) => Boolean(id))),
|
|
510
|
+
];
|
|
511
|
+
const loaded = await Promise.allSettled(connectorIds.map((id) => registry.getTools(id, baseUrl, requestScope)));
|
|
512
|
+
const catalogs = new Map();
|
|
513
|
+
loaded.forEach((result, index) => {
|
|
514
|
+
catalogs.set(connectorIds[index], result.status === "fulfilled"
|
|
515
|
+
? result.value
|
|
516
|
+
: result.reason instanceof Error
|
|
517
|
+
? result.reason
|
|
518
|
+
: new Error(String(result.reason)));
|
|
519
|
+
});
|
|
520
|
+
const out = resolved.map(({ address, resolved }) => {
|
|
521
|
+
if (!resolved) {
|
|
522
|
+
return { address, error: `Unknown address "${address}"` };
|
|
523
|
+
}
|
|
524
|
+
const catalog = catalogs.get(resolved.connector.id);
|
|
525
|
+
if (catalog instanceof Error) {
|
|
526
|
+
return { address, error: catalog.message };
|
|
527
|
+
}
|
|
528
|
+
const tool = catalog?.find((t) => t.name === resolved.toolName);
|
|
529
|
+
if (!tool) {
|
|
530
|
+
return {
|
|
531
|
+
address,
|
|
532
|
+
error: `Unknown tool "${resolved.toolName}" on connector "${resolved.connector.id}"`,
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
const schema = tool.inputSchema ?? { type: "object" };
|
|
536
|
+
return {
|
|
537
|
+
address,
|
|
538
|
+
name: tool.name,
|
|
539
|
+
description: summarizeDescription(tool.description, args.fullDescriptions === true),
|
|
540
|
+
inputSchema: format === "json" ? schema : compactSchema(schema),
|
|
541
|
+
...(tool.outputSchema
|
|
542
|
+
? {
|
|
543
|
+
outputSchema: format === "json"
|
|
544
|
+
? tool.outputSchema
|
|
545
|
+
: compactSchema(tool.outputSchema),
|
|
546
|
+
}
|
|
547
|
+
: {}),
|
|
548
|
+
...(tool.annotations ? { annotations: tool.annotations } : {}),
|
|
549
|
+
};
|
|
550
|
+
});
|
|
551
|
+
return jsonResult({ tools: out });
|
|
552
|
+
},
|
|
553
|
+
async callTool(args) {
|
|
554
|
+
return (await runCall(args, "call_tool")).toolResult;
|
|
555
|
+
},
|
|
556
|
+
async callDestructiveTool(args) {
|
|
557
|
+
return (await runCall(args, "call_destructive_tool", { allowDestructive: true })).toolResult;
|
|
558
|
+
},
|
|
559
|
+
async getResult(args) {
|
|
560
|
+
const results = registry.resultsStorage();
|
|
561
|
+
const stored = await results.get(`result:${args.id}`);
|
|
562
|
+
if (stored === null || stored === undefined) {
|
|
563
|
+
return errorResult(`Unknown or expired result id "${args.id}"`);
|
|
564
|
+
}
|
|
565
|
+
const bytes = enc.encode(stored);
|
|
566
|
+
const total = bytes.length;
|
|
567
|
+
const offset = Math.max(0, Math.trunc(args.offset ?? 0));
|
|
568
|
+
const maxBytes = args.maxBytes ?? cap;
|
|
569
|
+
// Align the slice end to a codepoint boundary so a multi-byte char is
|
|
570
|
+
// never split across pages (which would emit U+FFFD on both sides).
|
|
571
|
+
// `nextOffset` is this aligned end, so it is a valid boundary for the
|
|
572
|
+
// next call and paging reassembles the original byte-for-byte.
|
|
573
|
+
const end = alignEndToCharBoundary(bytes, offset, offset + maxBytes, total);
|
|
574
|
+
const slice = dec.decode(bytes.slice(offset, end));
|
|
575
|
+
const nextOffset = end < total ? end : undefined;
|
|
576
|
+
return jsonResult({
|
|
577
|
+
offset,
|
|
578
|
+
...(nextOffset !== undefined ? { nextOffset } : {}),
|
|
579
|
+
totalBytes: total,
|
|
580
|
+
text: slice,
|
|
581
|
+
});
|
|
582
|
+
},
|
|
583
|
+
async batchCall(args) {
|
|
584
|
+
const batchStarted = Date.now();
|
|
585
|
+
const settled = await Promise.allSettled(args.calls.map((c) => runCall({
|
|
586
|
+
...c,
|
|
587
|
+
resultMode: c.resultMode ?? args.resultMode,
|
|
588
|
+
timeoutMs: c.timeoutMs ?? args.timeoutMs,
|
|
589
|
+
maxRetries: c.maxRetries ?? args.maxRetries,
|
|
590
|
+
diagnostics: c.diagnostics ?? args.diagnostics,
|
|
591
|
+
}, "batch_call")));
|
|
592
|
+
const results = settled.map((s, i) => {
|
|
593
|
+
const address = args.calls[i].address;
|
|
594
|
+
if (s.status === "rejected") {
|
|
595
|
+
const message = msg(s.reason);
|
|
596
|
+
return {
|
|
597
|
+
address,
|
|
598
|
+
ok: false,
|
|
599
|
+
error: message,
|
|
600
|
+
errorDetails: errorDetails("batch_call_failed", message),
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
const r = s.value;
|
|
604
|
+
if (r.error) {
|
|
605
|
+
return {
|
|
606
|
+
address,
|
|
607
|
+
ok: false,
|
|
608
|
+
error: r.error.message,
|
|
609
|
+
errorDetails: r.error,
|
|
610
|
+
durationMs: r.durationMs,
|
|
611
|
+
attempts: r.attempts,
|
|
612
|
+
...((args.calls[i].diagnostics ?? args.diagnostics)
|
|
613
|
+
? { timing: r.timing }
|
|
614
|
+
: {}),
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
if ((args.calls[i].resultMode ?? args.resultMode) === "value") {
|
|
618
|
+
return {
|
|
619
|
+
address,
|
|
620
|
+
ok: true,
|
|
621
|
+
data: r.value,
|
|
622
|
+
durationMs: r.durationMs,
|
|
623
|
+
attempts: r.attempts,
|
|
624
|
+
...((args.calls[i].diagnostics ?? args.diagnostics)
|
|
625
|
+
? { timing: r.timing }
|
|
626
|
+
: {}),
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
return {
|
|
630
|
+
address,
|
|
631
|
+
ok: true,
|
|
632
|
+
result: r.toolResult.content,
|
|
633
|
+
durationMs: r.durationMs,
|
|
634
|
+
attempts: r.attempts,
|
|
635
|
+
...((args.calls[i].diagnostics ?? args.diagnostics)
|
|
636
|
+
? { timing: r.timing }
|
|
637
|
+
: {}),
|
|
638
|
+
};
|
|
639
|
+
});
|
|
640
|
+
return jsonResult({
|
|
641
|
+
results,
|
|
642
|
+
durationMs: Date.now() - batchStarted,
|
|
643
|
+
});
|
|
644
|
+
},
|
|
645
|
+
async authorizeConnector(args) {
|
|
646
|
+
const connector = registry.getConnector(args.connector);
|
|
647
|
+
if (!connector) {
|
|
648
|
+
return errorResult(`Unknown connector "${args.connector}"`);
|
|
649
|
+
}
|
|
650
|
+
if (!connector.startAuth) {
|
|
651
|
+
return errorResult(`Connector "${args.connector}" does not use downstream OAuth — its auth is static (headers/none), so there is nothing to authorize.`);
|
|
652
|
+
}
|
|
653
|
+
const ctx = registry.contextFor(connector.id, baseUrl, requestScope);
|
|
654
|
+
try {
|
|
655
|
+
const status = await connector.startAuth(ctx, { force: args.force });
|
|
656
|
+
if (status.state === "auth_required" && !status.authorizationUrl) {
|
|
657
|
+
// auth_required with nothing to open is a dead end for the operator.
|
|
658
|
+
return errorResult(`Connector "${connector.id}": authorization required but no URL is available — retry authorize_connector.`);
|
|
659
|
+
}
|
|
660
|
+
return jsonResult({
|
|
661
|
+
connector: connector.id,
|
|
662
|
+
status: status.state,
|
|
663
|
+
...(status.authorizationUrl
|
|
664
|
+
? {
|
|
665
|
+
authorizationUrl: status.authorizationUrl,
|
|
666
|
+
instructions: "Have the operator open authorizationUrl in a browser and complete the consent flow. The provider then redirects back to this server's /oauth/callback/<connector> route, which finishes the flow automatically. Re-run list_connectors afterwards to confirm status is ok.",
|
|
667
|
+
}
|
|
668
|
+
: {}),
|
|
669
|
+
...(status.message ? { message: status.message } : {}),
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
catch (err) {
|
|
673
|
+
return errorResult(msg(err));
|
|
674
|
+
}
|
|
675
|
+
finally {
|
|
676
|
+
// Auth state may have changed — even on a throw or a half-wiped force —
|
|
677
|
+
// so don't serve a stale tool list.
|
|
678
|
+
await registry.invalidateStored(connector.id);
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
const LIST_DESC = "List connectors with status, cached tool count, and recent real-call health. Use probe=false for a fast inventory; use probe=true (default) only to diagnose live health or authorization.";
|
|
684
|
+
const SEARCH_DESC = 'Start here when a tool address is unknown. Exact/name matches rank above description matches; an empty query browses all. includeSchemas="compact" usually removes the describe_tools round trip.';
|
|
685
|
+
const DESCRIBE_DESC = 'Inspect known tool addresses when search_tools did not include a sufficient schema. Returns descriptions, input/output schemas, and behavior annotations; format "compact" is the default.';
|
|
686
|
+
const CALL_DESC = 'Use for one tool explicitly annotated readOnlyHint: true. For 2–10 independent read-only calls use batch_call; for dependent steps or data reduction use execute_code when available. Unannotated, write-capable, and destructive tools are refused and require call_destructive_tool. fields selects JSON dot-paths, resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
|
|
687
|
+
const CALL_DESTRUCTIVE_DESC = "Invoke any tool that is not explicitly annotated readOnlyHint: true, including unannotated, write-capable, or destructive tools. The MCP destructiveHint on this meta-tool lets the host request human approval before execution. Use only after reviewing the downstream tool schema and consequences.";
|
|
688
|
+
const GET_RESULT_DESC = "Page a truncated result stashed by call_tool/batch_call. Input { id, offset?, maxBytes? } → { text, offset, nextOffset?, totalBytes } sliced by byte offset. Unknown/expired id is an error.";
|
|
689
|
+
const BATCH_DESC = "Use for 2–10 independent tools explicitly annotated readOnlyHint: true. Calls run in parallel with shared request-scoped clients; use execute_code when available instead for dependencies or in-sandbox reduction. Unannotated, write-capable, and destructive tools are refused. Batch timeout, safe retry, result mode, and diagnostics defaults may be overridden per call.";
|
|
690
|
+
const AUTHORIZE_DESC = "Use after a connector reports auth_required. Starts downstream OAuth and returns an authorizationUrl for the operator to open. force=true wipes stored credentials first and restarts consent.";
|
|
691
|
+
const SKILLS_DESC = 'List or fetch concise guidance for choosing among Connecta meta-tools. Call skills({ name: "usage" }) once when the routing workflow is unfamiliar; do not refetch it in the same task.';
|
|
692
|
+
/** Register the nine meta-tools onto an McpServer instance. */
|
|
693
|
+
export function registerMetaTools(server, registry, ctx) {
|
|
694
|
+
const mt = createMetaTools(registry, ctx.baseUrl, {
|
|
695
|
+
maxResultBytes: ctx.maxResultBytes,
|
|
696
|
+
activity: ctx.activity,
|
|
697
|
+
});
|
|
698
|
+
server.registerTool("skills", {
|
|
699
|
+
description: SKILLS_DESC,
|
|
700
|
+
inputSchema: { name: z.string().optional() },
|
|
701
|
+
annotations: {
|
|
702
|
+
readOnlyHint: true,
|
|
703
|
+
destructiveHint: false,
|
|
704
|
+
idempotentHint: true,
|
|
705
|
+
openWorldHint: false,
|
|
706
|
+
},
|
|
707
|
+
}, async (args) => mt.skills(args));
|
|
708
|
+
server.registerTool("list_connectors", {
|
|
709
|
+
description: LIST_DESC,
|
|
710
|
+
inputSchema: { probe: z.boolean().optional() },
|
|
711
|
+
}, async (args) => mt.listConnectors(args));
|
|
712
|
+
server.registerTool("search_tools", {
|
|
713
|
+
description: SEARCH_DESC,
|
|
714
|
+
inputSchema: {
|
|
715
|
+
query: z.string().optional(),
|
|
716
|
+
connector: z.string().optional(),
|
|
717
|
+
limit: z.number().int().positive().optional(),
|
|
718
|
+
offset: z.number().int().nonnegative().optional(),
|
|
719
|
+
fullDescriptions: z.boolean().optional(),
|
|
720
|
+
includeSchemas: z.enum(["compact", "json"]).optional(),
|
|
721
|
+
},
|
|
722
|
+
}, async (args) => mt.searchTools(args));
|
|
723
|
+
server.registerTool("describe_tools", {
|
|
724
|
+
description: DESCRIBE_DESC,
|
|
725
|
+
inputSchema: {
|
|
726
|
+
addresses: z.array(z.string()),
|
|
727
|
+
format: z.enum(["compact", "json"]).optional(),
|
|
728
|
+
fullDescriptions: z.boolean().optional(),
|
|
729
|
+
},
|
|
730
|
+
}, async (args) => mt.describeTools(args));
|
|
731
|
+
server.registerTool("call_tool", {
|
|
732
|
+
description: CALL_DESC,
|
|
733
|
+
inputSchema: {
|
|
734
|
+
address: z.string(),
|
|
735
|
+
args: z.record(z.string(), z.unknown()).optional(),
|
|
736
|
+
fields: z.array(z.string()).optional(),
|
|
737
|
+
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
738
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
739
|
+
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
740
|
+
diagnostics: z.boolean().optional(),
|
|
741
|
+
},
|
|
742
|
+
}, async (args) => mt.callTool(args));
|
|
743
|
+
server.registerTool("call_destructive_tool", {
|
|
744
|
+
description: CALL_DESTRUCTIVE_DESC,
|
|
745
|
+
inputSchema: {
|
|
746
|
+
address: z.string(),
|
|
747
|
+
args: z.record(z.string(), z.unknown()).optional(),
|
|
748
|
+
fields: z.array(z.string()).optional(),
|
|
749
|
+
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
750
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
751
|
+
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
752
|
+
diagnostics: z.boolean().optional(),
|
|
753
|
+
},
|
|
754
|
+
annotations: {
|
|
755
|
+
destructiveHint: true,
|
|
756
|
+
readOnlyHint: false,
|
|
757
|
+
openWorldHint: true,
|
|
758
|
+
},
|
|
759
|
+
}, async (args) => mt.callDestructiveTool(args));
|
|
760
|
+
server.registerTool("authorize_connector", {
|
|
761
|
+
description: AUTHORIZE_DESC,
|
|
762
|
+
inputSchema: {
|
|
763
|
+
connector: z.string(),
|
|
764
|
+
force: z.boolean().optional(),
|
|
765
|
+
},
|
|
766
|
+
}, async (args) => mt.authorizeConnector(args));
|
|
767
|
+
server.registerTool("get_result", {
|
|
768
|
+
description: GET_RESULT_DESC,
|
|
769
|
+
inputSchema: {
|
|
770
|
+
id: z.string(),
|
|
771
|
+
offset: z.number().int().nonnegative().optional(),
|
|
772
|
+
maxBytes: z.number().int().positive().optional(),
|
|
773
|
+
},
|
|
774
|
+
}, async (args) => mt.getResult(args));
|
|
775
|
+
server.registerTool("batch_call", {
|
|
776
|
+
description: BATCH_DESC,
|
|
777
|
+
inputSchema: {
|
|
778
|
+
calls: z
|
|
779
|
+
.array(z.object({
|
|
780
|
+
address: z.string(),
|
|
781
|
+
args: z.record(z.string(), z.unknown()).optional(),
|
|
782
|
+
fields: z.array(z.string()).optional(),
|
|
783
|
+
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
784
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
785
|
+
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
786
|
+
diagnostics: z.boolean().optional(),
|
|
787
|
+
}))
|
|
788
|
+
.min(1)
|
|
789
|
+
.max(10),
|
|
790
|
+
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
791
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
792
|
+
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
793
|
+
diagnostics: z.boolean().optional(),
|
|
794
|
+
},
|
|
795
|
+
}, async (args) => mt.batchCall(args));
|
|
796
|
+
}
|
|
797
|
+
//# sourceMappingURL=meta-tools.js.map
|