@zackbart/connecta 0.16.1 → 0.18.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 (46) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +4 -0
  3. package/dist/catalog-service.d.ts +10 -0
  4. package/dist/catalog-service.js +77 -5
  5. package/dist/catalog.js +114 -12
  6. package/dist/errors.d.ts +4 -6
  7. package/dist/execute.d.ts +7 -0
  8. package/dist/execute.js +262 -168
  9. package/dist/invocation.js +3 -1
  10. package/dist/meta-tools.d.ts +4 -0
  11. package/dist/meta-tools.js +55 -23
  12. package/dist/operator-ui/generated.d.ts +1 -1
  13. package/dist/operator-ui/generated.js +1 -1
  14. package/dist/operator-ui/model.d.ts +3 -1
  15. package/dist/providers/mixpanel.d.ts +3 -5
  16. package/dist/providers/mixpanel.js +73 -5
  17. package/dist/providers/stripe.d.ts +25 -24
  18. package/dist/providers/stripe.js +64 -35
  19. package/dist/registry.d.ts +32 -9
  20. package/dist/registry.js +217 -33
  21. package/dist/routes/mcp.js +6 -0
  22. package/dist/routes/ui.js +1 -1
  23. package/dist/skills.d.ts +5 -1
  24. package/dist/skills.js +206 -30
  25. package/dist/types.d.ts +14 -2
  26. package/dist/ui.js +4 -1
  27. package/dist/version.d.ts +1 -1
  28. package/dist/version.js +1 -1
  29. package/documentation/architecture.md +8 -5
  30. package/documentation/code-mode.md +68 -68
  31. package/documentation/connector-guides.md +29 -27
  32. package/documentation/connectors.md +13 -1
  33. package/documentation/meta-tools.md +53 -19
  34. package/documentation/mixpanel.md +20 -0
  35. package/documentation/notion.md +17 -0
  36. package/documentation/operations.md +24 -21
  37. package/documentation/operator-ui.md +12 -2
  38. package/documentation/provider-audit.md +15 -7
  39. package/documentation/provider-conventions.md +26 -13
  40. package/documentation/stripe.md +66 -59
  41. package/documentation/upgrading.md +46 -4
  42. package/ethos.md +7 -7
  43. package/examples/worker/README.md +4 -3
  44. package/package.json +2 -2
  45. package/templates/node/README.md +7 -0
  46. package/templates/node/package.json +5 -2
@@ -282,7 +282,9 @@ export class InvocationService {
282
282
  context.beforeDispatch?.();
283
283
  }
284
284
  catch (error) {
285
- return failed(classifyCallError(error));
285
+ return failed(error instanceof InvocationFailure
286
+ ? error.details
287
+ : classifyCallError(error));
286
288
  }
287
289
  const maxRetries = Math.min(2, Math.max(0, Math.trunc(context.maxRetries ?? 0)));
288
290
  let result;
@@ -1,6 +1,7 @@
1
1
  import type { McpServer } from "@modelcontextprotocol/server";
2
2
  import type { ActivityRequestContext } from "./activity.js";
3
3
  import { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_SEARCH_LIMIT } from "./catalog-service.js";
4
+ import type { DeferredWork } from "./connector-scope.js";
4
5
  import { MAX_RETRY_BACKOFF_MS, retryBackoffMs } from "./invocation.js";
5
6
  import { type RegistryView } from "./registry.js";
6
7
  export { MAX_DESCRIBE_ADDRESSES, MAX_DISCOVERY_RESULT_BYTES, MAX_RETRY_BACKOFF_MS, MAX_SEARCH_LIMIT, retryBackoffMs, };
@@ -112,6 +113,8 @@ export declare function createMetaTools(registry: RegistryView, baseUrl: string,
112
113
  activity?: ActivityRequestContext;
113
114
  /** Inbound request cancellation shared by every call this request makes. */
114
115
  requestSignal?: AbortSignal;
116
+ /** Runtime-owned tail for stale catalog refreshes. */
117
+ defer?: DeferredWork;
115
118
  }): {
116
119
  skills(args?: SkillArgs): Promise<ToolResult>;
117
120
  searchTools(args: SearchArgs): Promise<ToolResult>;
@@ -135,4 +138,5 @@ export declare function registerMetaTools(server: McpServer, registry: RegistryV
135
138
  discoveryConcurrency?: number;
136
139
  activity?: ActivityRequestContext;
137
140
  requestSignal?: AbortSignal;
141
+ defer?: DeferredWork;
138
142
  }): void;
@@ -125,25 +125,41 @@ export function alignEndToCharBoundary(bytes, offset, end, total) {
125
125
  }
126
126
  return e;
127
127
  }
128
- // --- fields selection (feature 2) -----------------------------------------
129
- /** Resolve a dot-path (segments) against a value; `key[]` maps the tail over an array. */
128
+ /** Resolve a dot-path and retain misses below every `[]` boundary. */
130
129
  function resolvePath(value, segments) {
131
130
  const seg = segments[0];
132
- if (seg === undefined)
133
- return value;
131
+ if (seg === undefined) {
132
+ return value === undefined
133
+ ? { status: "unmatched" }
134
+ : { status: "matched", value };
135
+ }
134
136
  const rest = segments.slice(1);
135
137
  const isArr = seg.endsWith("[]");
136
138
  const key = isArr ? seg.slice(0, -2) : seg;
137
139
  let next = value;
138
140
  if (key !== "") {
139
- if (value === null || typeof value !== "object")
140
- return undefined;
141
+ if (value === null || typeof value !== "object") {
142
+ return { status: "unmatched" };
143
+ }
141
144
  next = value[key];
142
145
  }
143
146
  if (isArr) {
144
147
  if (!Array.isArray(next))
145
- return undefined;
146
- return next.map((el) => resolvePath(el, rest));
148
+ return { status: "unmatched" };
149
+ if (next.length === 0)
150
+ return { status: "matched", value: [] };
151
+ const elements = next.map((el) => resolvePath(el, rest));
152
+ const matched = elements.some((element) => element.status !== "unmatched");
153
+ const missed = elements.some((element) => element.status !== "matched");
154
+ if (!matched)
155
+ return { status: "unmatched" };
156
+ return {
157
+ status: missed ? "partial" : "matched",
158
+ // Undefined placeholders retain the historical array positions in the
159
+ // partial value. JSON renders them as null; partialFields says they are
160
+ // unresolved rather than genuine downstream nulls.
161
+ value: elements.map((element) => element.status === "unmatched" ? undefined : element.value),
162
+ };
147
163
  }
148
164
  return resolvePath(next, rest);
149
165
  }
@@ -517,14 +533,19 @@ function schemaProjectionFeedback(outputSchema, unmatchedFields) {
517
533
  function applyFields(value, fields) {
518
534
  const out = {};
519
535
  const unmatchedFields = [];
536
+ const partialFields = [];
520
537
  for (const path of fields) {
521
538
  const resolved = resolvePath(value, path.split("."));
522
- if (resolved === undefined)
539
+ if (resolved.status === "unmatched") {
523
540
  unmatchedFields.push(path);
524
- else
525
- out[path] = resolved;
541
+ }
542
+ else {
543
+ out[path] = resolved.value;
544
+ if (resolved.status === "partial")
545
+ partialFields.push(path);
546
+ }
526
547
  }
527
- return { data: out, unmatchedFields };
548
+ return { data: out, unmatchedFields, partialFields };
528
549
  }
529
550
  /**
530
551
  * Keep the historical flat projection when every path resolves. A miss gets a
@@ -537,16 +558,25 @@ function projectionValue(value, fields, outputSchema) {
537
558
  // matched downstream field with that exact name is escaped below `data`, so
538
559
  // no user-controlled value can impersonate Connecta's discriminator.
539
560
  const reservedCollision = Object.prototype.hasOwnProperty.call(projected.data, "$connecta");
540
- if (projected.unmatchedFields.length === 0 && !reservedCollision) {
561
+ if (projected.unmatchedFields.length === 0 &&
562
+ projected.partialFields.length === 0 &&
563
+ !reservedCollision) {
541
564
  return projected.data;
542
565
  }
566
+ const problemFields = [
567
+ ...projected.unmatchedFields,
568
+ ...projected.partialFields,
569
+ ];
543
570
  return {
544
571
  data: projected.data,
545
572
  $connecta: {
546
573
  type: "field_projection",
547
574
  unmatchedFields: projected.unmatchedFields,
575
+ ...(projected.partialFields.length > 0
576
+ ? { partialFields: projected.partialFields }
577
+ : {}),
548
578
  ...(outputSchema
549
- ? schemaProjectionFeedback(outputSchema, projected.unmatchedFields)
579
+ ? schemaProjectionFeedback(outputSchema, problemFields)
550
580
  : {}),
551
581
  },
552
582
  };
@@ -731,6 +761,7 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
731
761
  requestScope,
732
762
  probeTimeoutMs,
733
763
  concurrency: discoveryConcurrency,
764
+ ...(opts.defer ? { defer: opts.defer } : {}),
734
765
  // searchRoute keeps its top-level default. In-program callers use a
735
766
  // separate CatalogService configured for connecta.search.
736
767
  });
@@ -1013,13 +1044,13 @@ export function createMetaTools(registry, baseUrl, opts = {}) {
1013
1044
  },
1014
1045
  };
1015
1046
  }
1016
- const SEARCH_DESC = `Use top-level search only for exactly one unreduced read, then call_tool, or for write-capable work, then call_destructive_tool. For read-only reduction, dependent or multiple calls, never search here: make one execute_code program that searches and calls. Use 2–4 distinctive action/object terms, not the full request; set connector to the obvious integration id to load one catalog instead of all; omit limit initially (default ${DEFAULT_SEARCH_LIMIT}), page to ${MAX_SEARCH_LIMIT} if needed. safety="readOnly" returns only calls available to call_tool/code; "approvalRequired" returns the rest; omitted/"all" returns all. This filters results, not authority. includeSchemas="compact" adds the input and any declared output shape, bounded; plain objects expose inputKeys, requiredInputKeys, and outputKeys; truncation flags mark incomplete shapes; matches also carry declared annotations. Require purpose/address fit plus compatible inputs, truncation, safety, and outputs — never the first lexical match. Empty or whitespace-only query browses all; non-empty input with no ASCII terms returns no match.`;
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.';
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.";
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.";
1047
+ const SEARCH_DESC = `Use top-level search for one unknown-address read before call_tool, or for approval-required work before call_destructive_tool. Use 2–4 action/object terms and includeSchemas="compact"; the default limit is ${DEFAULT_SEARCH_LIMIT}. Set connector when known. safety="readOnly" finds direct or program calls; "approvalRequired" finds the fail-closed complement. These filters grant no authority. For multiple, dependent, or reduced read-only calls, use one execute_code program instead. Empty query browses.`;
1048
+ const CALL_DESC = 'Call one tool explicitly annotated readOnlyHint: true. Use execute_code for multiple, dependent, or reduced read-only calls. Unannotated or write-capable tools fail closed to call_destructive_tool. fields projects JSON dot-paths; use [] through arrays. A truncated result carries a get_result action.';
1049
+ const CALL_DESTRUCTIVE_DESC = "Call any tool not explicitly annotated readOnlyHint: true. Include a short reason for the human reviewer after checking the schema and consequences. The reason grants no authority and is not sent downstream.";
1050
+ const GET_RESULT_DESC = "Page a truncated direct-call result by id and byte offset. A program result is never paged; reduce it inside execute_code. Returns text, offset, nextOffset when more remains, and totalBytes.";
1020
1051
  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.";
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.';
1022
- const SEARCH_WITH_DESCRIBE_DESC = `${SEARCH_DESC} Expand an ambiguous compact shape, or read exact JSON constraints, with connecta.describe inside execute_code.`;
1052
+ const SKILLS_DESC = 'List or fetch on-demand guidance. Fetch usage once per task for program syntax, selection, repair, examples, and runtime details.';
1053
+ const SEARCH_WITH_DESCRIBE_DESC = SEARCH_DESC;
1023
1054
  /**
1024
1055
  * Sentences appended to a meta-tool description only when this connection
1025
1056
  * actually has connector guides. Tool descriptions are always-loaded context,
@@ -1029,9 +1060,9 @@ const SEARCH_WITH_DESCRIBE_DESC = `${SEARCH_DESC} Expand an ambiguous compact sh
1029
1060
  * Registration is per connection and reads the configured connector set.
1030
1061
  */
1031
1062
  const GUIDE_NOTES = {
1032
- skills: " skills({}) also lists this deployment's scoped connector guides; fetch only an exact name listed there or carried by discovery, never one inferred from a connector id.",
1033
- search: " A result carrying `guide` also carries a bounded `guideSummary`. `guideRequired: true` is a hard stop: fetch that exact guide before calling. `guideRequiredReasons` explains why — `connector_required` and `approval_required` stand however you expand the schema; `schema_truncated` clears once describe returns the exact one. Otherwise fetch only when the summary names a connector convention relevant to the task. A complete, unambiguous read-only schema needs no otherwise-irrelevant guide fetch.",
1034
- destructive: " Before a consequential call, inspect the address through discovery or describe and fetch any connector guide it names.",
1063
+ skills: " Also lists this deployment's connector guides by exact name.",
1064
+ search: " A result with guideRequired: true requires its exact named connector guide before the call.",
1065
+ destructive: " Fetch any exact connector guide named by discovery before the call.",
1035
1066
  };
1036
1067
  /** `base`, plus its guide note when any VISIBLE connector carries a guide. */
1037
1068
  function describedFor(registry, base, note) {
@@ -1089,6 +1120,7 @@ export function registerMetaTools(server, registry, ctx) {
1089
1120
  ...(ctx.requestSignal !== undefined
1090
1121
  ? { requestSignal: ctx.requestSignal }
1091
1122
  : {}),
1123
+ ...(ctx.defer !== undefined ? { defer: ctx.defer } : {}),
1092
1124
  });
1093
1125
  server.registerTool("skills", {
1094
1126
  description: describedFor(registry, SKILLS_DESC, "skills"),