@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/README.md +43 -92
  3. package/dist/catalog-service.d.ts.map +1 -1
  4. package/dist/catalog-service.js +1 -4
  5. package/dist/catalog-service.js.map +1 -1
  6. package/dist/errors.d.ts +5 -0
  7. package/dist/errors.d.ts.map +1 -1
  8. package/dist/errors.js +26 -0
  9. package/dist/errors.js.map +1 -1
  10. package/dist/execute.d.ts +3 -1
  11. package/dist/execute.d.ts.map +1 -1
  12. package/dist/execute.js +79 -18
  13. package/dist/execute.js.map +1 -1
  14. package/dist/executor-result.d.ts.map +1 -1
  15. package/dist/executor-result.js +37 -6
  16. package/dist/executor-result.js.map +1 -1
  17. package/dist/executors/quickjs-protocol.d.ts +12 -0
  18. package/dist/executors/quickjs-protocol.d.ts.map +1 -1
  19. package/dist/executors/quickjs-protocol.js +14 -0
  20. package/dist/executors/quickjs-protocol.js.map +1 -1
  21. package/dist/executors/quickjs-runtime.d.ts.map +1 -1
  22. package/dist/executors/quickjs-runtime.js +6 -3
  23. package/dist/executors/quickjs-runtime.js.map +1 -1
  24. package/dist/executors/quickjs.d.ts.map +1 -1
  25. package/dist/executors/quickjs.js +10 -4
  26. package/dist/executors/quickjs.js.map +1 -1
  27. package/dist/index.d.ts +16 -6
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +31 -0
  30. package/dist/index.js.map +1 -1
  31. package/dist/invocation.d.ts.map +1 -1
  32. package/dist/invocation.js +1 -4
  33. package/dist/invocation.js.map +1 -1
  34. package/dist/meta-tools.d.ts +26 -5
  35. package/dist/meta-tools.d.ts.map +1 -1
  36. package/dist/meta-tools.js +84 -40
  37. package/dist/meta-tools.js.map +1 -1
  38. package/dist/routes/mcp.d.ts.map +1 -1
  39. package/dist/routes/mcp.js +8 -2
  40. package/dist/routes/mcp.js.map +1 -1
  41. package/dist/routes/shared.d.ts +8 -2
  42. package/dist/routes/shared.d.ts.map +1 -1
  43. package/dist/routes/shared.js.map +1 -1
  44. package/dist/skills.d.ts +15 -3
  45. package/dist/skills.d.ts.map +1 -1
  46. package/dist/skills.js +63 -10
  47. package/dist/skills.js.map +1 -1
  48. package/dist/types.d.ts +14 -0
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/version.d.ts +1 -1
  51. package/dist/version.d.ts.map +1 -1
  52. package/dist/version.js +1 -1
  53. package/dist/version.js.map +1 -1
  54. package/package.json +2 -2
  55. package/src/catalog-service.ts +1 -5
  56. package/src/errors.ts +28 -0
  57. package/src/execute.ts +123 -48
  58. package/src/executor-result.ts +50 -6
  59. package/src/executors/quickjs-protocol.ts +19 -0
  60. package/src/executors/quickjs-runtime.ts +6 -2
  61. package/src/executors/quickjs.ts +10 -3
  62. package/src/index.ts +52 -4
  63. package/src/invocation.ts +1 -5
  64. package/src/meta-tools.ts +116 -53
  65. package/src/routes/mcp.ts +8 -2
  66. package/src/routes/shared.ts +8 -1
  67. package/src/skills.ts +79 -9
  68. package/src/types.ts +15 -0
  69. 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 { ConnectorStatus, KVStorage } from "./types.js";
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
- * The nine meta-tool handlers over a registry. Exported for direct testing;
477
- * registerMetaTools() wires them onto an McpServer. `opts.defaultToolTimeoutMs`
478
- * supplies a deadline for calls that don't carry one. (execute_code, the
479
- * optional tenth tool, is registered separately by registerExecuteTool.)
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
- /** Register the nine meta-tools onto an McpServer instance. */
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
- server.registerTool(
1236
- "list_connectors",
1237
- {
1238
- description: LIST_DESC,
1239
- inputSchema: z.object({ probe: z.boolean().optional() }),
1240
- annotations: READ_ONLY_REMOTE,
1241
- },
1242
- async (args) => mt.listConnectors(args as ListArgs),
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(registry, SEARCH_DESC, "search"),
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
- server.registerTool(
1263
- "describe_tools",
1264
- {
1265
- description: describedFor(registry, DESCRIBE_DESC, "describe"),
1266
- inputSchema: z.object({
1267
- addresses: z.array(z.string()).max(MAX_DESCRIBE_ADDRESSES),
1268
- format: z.enum(["compact", "json"]).optional(),
1269
- fullDescriptions: z.boolean().optional(),
1270
- }),
1271
- annotations: READ_ONLY_REMOTE,
1272
- },
1273
- async (args) => mt.describeTools(args as DescribeArgs),
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
- server.registerTool(
1340
- "batch_call",
1341
- {
1342
- description: BATCH_DESC,
1343
- inputSchema: z.object({
1344
- calls: z
1345
- .array(z.object(CALL_INPUT_SCHEMA))
1346
- .min(1)
1347
- .max(10),
1348
- resultMode: z.enum(["mcp", "value"]).optional(),
1349
- timeoutMs: z.number().int().positive().optional(),
1350
- maxRetries: z.number().int().min(0).max(2).optional(),
1351
- diagnostics: z.boolean().optional(),
1352
- }),
1353
- // Same gate as call_tool: every call in the batch must be explicitly
1354
- // read-only or the batch is refused.
1355
- annotations: READ_ONLY_REMOTE,
1356
- },
1357
- async (args) => mt.batchCall(args as BatchArgs),
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 { CONNECTA_INSTRUCTIONS } from "../skills.js";
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: CONNECTA_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 } : {}),
@@ -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 nine. */
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(connectors: readonly Connector[]): string {
53
- return hasConnectorGuides(connectors)
54
- ? USAGE_SKILL + CONNECTOR_GUIDES_SECTION
55
- : USAGE_SKILL;
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(connectors: readonly Connector[]): SkillListing[] {
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: skill.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) return { found: true, content: builtIn.content(connectors) };
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
@@ -4,4 +4,4 @@
4
4
  * a bump that forgets this file fails the build rather than shipping a stale
5
5
  * version to `/health` and to downstream MCP handshakes.
6
6
  */
7
- export const CONNECTA_VERSION = "0.9.1";
7
+ export const CONNECTA_VERSION = "0.10.0";