@zackbart/connecta 0.10.6 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/AGENTS.md +8 -6
  2. package/CHANGELOG.md +80 -0
  3. package/README.md +5 -4
  4. package/bin/connecta.mjs +0 -7
  5. package/dist/activity.d.ts.map +1 -1
  6. package/dist/activity.js.map +1 -1
  7. package/dist/apps-shell.d.ts +38 -0
  8. package/dist/apps-shell.d.ts.map +1 -0
  9. package/dist/apps-shell.js +175 -0
  10. package/dist/apps-shell.js.map +1 -0
  11. package/dist/catalog-service.d.ts +2 -17
  12. package/dist/catalog-service.d.ts.map +1 -1
  13. package/dist/catalog-service.js +4 -6
  14. package/dist/catalog-service.js.map +1 -1
  15. package/dist/connectors/api.d.ts +2 -2
  16. package/dist/connectors/remote-mcp.d.ts +1 -1
  17. package/dist/errors.d.ts +1 -3
  18. package/dist/errors.d.ts.map +1 -1
  19. package/dist/errors.js +1 -1
  20. package/dist/errors.js.map +1 -1
  21. package/dist/execute.d.ts +37 -8
  22. package/dist/execute.d.ts.map +1 -1
  23. package/dist/execute.js +137 -42
  24. package/dist/execute.js.map +1 -1
  25. package/dist/executor-admission.d.ts +8 -0
  26. package/dist/executor-admission.d.ts.map +1 -1
  27. package/dist/executor-admission.js +11 -0
  28. package/dist/executor-admission.js.map +1 -1
  29. package/dist/executors/quickjs.d.ts.map +1 -1
  30. package/dist/executors/quickjs.js +2 -2
  31. package/dist/executors/quickjs.js.map +1 -1
  32. package/dist/index.d.ts +15 -31
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +23 -37
  35. package/dist/index.js.map +1 -1
  36. package/dist/invocation.js +2 -2
  37. package/dist/invocation.js.map +1 -1
  38. package/dist/meta-tools.d.ts +19 -58
  39. package/dist/meta-tools.d.ts.map +1 -1
  40. package/dist/meta-tools.js +34 -431
  41. package/dist/meta-tools.js.map +1 -1
  42. package/dist/registry.d.ts +1 -10
  43. package/dist/registry.d.ts.map +1 -1
  44. package/dist/registry.js +3 -15
  45. package/dist/registry.js.map +1 -1
  46. package/dist/routes/mcp.d.ts.map +1 -1
  47. package/dist/routes/mcp.js +67 -30
  48. package/dist/routes/mcp.js.map +1 -1
  49. package/dist/routes/shared.d.ts +5 -11
  50. package/dist/routes/shared.d.ts.map +1 -1
  51. package/dist/routes/shared.js.map +1 -1
  52. package/dist/server.js +5 -4
  53. package/dist/server.js.map +1 -1
  54. package/dist/skills.d.ts +8 -18
  55. package/dist/skills.d.ts.map +1 -1
  56. package/dist/skills.js +13 -60
  57. package/dist/skills.js.map +1 -1
  58. package/dist/types.d.ts +6 -20
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/version.d.ts +1 -1
  61. package/dist/version.js +1 -1
  62. package/documentation/code-first-exploration.md +16 -16
  63. package/documentation/code-mode.md +130 -41
  64. package/documentation/connectors.md +1 -1
  65. package/documentation/mcp-2026-07-28.md +1 -1
  66. package/documentation/mcp-ui-design.md +382 -0
  67. package/documentation/meta-tools.md +30 -43
  68. package/documentation/rich-output-design.md +4 -4
  69. package/ethos.md +7 -2
  70. package/examples/node/README.md +1 -2
  71. package/examples/node/src/index.ts +1 -3
  72. package/examples/worker/README.md +8 -13
  73. package/examples/worker/src/index.ts +6 -14
  74. package/examples/worker/wrangler.jsonc +3 -6
  75. package/package.json +1 -1
  76. package/src/activity.ts +5 -0
  77. package/src/apps-shell.ts +179 -0
  78. package/src/catalog-service.ts +6 -26
  79. package/src/connectors/api.ts +2 -2
  80. package/src/connectors/remote-mcp.ts +1 -1
  81. package/src/errors.ts +2 -2
  82. package/src/execute.ts +150 -49
  83. package/src/executor-admission.ts +12 -0
  84. package/src/executors/quickjs.ts +2 -1
  85. package/src/index.ts +40 -69
  86. package/src/invocation.ts +2 -2
  87. package/src/meta-tools.ts +38 -565
  88. package/src/registry.ts +2 -33
  89. package/src/routes/mcp.ts +79 -30
  90. package/src/routes/shared.ts +4 -11
  91. package/src/server.ts +7 -7
  92. package/src/skills.ts +11 -74
  93. package/src/types.ts +6 -21
  94. package/src/version.ts +1 -1
  95. package/templates/node/README.md +2 -1
  96. package/templates/node/package.json +1 -1
  97. package/templates/node/src/index.ts +1 -1
@@ -1,12 +1,10 @@
1
1
  import { z } from "zod";
2
2
  import { boundedDiscoveryText, CatalogService, DEFAULT_SEARCH_LIMIT, DiscoveryPolicyError, groupedSearchResult, MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT, } from "./catalog-service.js";
3
- import { mapSettledWithConcurrency, resolveDiscoveryConcurrency, } from "./concurrency.js";
4
- import { closeConnectorScope, } from "./connector-scope.js";
5
- import { boundedEchoText, classifyCallError, echoedCallArgs, messageLooksRetryable, } from "./errors.js";
3
+ import { resolveDiscoveryConcurrency } from "./concurrency.js";
6
4
  import { InvocationService, MAX_RETRY_BACKOFF_MS, retryBackoffMs, } from "./invocation.js";
7
5
  import { isValidMaxResultBytes, MIN_MAX_RESULT_BYTES, resolveMaxResultBytes, } from "./registry.js";
8
6
  import { hasConnectorGuides, listSkills, resolveSkill, } from "./skills.js";
9
- import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs, withAbortableTimeout, } from "./timeout.js";
7
+ import { DEFAULT_PROBE_TIMEOUT_MS, normalizeTimeoutMs, } from "./timeout.js";
10
8
  export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_RETRY_BACKOFF_MS, MAX_SEARCH_LIMIT, retryBackoffMs, };
11
9
  const RESULT_TTL_SECONDS = 900;
12
10
  const enc = new TextEncoder();
@@ -25,9 +23,6 @@ export function errorResult(message) {
25
23
  function msg(err) {
26
24
  return err instanceof Error ? err.message : String(err);
27
25
  }
28
- function errorDetails(code, message) {
29
- return { code, message, retryable: messageLooksRetryable(message) };
30
- }
31
26
  function discoveryErrorResult(error) {
32
27
  const result = jsonResult({
33
28
  error: {
@@ -612,86 +607,6 @@ async function stashResult(text, results, totalBytes) {
612
607
  },
613
608
  };
614
609
  }
615
- /**
616
- * Keep an oversized batch's inline outcome summary at fixed string overhead.
617
- * The same clamp the error envelopes use — one budget, one marker, defined
618
- * once in `errors.ts` so the two cannot drift apart.
619
- */
620
- function batchSummaryString(value) {
621
- return boundedEchoText(value);
622
- }
623
- /** Candidate addresses kept in an oversized batch's summary of one ambiguity. */
624
- const MAX_SUMMARY_ADDRESSES = 10;
625
- /**
626
- * A recovery route rebuilt field by field so the summary above keeps its
627
- * promise. Spreading `nextAction` through raw would reopen the hole this
628
- * function closes: every variant carries free-form strings, and one of them
629
- * carries the caller's arguments, which is exactly the payload an oversized
630
- * batch was already too large to hold.
631
- */
632
- function batchSummaryNextAction(nextAction) {
633
- if ("function" in nextAction) {
634
- // A batch runs on the top-level catalog, whose search route is the tool, so
635
- // the function-keyed discovery variant does not arrive here today. Rebuild
636
- // it anyway: a guard that silently dropped an unrecognized route would turn
637
- // a recovery record into nothing at exactly the moment one is needed.
638
- if (nextAction.function === "connecta.search") {
639
- return {
640
- function: "connecta.search",
641
- arguments: batchSummarySearchArgs(nextAction.arguments),
642
- purpose: batchSummaryString(nextAction.purpose),
643
- };
644
- }
645
- // Say so when the candidate list is clipped. The unclipped purpose reads
646
- // "choose the intended canonical address", which is a lie about a list
647
- // that no longer contains every candidate — and the caller has no other
648
- // way to learn that the address it wants was the eleventh.
649
- const candidates = nextAction.addresses.slice(0, MAX_SUMMARY_ADDRESSES);
650
- return {
651
- function: nextAction.function,
652
- addresses: candidates.map(batchSummaryString),
653
- purpose: batchSummaryString(candidates.length < nextAction.addresses.length
654
- ? `${nextAction.purpose} Showing the first ${candidates.length} of ` +
655
- `${nextAction.addresses.length} candidates; re-run the call on its ` +
656
- "own to see them all."
657
- : nextAction.purpose),
658
- };
659
- }
660
- if (nextAction.tool === "authorize_connector") {
661
- return {
662
- tool: "authorize_connector",
663
- arguments: {
664
- connector: batchSummaryString(nextAction.arguments.connector),
665
- },
666
- operatorHandoff: batchSummaryString(nextAction.operatorHandoff),
667
- };
668
- }
669
- if (nextAction.tool === "call_destructive_tool") {
670
- return {
671
- tool: "call_destructive_tool",
672
- arguments: {
673
- address: batchSummaryString(nextAction.arguments.address),
674
- ...echoedCallArgs(nextAction.arguments.args),
675
- },
676
- purpose: batchSummaryString(nextAction.purpose),
677
- };
678
- }
679
- return {
680
- tool: "search_tools",
681
- arguments: batchSummarySearchArgs(nextAction.arguments),
682
- purpose: batchSummaryString(nextAction.purpose),
683
- };
684
- }
685
- /** The scoping arguments both discovery routes carry, bounded the same way. */
686
- function batchSummarySearchArgs(args) {
687
- return {
688
- query: batchSummaryString(args.query),
689
- ...(args.connector !== undefined
690
- ? { connector: batchSummaryString(args.connector) }
691
- : {}),
692
- includeSchemas: "compact",
693
- };
694
- }
695
610
  /**
696
611
  * Return `text` as a single content block; if it exceeds `cap` bytes, stash the
697
612
  * full text and return the first `cap` bytes followed by a JSON truncation
@@ -782,36 +697,29 @@ async function guardContent(content, results, cap) {
782
697
  }
783
698
  /**
784
699
  * The sentence that closes the OAuth handoff, telling the operator's agent how
785
- * to confirm the flow landed. `authorize_connector` is registered on both
786
- * surfaces but `list_connectors` is not, so the classic status check cannot be
787
- * the only one offered: a code-first agent handed that advice gets an
788
- * unknown-tool error at exactly the moment it is trying to recover. It gets the
789
- * check its own surface serves instead — the same folded-name defect as the
790
- * describe path (#261), one tool result further along.
700
+ * to confirm the flow landed through the one surface it can call.
791
701
  */
792
- function oauthFollowUp(surface, connectorId) {
793
- return surface === "code-first"
794
- ? `Then retry the original call; connecta.search({ connector: ${JSON.stringify(connectorId)} }) inside execute_code confirms the catalog now loads.`
795
- : "Re-run list_connectors afterwards to confirm status is ok.";
702
+ function oauthFollowUp(connectorId) {
703
+ return `Then retry the original call; connecta.search({ connector: ${JSON.stringify(connectorId)} }) inside execute_code confirms the catalog now loads.`;
796
704
  }
797
705
  /**
798
- * Every base meta-tool handler over a registry all nine, whichever surface is
799
- * advertised, since folding a tool away only skips its registration and never
800
- * its handler. Exported for direct testing; registerMetaTools() wires the ones
801
- * this surface advertises onto an McpServer. `opts.defaultToolTimeoutMs`
802
- * supplies a deadline for calls that don't carry one. (execute_code is
803
- * registered separately by registerExecuteTool.)
706
+ * Every meta-tool handler over a registry, one per registered tool. Exported for
707
+ * direct testing; registerMetaTools() wires the six explicit tools onto an
708
+ * McpServer. `opts.defaultToolTimeoutMs` supplies a deadline for calls that
709
+ * don't carry one. (execute_code is registered separately by
710
+ * registerExecuteTool, and builds its own services over the same registry.)
711
+ *
712
+ * What execute_code shares with these handlers is the services layer beneath
713
+ * them — `CatalogService` and `InvocationService` — not the handlers, which no
714
+ * in-program path calls.
804
715
  *
805
716
  * Deployment-wide result-size caps are read off the registry view rather than
806
- * passed in: `ConnectaConfig.calls.maxResultBytes`, its per-connector override,
807
- * and the independent `calls.maxBatchResultBytes` final-envelope boundary each
808
- * have one runtime source of truth.
717
+ * passed in: `ConnectaConfig.calls.maxResultBytes` and its per-connector
718
+ * override each have one runtime source of truth.
809
719
  */
810
720
  export function createMetaTools(registry, baseUrl, opts = {}) {
811
- const surface = opts.surface ?? "classic";
812
721
  // Already normalized and warned about at registry construction.
813
722
  const globalCap = registry.maxResultBytes;
814
- const batchCap = registry.maxBatchResultBytes;
815
723
  const defaultToolTimeoutMs = normalizeTimeoutMs(opts.defaultToolTimeoutMs);
816
724
  const probeTimeoutMs = normalizeTimeoutMs(opts.probeTimeoutMs) ?? DEFAULT_PROBE_TIMEOUT_MS;
817
725
  const discoveryConcurrency = resolveDiscoveryConcurrency(opts.discoveryConcurrency);
@@ -823,16 +731,10 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
823
731
  requestScope,
824
732
  probeTimeoutMs,
825
733
  concurrency: discoveryConcurrency,
826
- // These handlers are the top-level tools, so the route is the advertised
827
- // one; in-program describes route through connecta.describe instead and
828
- // are built with their own CatalogService in execute.ts.
829
- describeRoute: surface === "code-first" ? "connecta.describe" : "describe_tools",
830
- // searchRoute keeps its default: unlike describe_tools, search_tools is
831
- // served by both advertised surfaces, so a top-level handler has nothing to
832
- // derive. Only an in-program caller needs to be sent to connecta.search.
734
+ // searchRoute keeps its top-level default. In-program callers use a
735
+ // separate CatalogService configured for connecta.search.
833
736
  });
834
737
  const invocation = new InvocationService(registry, catalog, opts.activity);
835
- const withProbeDeadline = (label, operation) => withAbortableTimeout((signal) => operation({ signal, timeoutMs: probeTimeoutMs }), probeTimeoutMs, label);
836
738
  /** MCP adapter: shared invocation semantics plus MCP-only result shaping. */
837
739
  async function runCall(call, source, options = {}) {
838
740
  const results = registry.resultsStorage();
@@ -854,7 +756,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
854
756
  processResult: async (result, resolved) => {
855
757
  // Result-size cap for THIS call: the connector's own override wins,
856
758
  // then the deployment-wide value, then the built-in default (already
857
- // folded into `globalCap`). Resolved per call so one batch_call can
759
+ // folded into `globalCap`). Resolved per call so one request can
858
760
  // mix a tight-capped connector with siblings on the global cap. An
859
761
  // override the registry already warned about at startup is dropped
860
762
  // here, so the connector simply inherits `globalCap`.
@@ -958,130 +860,24 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
958
860
  {
959
861
  type: "text",
960
862
  text: 'Available skills. Fetch one with skills({ name: "<name>" }).\n\n' +
961
- listSkills(connectors, surface)
863
+ listSkills(connectors)
962
864
  .map((skill) => `- \`${skill.name}\` — ${skill.description}`)
963
865
  .join("\n"),
964
866
  },
965
867
  ],
966
868
  };
967
869
  }
968
- const skill = resolveSkill(args.name, connectors, surface);
870
+ const skill = resolveSkill(args.name, connectors);
969
871
  if (!skill.found)
970
872
  return errorResult(skill.message);
971
873
  return { content: [{ type: "text", text: skill.content }] };
972
874
  },
973
- async listConnectors(args = {}) {
974
- const probe = args.probe ?? true;
975
- // Live inventory owns a short-lived scope separate from the request's
976
- // call scope. Closing it cannot defeat call_tool/batch/execute_code reuse.
977
- const connectors = registry.listConnectors();
978
- const scope = probe ? {} : requestScope;
979
- const inspect = async (c) => {
980
- const statusStarted = Date.now();
981
- const observed = registry.healthFor(c.id);
982
- const drift = await registry.credentialDriftFor(c.id);
983
- let status;
984
- if (drift) {
985
- status = { state: "auth_required", message: drift };
986
- }
987
- else if (probe) {
988
- try {
989
- status = await withProbeDeadline(`list_connectors probe of "${c.id}"`, (options) => registry.statusFor(c.id, baseUrl, scope, options));
990
- }
991
- catch (err) {
992
- // A probe that outran probeTimeoutMs (or otherwise threw)
993
- // degrades this connector to an error status rather than
994
- // hanging the whole list_connectors call.
995
- status = { state: "error", message: msg(err) };
996
- }
997
- }
998
- else {
999
- const derived = observed?.consecutiveFailures && observed.consecutiveFailures > 0
1000
- ? "error"
1001
- : registry.hasObservedSuccess(c.id) || c.kind === "api"
1002
- ? "ok"
1003
- : "unknown";
1004
- status = {
1005
- state: derived,
1006
- ...(observed?.lastError ? { message: observed.lastError } : {}),
1007
- };
1008
- }
1009
- // Stamped after any live probe so the response reports when its
1010
- // observation completed, not when a potentially slow request began.
1011
- const checkedAt = new Date().toISOString();
1012
- let tools = registry.peekTools(c.id);
1013
- // An auth_required status may have just started OAuth. A second
1014
- // listTools probe would overwrite its state/verifier while returning
1015
- // the first (now stale) authorization URL.
1016
- if (probe && status.state === "ok") {
1017
- try {
1018
- tools = await withProbeDeadline(`list_connectors catalog refresh of "${c.id}"`, (options) => registry.refreshTools(c.id, baseUrl, scope, options));
1019
- registry.recordSuccess(c.id, Date.now() - statusStarted);
1020
- }
1021
- catch (err) {
1022
- const details = classifyCallError(err);
1023
- if (details.code === "auth_required") {
1024
- let authStatus;
1025
- try {
1026
- authStatus = await withProbeDeadline(`list_connectors authorization status of "${c.id}"`, (options) => registry.statusFor(c.id, baseUrl, scope, options));
1027
- }
1028
- catch {
1029
- // The typed auth verdict is still authoritative; this second
1030
- // read exists only to recover the connector's pending URL.
1031
- }
1032
- status =
1033
- authStatus?.state === "auth_required"
1034
- ? authStatus
1035
- : {
1036
- state: "auth_required",
1037
- message: details.message,
1038
- };
1039
- }
1040
- else {
1041
- status = { state: "error", message: msg(err) };
1042
- }
1043
- registry.recordFailure(c.id, Date.now() - statusStarted, err);
1044
- }
1045
- }
1046
- const latencyMs = Date.now() - statusStarted;
1047
- const latestObserved = registry.healthFor(c.id);
1048
- return {
1049
- id: c.id,
1050
- ...(c.title ? { title: c.title } : {}),
1051
- description: c.description,
1052
- toolCount: tools?.length ?? 0,
1053
- status: status.state,
1054
- checkedAt,
1055
- latencyMs,
1056
- probe,
1057
- ...(latestObserved ?? observed),
1058
- ...("authorizationUrl" in status &&
1059
- status.authorizationUrl && {
1060
- authorizationUrl: status.authorizationUrl,
1061
- }),
1062
- ...(status.message && { message: status.message }),
1063
- };
1064
- };
1065
- const settled = await mapSettledWithConcurrency(connectors, discoveryConcurrency, inspect);
1066
- if (probe) {
1067
- await mapSettledWithConcurrency(connectors, discoveryConcurrency, (connector) => closeConnectorScope(connector, registry.contextFor(connector.id, baseUrl, scope), opts.defer));
1068
- }
1069
- const out = settled.map((result) => {
1070
- if (result.status === "rejected")
1071
- throw result.reason;
1072
- return result.value;
1073
- });
1074
- return jsonResult({ connectors: out });
1075
- },
1076
875
  async searchTools(args) {
1077
876
  return discoveryResult(async () => groupedSearchResult(await catalog.search({
1078
877
  ...args,
1079
878
  includeSchemaKeys: args.includeSchemas !== undefined,
1080
879
  })), "Request a smaller limit, omit fullDescriptions, or use compact schemas.");
1081
880
  },
1082
- async describeTools(args) {
1083
- return discoveryResult(async () => ({ tools: await catalog.describe(args) }), 'Split the address list or use format: "compact".');
1084
- },
1085
881
  async callTool(args) {
1086
882
  return (await runCall(args, "call_tool")).toolResult;
1087
883
  },
@@ -1137,131 +933,6 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
1137
933
  text: slice,
1138
934
  });
1139
935
  },
1140
- async batchCall(args) {
1141
- const batchStarted = Date.now();
1142
- const settled = await Promise.allSettled(args.calls.map((c) => runCall({
1143
- ...c,
1144
- ...((c.resultMode ?? args.resultMode) !== undefined
1145
- ? { resultMode: c.resultMode ?? args.resultMode }
1146
- : {}),
1147
- ...((c.timeoutMs ?? args.timeoutMs) !== undefined
1148
- ? { timeoutMs: c.timeoutMs ?? args.timeoutMs }
1149
- : {}),
1150
- ...((c.maxRetries ?? args.maxRetries) !== undefined
1151
- ? { maxRetries: c.maxRetries ?? args.maxRetries }
1152
- : {}),
1153
- ...((c.diagnostics ?? args.diagnostics) !== undefined
1154
- ? { diagnostics: c.diagnostics ?? args.diagnostics }
1155
- : {}),
1156
- }, "batch_call")));
1157
- const results = settled.map((s, i) => {
1158
- const call = args.calls[i];
1159
- if (!call) {
1160
- throw new Error("Batch result has no corresponding call");
1161
- }
1162
- const { address } = call;
1163
- if (s.status === "rejected") {
1164
- return {
1165
- address,
1166
- ok: false,
1167
- error: msg(s.reason),
1168
- errorDetails: classifyCallError(s.reason, "batch_call_failed"),
1169
- };
1170
- }
1171
- const r = s.value;
1172
- if (r.error) {
1173
- return {
1174
- address,
1175
- ok: false,
1176
- error: r.error.message,
1177
- errorDetails: r.error,
1178
- durationMs: r.durationMs,
1179
- attempts: r.attempts,
1180
- ...((call.diagnostics ?? args.diagnostics)
1181
- ? { timing: r.timing }
1182
- : {}),
1183
- };
1184
- }
1185
- if ((call.resultMode ?? args.resultMode) === "value") {
1186
- return {
1187
- address,
1188
- ok: true,
1189
- data: r.value,
1190
- durationMs: r.durationMs,
1191
- attempts: r.attempts,
1192
- ...((call.diagnostics ?? args.diagnostics)
1193
- ? { timing: r.timing }
1194
- : {}),
1195
- };
1196
- }
1197
- return {
1198
- address,
1199
- ok: true,
1200
- result: r.toolResult.content,
1201
- durationMs: r.durationMs,
1202
- attempts: r.attempts,
1203
- ...((call.diagnostics ?? args.diagnostics)
1204
- ? { timing: r.timing }
1205
- : {}),
1206
- };
1207
- });
1208
- const envelope = {
1209
- results,
1210
- durationMs: Date.now() - batchStarted,
1211
- };
1212
- const text = serializeResultText(envelope);
1213
- const bytes = enc.encode(text);
1214
- if (bytes.length <= batchCap)
1215
- return jsonResult(envelope);
1216
- const notice = await stashResult(text, registry.resultsStorage(), bytes.length);
1217
- return jsonResult({
1218
- results: results.map((result) => {
1219
- const common = {
1220
- address: batchSummaryString(result.address),
1221
- ok: !("error" in result),
1222
- ...("durationMs" in result
1223
- ? { durationMs: result.durationMs }
1224
- : {}),
1225
- ...("attempts" in result ? { attempts: result.attempts } : {}),
1226
- ...("timing" in result ? { timing: result.timing } : {}),
1227
- };
1228
- if (!("error" in result))
1229
- return common;
1230
- const error = result.error ?? "Batch call failed";
1231
- const details = result.errorDetails ??
1232
- errorDetails("batch_call_failed", error);
1233
- return {
1234
- ...common,
1235
- error: batchSummaryString(error),
1236
- errorDetails: {
1237
- code: batchSummaryString(details.code),
1238
- message: batchSummaryString(details.message),
1239
- retryable: details.retryable,
1240
- ...(details.retryAfterMs !== undefined
1241
- ? { retryAfterMs: details.retryAfterMs }
1242
- : {}),
1243
- ...(details.connector !== undefined
1244
- ? { connector: batchSummaryString(details.connector) }
1245
- : {}),
1246
- ...(details.operation !== undefined
1247
- ? { operation: batchSummaryString(details.operation) }
1248
- : {}),
1249
- ...(details.recovery !== undefined
1250
- ? { recovery: details.recovery }
1251
- : {}),
1252
- ...(details.nextAction !== undefined
1253
- ? { nextAction: batchSummaryNextAction(details.nextAction) }
1254
- : {}),
1255
- ...(details.retry !== undefined
1256
- ? { retry: batchSummaryString(details.retry) }
1257
- : {}),
1258
- },
1259
- };
1260
- }),
1261
- durationMs: envelope.durationMs,
1262
- ...notice,
1263
- });
1264
- },
1265
936
  async authorizeConnector(args) {
1266
937
  const connector = registry.getConnector(args.connector);
1267
938
  if (!connector) {
@@ -1325,7 +996,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
1325
996
  ? {
1326
997
  authorizationUrl: status.authorizationUrl,
1327
998
  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. " +
1328
- oauthFollowUp(surface, connector.id),
999
+ oauthFollowUp(connector.id),
1329
1000
  }
1330
1001
  : {}),
1331
1002
  ...(status.message ? { message: status.message } : {}),
@@ -1342,29 +1013,13 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
1342
1013
  },
1343
1014
  };
1344
1015
  }
1345
- 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.";
1346
1016
  const SEARCH_DESC = `Unknown address: use 2–4 distinctive action/object terms, not the full request; omit limit initially (default ${DEFAULT_SEARCH_LIMIT}) and page only if needed, up to ${MAX_SEARCH_LIMIT}. Partial and no-match searches report term coverage and next-step guidance. safety="readOnly" returns only calls available to call_tool and generated code; "approvalRequired" returns everything else; omitted or "all" preserves the complete catalog. This filters results, not authority. includeSchemas="compact" adds the input and any declared output shape, each bounded; plain-object schemas also expose inputKeys, requiredInputKeys, and outputKeys, while inputSchemaTruncated/outputSchemaTruncated mark shapes that need exact retrieval; matches also carry declared annotations. Call directly when sufficient. Empty query browses all.`;
1347
- const DESCRIBE_DESC = `Only when search_tools omitted schemas, a compact shape is ambiguous, or exact JSON constraints are needed. Inspects up to ${MAX_DESCRIBE_ADDRESSES} addresses with schemas and annotations; "compact" is default, while "json" preserves exact constraints.`;
1348
- 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; traverse arrays with [] (for example results[].id). Misses return data plus `$connecta` feedback. resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
1017
+ const CALL_DESC = '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; traverse arrays with [] (for example results[].id). Misses return data plus `$connecta` feedback. resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
1349
1018
  const CALL_DESTRUCTIVE_DESC = "Invoke any tool that is not explicitly annotated readOnlyHint: true, including unannotated, write-capable, or destructive tools. Include a short reason explaining the intended consequence for the human reviewer; it grants no authority and is never passed downstream. 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.";
1350
- 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. 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.";
1351
- 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. An oversized final envelope returns ordered outcome summaries plus a get_result page handle.";
1019
+ const GET_RESULT_DESC = "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.";
1352
1020
  const AUTHORIZE_DESC = "Use after auth_required. Returns an OAuth or operator-credential handoff, or reports required deployment configuration. force=true restarts OAuth only; this tool never accepts credentials.";
1353
1021
  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.';
1354
- /**
1355
- * Code-first replacements for the descriptions that route work between tools.
1356
- * Every one of these mentions a tool the consolidated surface removed, so on a
1357
- * code-first deployment the routing sentence has to point at the in-program
1358
- * function that took the work over — a description naming `batch_call` on a
1359
- * surface without one teaches a call that cannot succeed.
1360
- *
1361
- * The classic strings above are left byte-for-byte alone: classic is the
1362
- * compatibility surface and the eval's control arm, and rewording it would
1363
- * change what that control measures.
1364
- */
1365
- const CODE_FIRST_SEARCH_DESC = `${SEARCH_DESC} Expand an ambiguous compact shape, or read exact JSON constraints, with connecta.describe inside execute_code.`;
1366
- const CODE_FIRST_CALL_DESC = '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; traverse arrays with [] (for example results[].id). Misses return data plus `$connecta` feedback. resultMode "value" unwraps results, timeoutMs sets a deadline, safe maxRetries are annotation-gated, diagnostics adds timing, and large results page through get_result.';
1367
- const CODE_FIRST_GET_RESULT_DESC = "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.";
1022
+ const SEARCH_WITH_DESCRIBE_DESC = `${SEARCH_DESC} Expand an ambiguous compact shape, or read exact JSON constraints, with connecta.describe inside execute_code.`;
1368
1023
  /**
1369
1024
  * Sentences appended to a meta-tool description only when this connection
1370
1025
  * actually has connector guides. Tool descriptions are always-loaded context,
@@ -1376,7 +1031,6 @@ const CODE_FIRST_GET_RESULT_DESC = "Page a truncated result stashed by call_tool
1376
1031
  const GUIDE_NOTES = {
1377
1032
  skills: ' skills({}) also lists this deployment\'s per-connector usage guides as "connector:<connectorId>"; fetch the guide for a connector before working with it for the first time.',
1378
1033
  search: " A connector group carrying `guide` has a usage guide; fetch it with skills({ name: <guide> }).",
1379
- describe: " An entry carrying `guide` belongs to a connector with a usage guide; fetch it with skills({ name: <guide> }).",
1380
1034
  };
1381
1035
  /** `base`, plus its guide note when any VISIBLE connector carries a guide. */
1382
1036
  function describedFor(registry, base, note) {
@@ -1412,21 +1066,15 @@ const CALL_INPUT_SCHEMA = {
1412
1066
  diagnostics: z.boolean().optional(),
1413
1067
  };
1414
1068
  /**
1415
- * Register the base meta-tools onto an McpServer instance: nine on the classic
1416
- * surface, six on the code-first one, where `list_connectors`,
1417
- * `describe_tools`, and `batch_call` have folded into the program surface
1418
- * (`registerExecuteTool` adds the seventh, `execute_code`).
1419
- *
1420
- * Only the registrations differ. Every handler still exists on the object
1421
- * `createMetaTools` returns, and a folded tool's behavior is reached through
1422
- * `connecta.search` / `connecta.describe` / `connecta.batch` inside a program —
1423
- * the same code paths, one layer down.
1069
+ * Register the six explicit meta-tools onto an McpServer instance.
1070
+ * `registerExecuteTool` adds the seventh, `execute_code`. Broad discovery and
1071
+ * multi-call work is reached through `connecta.search` / `connecta.describe` /
1072
+ * `connecta.batch` inside a program, which `execute_code` builds over the same
1073
+ * `CatalogService` and `InvocationService` these handlers use — one shared
1074
+ * services layer, two adapters above it.
1424
1075
  */
1425
1076
  export function registerMetaTools(server, registry, ctx) {
1426
- const surface = ctx.surface ?? "classic";
1427
- const codeFirst = surface === "code-first";
1428
1077
  const mt = createMetaTools(registry, ctx.baseUrl, {
1429
- surface,
1430
1078
  ...(ctx.defaultToolTimeoutMs !== undefined
1431
1079
  ? { defaultToolTimeoutMs: ctx.defaultToolTimeoutMs }
1432
1080
  : {}),
@@ -1440,26 +1088,14 @@ export function registerMetaTools(server, registry, ctx) {
1440
1088
  ...(ctx.requestSignal !== undefined
1441
1089
  ? { requestSignal: ctx.requestSignal }
1442
1090
  : {}),
1443
- ...(ctx.defer !== undefined ? { defer: ctx.defer } : {}),
1444
1091
  });
1445
1092
  server.registerTool("skills", {
1446
1093
  description: describedFor(registry, SKILLS_DESC, "skills"),
1447
1094
  inputSchema: z.object({ name: z.string().optional() }),
1448
1095
  annotations: READ_ONLY_LOCAL,
1449
1096
  }, async (args) => mt.skills(args));
1450
- // Folded on the code-first surface: a program browses the same inventory with
1451
- // connecta.search({}) (every catalog) or connecta.search({ connector }) (one).
1452
- // Live connector probing is an operator concern, not a model one — it stays on
1453
- // the operator pages and /health, which is where the ethos puts observability.
1454
- if (!codeFirst) {
1455
- server.registerTool("list_connectors", {
1456
- description: LIST_DESC,
1457
- inputSchema: z.object({ probe: z.boolean().optional() }),
1458
- annotations: READ_ONLY_REMOTE,
1459
- }, async (args) => mt.listConnectors(args));
1460
- }
1461
1097
  server.registerTool("search_tools", {
1462
- description: describedFor(registry, codeFirst ? CODE_FIRST_SEARCH_DESC : SEARCH_DESC, "search"),
1098
+ description: describedFor(registry, SEARCH_WITH_DESCRIBE_DESC, "search"),
1463
1099
  inputSchema: z.object({
1464
1100
  query: z.string().optional(),
1465
1101
  connector: z.string().optional(),
@@ -1473,21 +1109,8 @@ export function registerMetaTools(server, registry, ctx) {
1473
1109
  }),
1474
1110
  annotations: READ_ONLY_REMOTE,
1475
1111
  }, async (args) => mt.searchTools(args));
1476
- // Folded on the code-first surface: connecta.describe takes the same
1477
- // addresses, format, and per-address error reporting inside a program.
1478
- if (!codeFirst) {
1479
- server.registerTool("describe_tools", {
1480
- description: describedFor(registry, DESCRIBE_DESC, "describe"),
1481
- inputSchema: z.object({
1482
- addresses: z.array(z.string()).max(MAX_DESCRIBE_ADDRESSES),
1483
- format: z.enum(["compact", "json"]).optional(),
1484
- fullDescriptions: z.boolean().optional(),
1485
- }),
1486
- annotations: READ_ONLY_REMOTE,
1487
- }, async (args) => mt.describeTools(args));
1488
- }
1489
1112
  server.registerTool("call_tool", {
1490
- description: codeFirst ? CODE_FIRST_CALL_DESC : CALL_DESC,
1113
+ description: CALL_DESC,
1491
1114
  inputSchema: z.object(CALL_INPUT_SCHEMA),
1492
1115
  // call_tool admits only tools that are themselves explicitly read-only;
1493
1116
  // anything else is refused and routed to call_destructive_tool.
@@ -1532,7 +1155,7 @@ export function registerMetaTools(server, registry, ctx) {
1532
1155
  },
1533
1156
  }, async (args) => mt.authorizeConnector(args));
1534
1157
  server.registerTool("get_result", {
1535
- description: codeFirst ? CODE_FIRST_GET_RESULT_DESC : GET_RESULT_DESC,
1158
+ description: GET_RESULT_DESC,
1536
1159
  inputSchema: z.object({
1537
1160
  id: z.string(),
1538
1161
  // Both bounds are the shared rules (isValidResultOffset,
@@ -1544,25 +1167,5 @@ export function registerMetaTools(server, registry, ctx) {
1544
1167
  }),
1545
1168
  annotations: READ_ONLY_LOCAL,
1546
1169
  }, async (args) => mt.getResult(args));
1547
- // Folded on the code-first surface: connecta.batch runs the same 1–10
1548
- // parallel read-only calls and returns the same typed per-call outcomes.
1549
- if (!codeFirst) {
1550
- server.registerTool("batch_call", {
1551
- description: BATCH_DESC,
1552
- inputSchema: z.object({
1553
- calls: z
1554
- .array(z.object(CALL_INPUT_SCHEMA))
1555
- .min(1)
1556
- .max(10),
1557
- resultMode: z.enum(["mcp", "value"]).optional(),
1558
- timeoutMs: z.number().int().positive().optional(),
1559
- maxRetries: z.number().int().min(0).max(2).optional(),
1560
- diagnostics: z.boolean().optional(),
1561
- }),
1562
- // Same gate as call_tool: every call in the batch must be explicitly
1563
- // read-only or the batch is refused.
1564
- annotations: READ_ONLY_REMOTE,
1565
- }, async (args) => mt.batchCall(args));
1566
- }
1567
1170
  }
1568
1171
  //# sourceMappingURL=meta-tools.js.map