@zackbart/connecta 0.9.1 → 0.10.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 +96 -0
- package/README.md +43 -92
- package/dist/catalog-service.d.ts.map +1 -1
- package/dist/catalog-service.js +1 -4
- package/dist/catalog-service.js.map +1 -1
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +26 -0
- package/dist/errors.js.map +1 -1
- package/dist/execute.d.ts +3 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +79 -18
- package/dist/execute.js.map +1 -1
- package/dist/executor-result.d.ts.map +1 -1
- package/dist/executor-result.js +37 -6
- package/dist/executor-result.js.map +1 -1
- package/dist/executors/quickjs-protocol.d.ts +12 -0
- package/dist/executors/quickjs-protocol.d.ts.map +1 -1
- package/dist/executors/quickjs-protocol.js +14 -0
- package/dist/executors/quickjs-protocol.js.map +1 -1
- package/dist/executors/quickjs-runtime.d.ts.map +1 -1
- package/dist/executors/quickjs-runtime.js +6 -3
- package/dist/executors/quickjs-runtime.js.map +1 -1
- package/dist/executors/quickjs.d.ts.map +1 -1
- package/dist/executors/quickjs.js +10 -4
- package/dist/executors/quickjs.js.map +1 -1
- package/dist/index.d.ts +16 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +31 -0
- package/dist/index.js.map +1 -1
- package/dist/invocation.d.ts.map +1 -1
- package/dist/invocation.js +1 -4
- package/dist/invocation.js.map +1 -1
- package/dist/meta-tools.d.ts +26 -5
- package/dist/meta-tools.d.ts.map +1 -1
- package/dist/meta-tools.js +84 -40
- package/dist/meta-tools.js.map +1 -1
- package/dist/routes/mcp.d.ts.map +1 -1
- package/dist/routes/mcp.js +8 -2
- package/dist/routes/mcp.js.map +1 -1
- package/dist/routes/shared.d.ts +8 -2
- package/dist/routes/shared.d.ts.map +1 -1
- package/dist/routes/shared.js.map +1 -1
- package/dist/skills.d.ts +15 -3
- package/dist/skills.d.ts.map +1 -1
- package/dist/skills.js +63 -10
- package/dist/skills.js.map +1 -1
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +2 -2
- package/src/catalog-service.ts +1 -5
- package/src/errors.ts +28 -0
- package/src/execute.ts +123 -48
- package/src/executor-result.ts +50 -6
- package/src/executors/quickjs-protocol.ts +19 -0
- package/src/executors/quickjs-runtime.ts +6 -2
- package/src/executors/quickjs.ts +10 -3
- package/src/index.ts +52 -4
- package/src/invocation.ts +1 -5
- package/src/meta-tools.ts +116 -53
- package/src/routes/mcp.ts +8 -2
- package/src/routes/shared.ts +8 -1
- package/src/skills.ts +79 -9
- package/src/types.ts +15 -0
- package/src/version.ts +1 -1
package/src/meta-tools.ts
CHANGED
|
@@ -49,7 +49,11 @@ import {
|
|
|
49
49
|
normalizeTimeoutMs,
|
|
50
50
|
withAbortableTimeout,
|
|
51
51
|
} from "./timeout.js";
|
|
52
|
-
import type {
|
|
52
|
+
import type {
|
|
53
|
+
ConnectaSurface,
|
|
54
|
+
ConnectorStatus,
|
|
55
|
+
KVStorage,
|
|
56
|
+
} from "./types.js";
|
|
53
57
|
|
|
54
58
|
export {
|
|
55
59
|
MAX_DESCRIBE_ADDRESSES,
|
|
@@ -473,10 +477,12 @@ export interface SkillArgs {
|
|
|
473
477
|
}
|
|
474
478
|
|
|
475
479
|
/**
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
479
|
-
*
|
|
480
|
+
* Every base meta-tool handler over a registry — all nine, whichever surface is
|
|
481
|
+
* advertised, since folding a tool away only skips its registration and never
|
|
482
|
+
* its handler. Exported for direct testing; registerMetaTools() wires the ones
|
|
483
|
+
* this surface advertises onto an McpServer. `opts.defaultToolTimeoutMs`
|
|
484
|
+
* supplies a deadline for calls that don't carry one. (execute_code is
|
|
485
|
+
* registered separately by registerExecuteTool.)
|
|
480
486
|
*
|
|
481
487
|
* Deployment-wide result-size caps are read off the registry view rather than
|
|
482
488
|
* passed in: `ConnectaConfig.calls.maxResultBytes`, its per-connector override,
|
|
@@ -498,8 +504,15 @@ export function createMetaTools(
|
|
|
498
504
|
requestSignal?: AbortSignal;
|
|
499
505
|
/** Runtime continuation for the bounded tail of probe-owned teardown. */
|
|
500
506
|
defer?: DeferredWork;
|
|
507
|
+
/**
|
|
508
|
+
* The advertised surface, which the `skills` guidance must match: a
|
|
509
|
+
* code-first deployment never gets guidance naming a tool it does not
|
|
510
|
+
* advertise. Default `classic`.
|
|
511
|
+
*/
|
|
512
|
+
surface?: ConnectaSurface;
|
|
501
513
|
} = {},
|
|
502
514
|
) {
|
|
515
|
+
const surface: ConnectaSurface = opts.surface ?? "classic";
|
|
503
516
|
// Already normalized and warned about at registry construction.
|
|
504
517
|
const globalCap = registry.maxResultBytes;
|
|
505
518
|
const batchCap = registry.maxBatchResultBytes;
|
|
@@ -666,14 +679,14 @@ export function createMetaTools(
|
|
|
666
679
|
type: "text",
|
|
667
680
|
text:
|
|
668
681
|
'Available skills. Fetch one with skills({ name: "<name>" }).\n\n' +
|
|
669
|
-
listSkills(connectors)
|
|
682
|
+
listSkills(connectors, surface)
|
|
670
683
|
.map((skill) => `- \`${skill.name}\` — ${skill.description}`)
|
|
671
684
|
.join("\n"),
|
|
672
685
|
},
|
|
673
686
|
],
|
|
674
687
|
};
|
|
675
688
|
}
|
|
676
|
-
const skill = resolveSkill(args.name, connectors);
|
|
689
|
+
const skill = resolveSkill(args.name, connectors, surface);
|
|
677
690
|
if (!skill.found) return errorResult(skill.message);
|
|
678
691
|
return { content: [{ type: "text", text: skill.content }] };
|
|
679
692
|
},
|
|
@@ -1133,6 +1146,23 @@ const AUTHORIZE_DESC =
|
|
|
1133
1146
|
const SKILLS_DESC =
|
|
1134
1147
|
'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.';
|
|
1135
1148
|
|
|
1149
|
+
/**
|
|
1150
|
+
* Code-first replacements for the descriptions that route work between tools.
|
|
1151
|
+
* Every one of these mentions a tool the consolidated surface removed, so on a
|
|
1152
|
+
* code-first deployment the routing sentence has to point at the in-program
|
|
1153
|
+
* function that took the work over — a description naming `batch_call` on a
|
|
1154
|
+
* surface without one teaches a call that cannot succeed.
|
|
1155
|
+
*
|
|
1156
|
+
* The classic strings above are left byte-for-byte alone: classic is the
|
|
1157
|
+
* compatibility surface and the eval's control arm, and rewording it would
|
|
1158
|
+
* change what that control measures.
|
|
1159
|
+
*/
|
|
1160
|
+
const CODE_FIRST_SEARCH_DESC = `${SEARCH_DESC} Expand an ambiguous compact shape, or read exact JSON constraints, with connecta.describe inside execute_code.`;
|
|
1161
|
+
const CODE_FIRST_CALL_DESC =
|
|
1162
|
+
'Use for ONE tool explicitly annotated readOnlyHint: true — the cheapest path for a single cold call. For two or more calls, dependent steps, loops, joins, or data reduction use execute_code, whose connecta.call and connecta.batch reach the same tools. 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.';
|
|
1163
|
+
const CODE_FIRST_GET_RESULT_DESC =
|
|
1164
|
+
"Page a truncated result stashed by call_tool or call_destructive_tool; a program's oversized return is not paged, so reduce it in code instead. Input { id, offset?, maxBytes? } → { text, offset, nextOffset?, totalBytes } sliced by byte offset. maxBytes is a whole number of bytes >= 1 (omit for the deployment default) and offset a whole number of bytes >= 0; an offset inside a multi-byte character is moved back to that character's first byte and the offset served is returned. Unknown/expired id is an error.";
|
|
1165
|
+
|
|
1136
1166
|
/**
|
|
1137
1167
|
* Sentences appended to a meta-tool description only when this connection
|
|
1138
1168
|
* actually has connector guides. Tool descriptions are always-loaded context,
|
|
@@ -1191,7 +1221,17 @@ const CALL_INPUT_SCHEMA = {
|
|
|
1191
1221
|
diagnostics: z.boolean().optional(),
|
|
1192
1222
|
};
|
|
1193
1223
|
|
|
1194
|
-
/**
|
|
1224
|
+
/**
|
|
1225
|
+
* Register the base meta-tools onto an McpServer instance: nine on the classic
|
|
1226
|
+
* surface, six on the code-first one, where `list_connectors`,
|
|
1227
|
+
* `describe_tools`, and `batch_call` have folded into the program surface
|
|
1228
|
+
* (`registerExecuteTool` adds the seventh, `execute_code`).
|
|
1229
|
+
*
|
|
1230
|
+
* Only the registrations differ. Every handler still exists on the object
|
|
1231
|
+
* `createMetaTools` returns, and a folded tool's behavior is reached through
|
|
1232
|
+
* `connecta.search` / `connecta.describe` / `connecta.batch` inside a program —
|
|
1233
|
+
* the same code paths, one layer down.
|
|
1234
|
+
*/
|
|
1195
1235
|
export function registerMetaTools(
|
|
1196
1236
|
server: McpServer,
|
|
1197
1237
|
registry: RegistryView,
|
|
@@ -1203,9 +1243,14 @@ export function registerMetaTools(
|
|
|
1203
1243
|
activity?: ActivityRequestContext;
|
|
1204
1244
|
requestSignal?: AbortSignal;
|
|
1205
1245
|
defer?: DeferredWork;
|
|
1246
|
+
/** The advertised surface. Default `classic`. */
|
|
1247
|
+
surface?: ConnectaSurface;
|
|
1206
1248
|
},
|
|
1207
1249
|
): void {
|
|
1250
|
+
const surface: ConnectaSurface = ctx.surface ?? "classic";
|
|
1251
|
+
const codeFirst = surface === "code-first";
|
|
1208
1252
|
const mt = createMetaTools(registry, ctx.baseUrl, {
|
|
1253
|
+
surface,
|
|
1209
1254
|
...(ctx.defaultToolTimeoutMs !== undefined
|
|
1210
1255
|
? { defaultToolTimeoutMs: ctx.defaultToolTimeoutMs }
|
|
1211
1256
|
: {}),
|
|
@@ -1232,20 +1277,30 @@ export function registerMetaTools(
|
|
|
1232
1277
|
async (args) => mt.skills(args as SkillArgs),
|
|
1233
1278
|
);
|
|
1234
1279
|
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1280
|
+
// Folded on the code-first surface: a program browses the same inventory with
|
|
1281
|
+
// connecta.search({}) (every catalog) or connecta.search({ connector }) (one).
|
|
1282
|
+
// Live connector probing is an operator concern, not a model one — it stays on
|
|
1283
|
+
// the operator pages and /health, which is where the ethos puts observability.
|
|
1284
|
+
if (!codeFirst) {
|
|
1285
|
+
server.registerTool(
|
|
1286
|
+
"list_connectors",
|
|
1287
|
+
{
|
|
1288
|
+
description: LIST_DESC,
|
|
1289
|
+
inputSchema: z.object({ probe: z.boolean().optional() }),
|
|
1290
|
+
annotations: READ_ONLY_REMOTE,
|
|
1291
|
+
},
|
|
1292
|
+
async (args) => mt.listConnectors(args as ListArgs),
|
|
1293
|
+
);
|
|
1294
|
+
}
|
|
1244
1295
|
|
|
1245
1296
|
server.registerTool(
|
|
1246
1297
|
"search_tools",
|
|
1247
1298
|
{
|
|
1248
|
-
description: describedFor(
|
|
1299
|
+
description: describedFor(
|
|
1300
|
+
registry,
|
|
1301
|
+
codeFirst ? CODE_FIRST_SEARCH_DESC : SEARCH_DESC,
|
|
1302
|
+
"search",
|
|
1303
|
+
),
|
|
1249
1304
|
inputSchema: z.object({
|
|
1250
1305
|
query: z.string().optional(),
|
|
1251
1306
|
connector: z.string().optional(),
|
|
@@ -1259,24 +1314,28 @@ export function registerMetaTools(
|
|
|
1259
1314
|
async (args) => mt.searchTools(args as SearchArgs),
|
|
1260
1315
|
);
|
|
1261
1316
|
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1317
|
+
// Folded on the code-first surface: connecta.describe takes the same
|
|
1318
|
+
// addresses, format, and per-address error reporting inside a program.
|
|
1319
|
+
if (!codeFirst) {
|
|
1320
|
+
server.registerTool(
|
|
1321
|
+
"describe_tools",
|
|
1322
|
+
{
|
|
1323
|
+
description: describedFor(registry, DESCRIBE_DESC, "describe"),
|
|
1324
|
+
inputSchema: z.object({
|
|
1325
|
+
addresses: z.array(z.string()).max(MAX_DESCRIBE_ADDRESSES),
|
|
1326
|
+
format: z.enum(["compact", "json"]).optional(),
|
|
1327
|
+
fullDescriptions: z.boolean().optional(),
|
|
1328
|
+
}),
|
|
1329
|
+
annotations: READ_ONLY_REMOTE,
|
|
1330
|
+
},
|
|
1331
|
+
async (args) => mt.describeTools(args as DescribeArgs),
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1275
1334
|
|
|
1276
1335
|
server.registerTool(
|
|
1277
1336
|
"call_tool",
|
|
1278
1337
|
{
|
|
1279
|
-
description: CALL_DESC,
|
|
1338
|
+
description: codeFirst ? CODE_FIRST_CALL_DESC : CALL_DESC,
|
|
1280
1339
|
inputSchema: z.object(CALL_INPUT_SCHEMA),
|
|
1281
1340
|
// call_tool admits only tools that are themselves explicitly read-only;
|
|
1282
1341
|
// anything else is refused and routed to call_destructive_tool.
|
|
@@ -1321,7 +1380,7 @@ export function registerMetaTools(
|
|
|
1321
1380
|
server.registerTool(
|
|
1322
1381
|
"get_result",
|
|
1323
1382
|
{
|
|
1324
|
-
description: GET_RESULT_DESC,
|
|
1383
|
+
description: codeFirst ? CODE_FIRST_GET_RESULT_DESC : GET_RESULT_DESC,
|
|
1325
1384
|
inputSchema: z.object({
|
|
1326
1385
|
id: z.string(),
|
|
1327
1386
|
// Both bounds are the shared rules (isValidResultOffset,
|
|
@@ -1336,24 +1395,28 @@ export function registerMetaTools(
|
|
|
1336
1395
|
async (args) => mt.getResult(args as GetResultArgs),
|
|
1337
1396
|
);
|
|
1338
1397
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1398
|
+
// Folded on the code-first surface: connecta.batch runs the same 1–10
|
|
1399
|
+
// parallel read-only calls and returns the same typed per-call outcomes.
|
|
1400
|
+
if (!codeFirst) {
|
|
1401
|
+
server.registerTool(
|
|
1402
|
+
"batch_call",
|
|
1403
|
+
{
|
|
1404
|
+
description: BATCH_DESC,
|
|
1405
|
+
inputSchema: z.object({
|
|
1406
|
+
calls: z
|
|
1407
|
+
.array(z.object(CALL_INPUT_SCHEMA))
|
|
1408
|
+
.min(1)
|
|
1409
|
+
.max(10),
|
|
1410
|
+
resultMode: z.enum(["mcp", "value"]).optional(),
|
|
1411
|
+
timeoutMs: z.number().int().positive().optional(),
|
|
1412
|
+
maxRetries: z.number().int().min(0).max(2).optional(),
|
|
1413
|
+
diagnostics: z.boolean().optional(),
|
|
1414
|
+
}),
|
|
1415
|
+
// Same gate as call_tool: every call in the batch must be explicitly
|
|
1416
|
+
// read-only or the batch is refused.
|
|
1417
|
+
annotations: READ_ONLY_REMOTE,
|
|
1418
|
+
},
|
|
1419
|
+
async (args) => mt.batchCall(args as BatchArgs),
|
|
1420
|
+
);
|
|
1421
|
+
}
|
|
1359
1422
|
}
|
package/src/routes/mcp.ts
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "../executor-admission.js";
|
|
13
13
|
import { registerMetaTools } from "../meta-tools.js";
|
|
14
14
|
import type { RegistryView } from "../registry.js";
|
|
15
|
-
import {
|
|
15
|
+
import { instructionsFor } from "../skills.js";
|
|
16
16
|
import type { Logger } from "../types.js";
|
|
17
17
|
import {
|
|
18
18
|
authorize,
|
|
@@ -192,9 +192,13 @@ async function serveMcp(
|
|
|
192
192
|
registry: RegistryView,
|
|
193
193
|
runtimeContext?: RuntimeExecutionContext,
|
|
194
194
|
): Promise<Response> {
|
|
195
|
+
// One deployment-wide value, read once here so the instructions, the
|
|
196
|
+
// registered tools, and the guidance the `skills` tool serves cannot
|
|
197
|
+
// disagree about which surface this deployment advertises.
|
|
198
|
+
const surface = opts.surface ?? "classic";
|
|
195
199
|
const createServer = (): McpServer => {
|
|
196
200
|
const server = new McpServer(opts.serverInfo, {
|
|
197
|
-
instructions:
|
|
201
|
+
instructions: instructionsFor(surface),
|
|
198
202
|
cacheHints: {
|
|
199
203
|
"tools/list": {
|
|
200
204
|
ttlMs: 3_600_000,
|
|
@@ -219,6 +223,7 @@ async function serveMcp(
|
|
|
219
223
|
: undefined;
|
|
220
224
|
registerMetaTools(server, registry, {
|
|
221
225
|
baseUrl,
|
|
226
|
+
surface,
|
|
222
227
|
...(activity ? { activity } : {}),
|
|
223
228
|
...(opts.defaultToolTimeoutMs !== undefined
|
|
224
229
|
? { defaultToolTimeoutMs: opts.defaultToolTimeoutMs }
|
|
@@ -237,6 +242,7 @@ async function serveMcp(
|
|
|
237
242
|
if (opts.executor) {
|
|
238
243
|
registerExecuteTool(server, registry, {
|
|
239
244
|
baseUrl,
|
|
245
|
+
surface,
|
|
240
246
|
executor: opts.executor,
|
|
241
247
|
logger: opts.logger,
|
|
242
248
|
...(activity ? { activity } : {}),
|
package/src/routes/shared.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { AdmissionController } from "../executor-admission.js";
|
|
|
6
6
|
import type { Registry } from "../registry.js";
|
|
7
7
|
import type {
|
|
8
8
|
ConnectaBranding,
|
|
9
|
+
ConnectaSurface,
|
|
9
10
|
Executor,
|
|
10
11
|
InboundAuth,
|
|
11
12
|
Logger,
|
|
@@ -30,8 +31,14 @@ export interface ServerOptions {
|
|
|
30
31
|
probeTimeoutMs?: number;
|
|
31
32
|
/** Maximum simultaneous connector discovery operations. Default 4. */
|
|
32
33
|
discoveryConcurrency?: number;
|
|
33
|
-
/** When set, the execute_code meta-tool is registered on top of the
|
|
34
|
+
/** When set, the execute_code meta-tool is registered on top of the base surface. */
|
|
34
35
|
executor?: Executor;
|
|
36
|
+
/**
|
|
37
|
+
* The advertised model-facing surface. createConnecta() always resolves it
|
|
38
|
+
* from the executor; absent (a direct createFetchHandler() caller) is
|
|
39
|
+
* classic, and `code-first` is only ever set alongside an `executor`.
|
|
40
|
+
*/
|
|
41
|
+
surface?: ConnectaSurface;
|
|
35
42
|
/** Global FIFO boundary for all non-preflight `/mcp` requests. */
|
|
36
43
|
requestAdmission: AdmissionController;
|
|
37
44
|
/** Encrypted connector-credential storage backing the Credentials page. */
|
package/src/skills.ts
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
import type { Connector } from "./types.js";
|
|
1
|
+
import type { Connector, ConnectaSurface } from "./types.js";
|
|
2
2
|
|
|
3
3
|
export const CONNECTA_INSTRUCTIONS =
|
|
4
4
|
'Connecta exposes integrations behind meta-tools. Unknown address: use search_tools with 2–4 distinctive action/object terms, no initial limit, and includeSchemas="compact"; describe_tools only if that shape is ambiguous or exact JSON constraints are needed. Use call_tool for one explicitly read-only call, batch_call for 2–10 independent read-only calls, and execute_code (when available) only for dependencies, loops, joins, or substantial reduction — searching inside that one run rather than searching first. Use call_destructive_tool individually for unannotated, write-capable, or destructive tools. authorize_connector follows auth_required; get_result follows truncation. If this routing is unfamiliar, fetch skills({ name: "usage" }).';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* The instructions a code-first deployment loads (#224). It never names
|
|
8
|
+
* `list_connectors`, `describe_tools`, or `batch_call` — not even to say they
|
|
9
|
+
* are gone. Always-loaded text describes the surface that exists; a sentence
|
|
10
|
+
* about three tools this deployment does not have is context paid for the past,
|
|
11
|
+
* and a model that names one anyway gets an unknown-tool error, which is a
|
|
12
|
+
* cheaper correction than the tokens the disclaimer costs every request.
|
|
13
|
+
*/
|
|
14
|
+
export const CODE_FIRST_INSTRUCTIONS =
|
|
15
|
+
'Connecta exposes integrations behind seven meta-tools, and execute_code is the primary one: write an async arrow function and use connecta.search (empty query browses every catalog), connecta.describe, connecta.call, and connecta.batch inside it for discovery, two or more calls, dependent steps, loops, joins, and reducing large results before they reach you. For a single read at an unknown address, search_tools with 2–4 distinctive action/object terms and includeSchemas="compact", then one call_tool — a lone cold call is cheaper direct than through a program. Use call_destructive_tool individually for unannotated, write-capable, or destructive tools; authorize_connector follows auth_required; get_result follows truncation. If this routing is unfamiliar, fetch skills({ name: "usage" }).';
|
|
16
|
+
|
|
6
17
|
export const USAGE_SKILL = `# Connecta usage
|
|
7
18
|
|
|
8
19
|
## Choose the smallest execution tool
|
|
@@ -29,6 +40,33 @@ Connector namespace calls and \`connecta.call\` use the same read-only gate and
|
|
|
29
40
|
Skip code mode for one call, calls suited to \`batch_call\`, or tools lacking \`readOnlyHint: true\`. Return only the needed reduction.
|
|
30
41
|
`;
|
|
31
42
|
|
|
43
|
+
export const CODE_FIRST_USAGE_SKILL = `# Connecta usage
|
|
44
|
+
|
|
45
|
+
## The surface
|
|
46
|
+
|
|
47
|
+
Seven tools: \`execute_code\`, \`search_tools\`, \`call_tool\`, \`call_destructive_tool\`, \`authorize_connector\`, \`get_result\`, \`skills\`. Broad discovery and multi-call work live inside a program rather than in top-level tools.
|
|
48
|
+
|
|
49
|
+
## Choose the smallest execution tool
|
|
50
|
+
|
|
51
|
+
Use exact addresses returned by discovery; never invent one. Search with 2–4 distinctive action/object terms rather than the full request.
|
|
52
|
+
|
|
53
|
+
- One read at an unknown address: \`search_tools({ query, includeSchemas: "compact" })\`, then \`call_tool\` once. A lone cold call is cheaper direct than through a program.
|
|
54
|
+
- Anything wider — two or more calls, dependent steps, loops, joins, branching, browsing a whole catalog, or a result that must be reduced: one \`execute_code\` run.
|
|
55
|
+
- Any unannotated, write-capable, or destructive call: \`call_destructive_tool\`, individually and only after reviewing its schema and consequences. Generated code cannot make one.
|
|
56
|
+
- Truncated result: retry with \`fields\` when possible; otherwise page it with \`get_result\`.
|
|
57
|
+
- \`auth_required\`: use \`authorize_connector\`, give its recovery handoff to the operator, then retry the original call.
|
|
58
|
+
|
|
59
|
+
## Inside a program
|
|
60
|
+
|
|
61
|
+
One async arrow function. The only capabilities are one global per connector (\`<connectorId>.<toolName>(args)\`), the four \`connecta\` functions, and \`console.log\`.
|
|
62
|
+
|
|
63
|
+
- What exists: \`connecta.search({})\` browses every catalog and \`connecta.search({ connector: "<id>" })\` browses one — that inventory is what a program discovers with, and each match carries its \`address\` and annotations.
|
|
64
|
+
- Exact schemas for known addresses: \`connecta.describe({ addresses: [...] })\`; \`format: "json"\` only for exact constraints.
|
|
65
|
+
- Two to ten independent calls: \`connecta.batch([...])\`. Each outcome is \`{ address, ok: true, data }\` or \`{ address, ok: false, error, errorDetails: { code, retryable } }\`, which is also how a program tells a policy refusal from a transient failure.
|
|
66
|
+
- Search inside the run rather than searching first, and return only the reduction the answer needs — never raw payloads.
|
|
67
|
+
- Only tools annotated \`readOnlyHint: true\` are reachable; the read-only gate, credentials, and admission are enforced below the sandbox, so nothing a program does widens what it can reach.
|
|
68
|
+
`;
|
|
69
|
+
|
|
32
70
|
/**
|
|
33
71
|
* Appended to USAGE_SKILL only when the deployment actually has at least one
|
|
34
72
|
* connector guide. A deployment with none — every deployment that has not
|
|
@@ -41,6 +79,20 @@ export const CONNECTOR_GUIDES_SECTION = `
|
|
|
41
79
|
Some connectors here ship their own usage guide — preferred tools, address quirks, pagination conventions, rate-limit etiquette, query patterns. \`skills({})\` lists each one as \`connector:<connectorId>\`; fetch it with \`skills({ name: "connector:<connectorId>" })\`. \`search_tools\` and \`describe_tools\` set \`guide\` on matches whose connector has one. Read a connector's guide before working with it for the first time in a task.
|
|
42
80
|
`;
|
|
43
81
|
|
|
82
|
+
/** The same section, naming only surfaces a code-first deployment has. */
|
|
83
|
+
const CODE_FIRST_CONNECTOR_GUIDES_SECTION = `
|
|
84
|
+
## Per-connector guides
|
|
85
|
+
|
|
86
|
+
Some connectors here ship their own usage guide — preferred tools, address quirks, pagination conventions, rate-limit etiquette, query patterns. \`skills({})\` lists each one as \`connector:<connectorId>\`; fetch it with \`skills({ name: "connector:<connectorId>" })\`. \`search_tools\`, \`connecta.search\`, and \`connecta.describe\` set \`guide\` on matches whose connector has one. Read a connector's guide before working with it for the first time in a task.
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
/** The always-loaded MCP `instructions` string for `surface`. */
|
|
90
|
+
export function instructionsFor(surface: ConnectaSurface): string {
|
|
91
|
+
return surface === "code-first"
|
|
92
|
+
? CODE_FIRST_INSTRUCTIONS
|
|
93
|
+
: CONNECTA_INSTRUCTIONS;
|
|
94
|
+
}
|
|
95
|
+
|
|
44
96
|
/** True when at least one of `connectors` carries a usage guide. */
|
|
45
97
|
export function hasConnectorGuides(connectors: readonly Connector[]): boolean {
|
|
46
98
|
return connectors.some(
|
|
@@ -49,10 +101,19 @@ export function hasConnectorGuides(connectors: readonly Connector[]): boolean {
|
|
|
49
101
|
}
|
|
50
102
|
|
|
51
103
|
/** The built-in usage guide, plus the guides section when there is one to point at. */
|
|
52
|
-
function usageSkill(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
104
|
+
function usageSkill(
|
|
105
|
+
connectors: readonly Connector[],
|
|
106
|
+
surface: ConnectaSurface,
|
|
107
|
+
): string {
|
|
108
|
+
const base =
|
|
109
|
+
surface === "code-first" ? CODE_FIRST_USAGE_SKILL : USAGE_SKILL;
|
|
110
|
+
if (!hasConnectorGuides(connectors)) return base;
|
|
111
|
+
return (
|
|
112
|
+
base +
|
|
113
|
+
(surface === "code-first"
|
|
114
|
+
? CODE_FIRST_CONNECTOR_GUIDES_SECTION
|
|
115
|
+
: CONNECTOR_GUIDES_SECTION)
|
|
116
|
+
);
|
|
56
117
|
}
|
|
57
118
|
|
|
58
119
|
const AVAILABLE_SKILLS = [
|
|
@@ -60,6 +121,8 @@ const AVAILABLE_SKILLS = [
|
|
|
60
121
|
name: "usage",
|
|
61
122
|
description:
|
|
62
123
|
"How to choose among Connecta discovery, direct, batch, destructive, and code-mode tools.",
|
|
124
|
+
codeFirstDescription:
|
|
125
|
+
"How to route work between one execute_code program and Connecta's explicit call, authorization, and result tools.",
|
|
63
126
|
content: usageSkill,
|
|
64
127
|
},
|
|
65
128
|
] as const;
|
|
@@ -149,10 +212,14 @@ export interface SkillListing {
|
|
|
149
212
|
* carries a usage guide. Derived from the connector list passed in — the single
|
|
150
213
|
* place guide visibility is decided.
|
|
151
214
|
*/
|
|
152
|
-
export function listSkills(
|
|
215
|
+
export function listSkills(
|
|
216
|
+
connectors: readonly Connector[],
|
|
217
|
+
surface: ConnectaSurface = "classic",
|
|
218
|
+
): SkillListing[] {
|
|
153
219
|
const listing: SkillListing[] = AVAILABLE_SKILLS.map((skill) => ({
|
|
154
220
|
name: skill.name,
|
|
155
|
-
description:
|
|
221
|
+
description:
|
|
222
|
+
surface === "code-first" ? skill.codeFirstDescription : skill.description,
|
|
156
223
|
}));
|
|
157
224
|
for (const connector of connectors) {
|
|
158
225
|
const guide = connectorGuide(connector);
|
|
@@ -177,11 +244,14 @@ export type SkillLookup =
|
|
|
177
244
|
export function resolveSkill(
|
|
178
245
|
name: string,
|
|
179
246
|
connectors: readonly Connector[],
|
|
247
|
+
surface: ConnectaSurface = "classic",
|
|
180
248
|
): SkillLookup {
|
|
181
249
|
const builtIn = AVAILABLE_SKILLS.find((skill) => skill.name === name);
|
|
182
|
-
if (builtIn)
|
|
250
|
+
if (builtIn) {
|
|
251
|
+
return { found: true, content: builtIn.content(connectors, surface) };
|
|
252
|
+
}
|
|
183
253
|
const available = () =>
|
|
184
|
-
listSkills(connectors)
|
|
254
|
+
listSkills(connectors, surface)
|
|
185
255
|
.map((skill) => skill.name)
|
|
186
256
|
.join(", ");
|
|
187
257
|
if (name.startsWith(CONNECTOR_SKILL_PREFIX)) {
|
package/src/types.ts
CHANGED
|
@@ -304,6 +304,21 @@ export interface Connector {
|
|
|
304
304
|
): Promise<Response | null>;
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
+
/**
|
|
308
|
+
* Which model-facing surface a deployment advertises. The `executor` decides
|
|
309
|
+
* it; this type is how a deployment overrides that.
|
|
310
|
+
*
|
|
311
|
+
* - `code-first`: seven tools, the default wherever an executor is configured.
|
|
312
|
+
* `list_connectors`, `describe_tools`, and `batch_call` are not top-level
|
|
313
|
+
* tools; their behavior lives in `connecta.search`, `connecta.describe`, and
|
|
314
|
+
* `connecta.batch` inside a program.
|
|
315
|
+
* - `classic`: the nine base meta-tools, plus `execute_code` when an executor
|
|
316
|
+
* is configured. Without an executor it is what a deployment necessarily
|
|
317
|
+
* serves and the eval gate's control arm; with one it is the ten-tool shape
|
|
318
|
+
* the gate's incremental arm measures, and the only thing `surface` is for.
|
|
319
|
+
*/
|
|
320
|
+
export type ConnectaSurface = "classic" | "code-first";
|
|
321
|
+
|
|
307
322
|
/** Result of one sandboxed code execution. */
|
|
308
323
|
export interface ExecuteResult {
|
|
309
324
|
result: unknown;
|
package/src/version.ts
CHANGED