@zapier/connectors-sdk 0.1.0-experimental.1 → 0.1.0-experimental.11

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.
@@ -346,32 +346,6 @@ A multi-credential resolver `{ name: "hmac", keySuffixes: ["KEY", "SECRET"] }` w
346
346
 
347
347
  The author never sees `process.env`, never threads a `Fetch` through their script body, never re-implements auth logic.
348
348
 
349
- ## `inputDependencies` — out-of-band dependent-field metadata
350
-
351
- Some scripts have input fields whose accepted shape depends on another input field — e.g. Notion's `create_database_item` takes a `databaseId` and a `properties` map whose schema is determined by which database was chosen. JSON Schema can't express that conditional shape, but adapters that drive option-loading or schema-resolution chains need to know about it.
352
-
353
- `defineTool` publishes the metadata in two places:
354
-
355
- 1. `definition.inputDependencies` — programmatic readers reach for it directly on the script's default export.
356
- 2. MCP registration shape — `_meta["zapier:inputDependencies"]` via either `toMcpTool` (wire-format) or `toMcpServerTool` (`McpServer.registerTool` config).
357
-
358
- Shape constraints:
359
-
360
- - Dependencies are keyed by the dependent input field's name (`databaseId`, `properties`).
361
- - Each entry names another tool by **string** (`fromTool: "list-databases"`), not by function reference — so the chain stays serializable.
362
- - Args passed to the resolver tool use `$<fieldName>` syntax to reference other input fields (`fromArgs: { databaseId: "$databaseId" }`). The adapter reads and evaluates these references; the script body does not.
363
-
364
- ```ts
365
- inputDependencies: {
366
- databaseId: { kind: "options", fromTool: "list-databases", fromArgs: {} },
367
- properties: {
368
- kind: "schema",
369
- fromTool: "get-database-schema",
370
- fromArgs: { databaseId: "$databaseId" },
371
- },
372
- } as const,
373
- ```
374
-
375
349
  ## `<bin> mcp` — local-MCP-server execution surface
376
350
 
377
351
  Reached through the bundled CLI as `npx @zapier/<x>-connector mcp` — no per-app glue. The dispatcher reserves the primary command `mcp` and delegates to the internal `serveMcpStdio` helper:
package/dist/index.cjs CHANGED
@@ -109,6 +109,7 @@ function defineConnectionResolver(resolver) {
109
109
  }
110
110
  var zapierConnectionResolver = defineConnectionResolver({
111
111
  name: "zapier-connection-id",
112
+ optionalPackages: ["@zapier/zapier-sdk"],
112
113
  resolve: async (connectionId) => {
113
114
  const { buildZapierFetch: buildZapierFetch2 } = await Promise.resolve().then(() => (init_build_zapier_fetch(), build_zapier_fetch_exports));
114
115
  return buildZapierFetch2(connectionId);
@@ -128,12 +129,16 @@ function defineBearerTokenResolver(opts = {}) {
128
129
  });
129
130
  }
130
131
 
132
+ // src/normalize-connections.ts
133
+ var import_node_module = require("module");
134
+
131
135
  // src/connection-key.ts
132
136
  function envFromKey(name) {
133
137
  return name.toUpperCase().replace(/-/g, "_");
134
138
  }
135
139
 
136
140
  // src/normalize-connections.ts
141
+ var import_meta = {};
137
142
  function* walkConnections(definition) {
138
143
  if (typeof definition.connection === "string") {
139
144
  yield { slotName: void 0, connectionKey: definition.connection };
@@ -267,23 +272,103 @@ function buildRunOptionsFromEnv(definition, env, connectionResolvers) {
267
272
  }
268
273
  return { connections: wrapped };
269
274
  }
270
- function formatHelpForConnections(definition, connectionResolvers) {
275
+ function isPackageInstalled(name) {
276
+ try {
277
+ (0, import_node_module.createRequire)(import_meta.url).resolve(name);
278
+ return true;
279
+ } catch {
280
+ return false;
281
+ }
282
+ }
283
+ function buildMissingConnectionError(scriptName, missingSlots) {
284
+ const lines = [];
285
+ if (missingSlots.length === 1 && missingSlots[0].slotName === void 0) {
286
+ const { connectionKey, resolvers } = missingSlots[0];
287
+ lines.push(
288
+ `"${scriptName}" requires the "${connectionKey}" connection but no credentials were found in the environment.`
289
+ );
290
+ lines.push("");
291
+ lines.push(
292
+ "Set one of the following environment variables before running:"
293
+ );
294
+ lines.push("");
295
+ for (const resolver of resolvers) {
296
+ const vars = envVarsFor(void 0, connectionKey, resolver);
297
+ lines.push(` ${vars.join(" ")} (${resolver.name})`);
298
+ }
299
+ } else {
300
+ lines.push(
301
+ `"${scriptName}" is missing credentials for connection slot(s):`
302
+ );
303
+ for (const { slotName, connectionKey, resolvers } of missingSlots) {
304
+ lines.push("");
305
+ lines.push(` ${slotName} (${connectionKey}):`);
306
+ for (const resolver of resolvers) {
307
+ const vars = envVarsFor(slotName, connectionKey, resolver);
308
+ lines.push(` ${vars.join(" ")} (${resolver.name})`);
309
+ }
310
+ }
311
+ }
312
+ lines.push("");
313
+ lines.push("Run with --help for the full auth guide.");
314
+ return new Error(lines.join("\n"));
315
+ }
316
+ function formatHelpForConnections(definition, connectionResolvers, env) {
271
317
  const slots = [...walkConnections(definition)];
272
318
  if (slots.length === 0) return [];
273
319
  const lines = [];
274
320
  lines.push(
275
- "Connections (set as environment variables; do NOT pass via CLI argument):"
321
+ "Auth (set as environment variables; do NOT pass via CLI argument):"
276
322
  );
323
+ const notReadySlots = [];
277
324
  for (const { slotName, connectionKey } of slots) {
278
325
  const resolvers = resolversForKey(connectionResolvers, connectionKey);
279
- const heading = slotName ? ` ${slotName} (${connectionKey}):` : ` ${connectionKey}:`;
280
- lines.push(heading);
326
+ if (slotName !== void 0) {
327
+ lines.push(` ${slotName} (${connectionKey}):`);
328
+ }
329
+ const resolverIndent = slotName !== void 0 ? " " : " ";
330
+ const varIndent = slotName !== void 0 ? " " : " ";
331
+ let slotHasReady = false;
281
332
  for (const resolver of resolvers) {
282
- lines.push(` ${resolver.name}:`);
283
- for (const envVar of envVarsFor(slotName, connectionKey, resolver)) {
284
- lines.push(` ${envVar}`);
333
+ const vars = envVarsFor(slotName, connectionKey, resolver);
334
+ let isReady = false;
335
+ let missingPkgs = [];
336
+ if (env !== void 0) {
337
+ const envVarsOk = vars.every(
338
+ (k) => typeof env[k] === "string" && env[k] !== ""
339
+ );
340
+ if (envVarsOk) isReady = true;
341
+ missingPkgs = (resolver.optionalPackages ?? []).filter(
342
+ (pkg) => !isPackageInstalled(pkg)
343
+ );
344
+ }
345
+ const markReady = isReady && !slotHasReady;
346
+ if (markReady) slotHasReady = true;
347
+ const resolverSuffix = env !== void 0 && markReady ? " [READY \u2014 use this]" : "";
348
+ lines.push(`${resolverIndent}${resolver.name}:${resolverSuffix}`);
349
+ for (const envVar of vars) {
350
+ const varSuffix = env !== void 0 ? env[envVar] !== void 0 && env[envVar] !== "" ? " [set]" : " [not set]" : "";
351
+ lines.push(`${varIndent}${envVar}${varSuffix}`);
352
+ }
353
+ if (env !== void 0) {
354
+ for (const pkg of resolver.optionalPackages ?? []) {
355
+ if (missingPkgs.includes(pkg)) {
356
+ lines.push(
357
+ `${varIndent}${pkg} [not installed \u2014 run \`npm install ${pkg}\` first]`
358
+ );
359
+ } else {
360
+ lines.push(`${varIndent}${pkg} [installed]`);
361
+ }
362
+ }
285
363
  }
286
364
  }
365
+ if (env !== void 0 && !slotHasReady) {
366
+ notReadySlots.push(slotName ?? connectionKey);
367
+ }
368
+ }
369
+ if (env !== void 0 && notReadySlots.length > 0) {
370
+ const label = notReadySlots.length === slots.length ? "No option is ready" : `No option is ready for slot${notReadySlots.length > 1 ? "s" : ""} ${notReadySlots.join(", ")}`;
371
+ lines.push(`${label} \u2014 set one of the env vars above.`);
287
372
  }
288
373
  return lines;
289
374
  }
@@ -368,8 +453,8 @@ function ensureConnectionValue(scriptName, slotName, connectionKey, value) {
368
453
  return value;
369
454
  }
370
455
  async function buildContext(definition, opts, connectionResolvers) {
371
- const hasConnection = "connection" in opts && opts.connection !== void 0;
372
- const hasConnections = "connections" in opts && opts.connections !== void 0;
456
+ const hasConnection = opts !== void 0 && "connection" in opts && opts.connection !== void 0;
457
+ const hasConnections = opts !== void 0 && "connections" in opts && opts.connections !== void 0;
373
458
  if (definition.connection === void 0 && definition.connections === void 0) {
374
459
  if (hasConnection || hasConnections) {
375
460
  throw new Error(
@@ -378,6 +463,11 @@ async function buildContext(definition, opts, connectionResolvers) {
378
463
  }
379
464
  return {};
380
465
  }
466
+ if (opts === void 0) {
467
+ throw new Error(
468
+ `ToolDefinition "${definition.name}": \`RunOptions\` is required \u2014 pass \`{ connection: ... }\` or \`{ connections: ... }\`.`
469
+ );
470
+ }
381
471
  if (hasConnection && hasConnections) {
382
472
  throw new Error(
383
473
  `ToolDefinition "${definition.name}": \`RunOptions\` sets both \`connection\` and \`connections\`. Use one or the other.`
@@ -395,7 +485,7 @@ async function buildContext(definition, opts, connectionResolvers) {
395
485
  definition.name,
396
486
  void 0,
397
487
  connectionKey,
398
- opts.connection
488
+ "connection" in opts ? opts.connection : void 0
399
489
  );
400
490
  const fetch = await resolveSlotFetch(
401
491
  definition.name,
@@ -412,12 +502,12 @@ async function buildContext(definition, opts, connectionResolvers) {
412
502
  `ToolDefinition "${definition.name}" is multi-connection (slots: ${declaredSlots.join(", ")}) \u2014 call with \`{ connections: { ... } }\`, not \`{ connection: ... }\`.`
413
503
  );
414
504
  }
415
- if (!hasConnections) {
505
+ const provided = opts.connections;
506
+ if (provided === void 0) {
416
507
  throw new Error(
417
508
  `ToolDefinition "${definition.name}" is multi-connection \u2014 \`RunOptions.connections\` is required.`
418
509
  );
419
510
  }
420
- const provided = opts.connections;
421
511
  const unknown = Object.keys(provided).filter(
422
512
  (s) => !declaredSlots.includes(s)
423
513
  );
@@ -471,28 +561,18 @@ function toChatCompletionTool(definition) {
471
561
 
472
562
  // src/surfaces/to-mcp-server-tool.ts
473
563
  function toMcpServerTool(definition) {
474
- const config = {
564
+ return {
475
565
  title: definition.title,
476
566
  description: definition.description,
477
567
  inputSchema: definition.inputSchema,
478
568
  outputSchema: definition.outputSchema,
479
569
  annotations: definition.annotations
480
570
  };
481
- if (definition.inputDependencies) {
482
- config._meta = {
483
- "zapier:inputDependencies": definition.inputDependencies
484
- };
485
- }
486
- return config;
487
571
  }
488
572
 
489
573
  // src/surfaces/to-mcp-tool.ts
490
574
  var import_zod2 = require("zod");
491
575
  function toMcpTool(definition) {
492
- const meta = {};
493
- if (definition.inputDependencies) {
494
- meta["zapier:inputDependencies"] = definition.inputDependencies;
495
- }
496
576
  return {
497
577
  name: definition.name,
498
578
  title: definition.title,
@@ -501,8 +581,7 @@ function toMcpTool(definition) {
501
581
  outputSchema: import_zod2.z.toJSONSchema(
502
582
  definition.outputSchema
503
583
  ),
504
- annotations: definition.annotations,
505
- ...Object.keys(meta).length > 0 ? { _meta: meta } : {}
584
+ annotations: definition.annotations
506
585
  };
507
586
  }
508
587
 
@@ -516,24 +595,9 @@ function toResponsesTool(definition) {
516
595
 
517
596
  // src/define-connector.ts
518
597
  function wrapScriptWithResolvers(definition, connectionResolvers) {
519
- const authorRun = definition.run;
520
598
  const wrappedRun = async (input, opts) => {
521
- const validated = definition.inputSchema.parse(input);
522
- if (definition.connection === void 0 && definition.connections === void 0) {
523
- if (opts !== void 0 && (opts.connection !== void 0 || opts.connections !== void 0)) {
524
- throw new Error(
525
- `ToolDefinition "${definition.name}" is credential-free \u2014 \`RunOptions\` must not include \`connection\` or \`connections\`.`
526
- );
527
- }
528
- return authorRun(validated);
529
- }
530
- if (opts === void 0) {
531
- throw new Error(
532
- `ToolDefinition "${definition.name}": \`RunOptions\` is required \u2014 pass \`{ connection: ... }\` or \`{ connections: ... }\`.`
533
- );
534
- }
535
599
  const ctx = await buildContext(definition, opts, connectionResolvers);
536
- return authorRun(validated, ctx);
600
+ return definition.run(input, ctx);
537
601
  };
538
602
  return { ...definition, run: wrappedRun };
539
603
  }
@@ -543,6 +607,11 @@ function defineConnector(config) {
543
607
  validateConnectionResolvers(scriptsAsAny, connectionResolvers);
544
608
  const wrapped = {};
545
609
  for (const [key, definition] of Object.entries(scriptsAsAny)) {
610
+ if (key !== definition.name) {
611
+ throw new Error(
612
+ `defineConnector: script key "${key}" must match its tool name \u2014 got name "${definition.name}". Rename the key to "${definition.name}" or update the tool's name field.`
613
+ );
614
+ }
546
615
  wrapped[key] = wrapScriptWithResolvers(definition, connectionResolvers);
547
616
  }
548
617
  return {
@@ -612,6 +681,12 @@ function defineTool(config) {
612
681
  validateConnectionKey(config.name, value);
613
682
  }
614
683
  }
684
+ const authorRun = config.run;
685
+ const wrappedRun = async (input, ctx) => {
686
+ const validated = config.inputSchema.parse(input);
687
+ const result = await authorRun(validated, ctx);
688
+ return config.outputSchema.parse(result);
689
+ };
615
690
  const base = {
616
691
  kind: "tool",
617
692
  name: config.name,
@@ -620,9 +695,7 @@ function defineTool(config) {
620
695
  inputSchema: config.inputSchema,
621
696
  outputSchema: config.outputSchema,
622
697
  annotations: config.annotations,
623
- statements: config.statements,
624
- inputDependencies: config.inputDependencies,
625
- run: config.run
698
+ run: wrappedRun
626
699
  };
627
700
  if (hasSingular) base.connection = config.connection;
628
701
  if (hasPlural) base.connections = config.connections;
@@ -766,10 +839,73 @@ async function runDispatchCliBody(connector, opts) {
766
839
  });
767
840
  return;
768
841
  }
842
+ if (first === "skill") {
843
+ if (!opts.meta) {
844
+ throw new Error(
845
+ "runDispatchCliBody: `skill` requires `meta` in options (the connector's `import.meta` from `cli.ts`)."
846
+ );
847
+ }
848
+ const cliPath = (0, import_node_url2.fileURLToPath)(opts.meta.url);
849
+ const connectorDir = path2.dirname(cliPath);
850
+ const skillPath = path2.join(connectorDir, "SKILL.md");
851
+ let content;
852
+ try {
853
+ content = await fs2.readFile(skillPath, "utf8");
854
+ } catch {
855
+ throw new Error(
856
+ `SKILL.md not found at ${skillPath}. Ensure the connector ships a SKILL.md.`
857
+ );
858
+ }
859
+ opts.stdout.write(resolveRelativeLinks(content, connectorDir));
860
+ return;
861
+ }
862
+ if (first === "reference") {
863
+ if (!opts.meta) {
864
+ throw new Error(
865
+ "runDispatchCliBody: `reference` requires `meta` in options (the connector's `import.meta` from `cli.ts`)."
866
+ );
867
+ }
868
+ const cliPath = (0, import_node_url2.fileURLToPath)(opts.meta.url);
869
+ const connectorDir = path2.dirname(cliPath);
870
+ const refsDir = path2.join(connectorDir, "references");
871
+ const name = args[1];
872
+ if (!name) {
873
+ let entries;
874
+ try {
875
+ const files = await fs2.readdir(refsDir);
876
+ entries = files.filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -".md".length));
877
+ } catch {
878
+ entries = [];
879
+ }
880
+ if (entries.length === 0) {
881
+ opts.stdout.write(`Available references: <none>
882
+ `);
883
+ } else {
884
+ opts.stdout.write(`Available references:
885
+ `);
886
+ for (const entry of entries) {
887
+ opts.stdout.write(` ${entry}
888
+ `);
889
+ }
890
+ }
891
+ return;
892
+ }
893
+ const refPath = path2.join(refsDir, `${name}.md`);
894
+ let content;
895
+ try {
896
+ content = await fs2.readFile(refPath, "utf8");
897
+ } catch {
898
+ throw new Error(
899
+ `Reference file not found at ${refPath}. Ensure the connector ships \`references/${name}.md\`.`
900
+ );
901
+ }
902
+ opts.stdout.write(resolveRelativeLinks(content, refsDir));
903
+ return;
904
+ }
769
905
  if (first !== "run") {
770
906
  printUsage(scripts, errOut, packageName);
771
907
  throw new Error(
772
- `Unknown command "${first}". Use \`run <script>\` to dispatch a script (available: ${Object.keys(scripts).join(", ") || "<none>"}), or \`mcp\` to boot a local MCP server.`
908
+ `Unknown command "${first}". Use \`run <script>\` to dispatch a script (available: ${Object.keys(scripts).join(", ") || "<none>"}), \`mcp\` to boot a local MCP server, \`skill\` to print SKILL.md, or \`reference [<name>]\` to print or list reference docs.`
773
909
  );
774
910
  }
775
911
  const scriptName = args[1];
@@ -796,6 +932,17 @@ async function runDispatchCliBody(connector, opts) {
796
932
  invocation
797
933
  });
798
934
  }
935
+ function resolveRelativeLinks(content, baseDir) {
936
+ return content.replace(
937
+ /(!?\[([^\]]*)\])\(([^)]+)\)/g,
938
+ (match, prefix, _alt, url) => {
939
+ if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file:") || url.startsWith("#") || url.startsWith("/")) {
940
+ return match;
941
+ }
942
+ return `${prefix}(${path2.resolve(baseDir, url)})`;
943
+ }
944
+ );
945
+ }
799
946
  async function resolvePackageName(meta) {
800
947
  if (!meta?.url) return void 0;
801
948
  try {
@@ -814,6 +961,8 @@ function printUsage(scripts, out, packageName) {
814
961
  `Usage: <bin> run <script> [<json-input>]
815
962
  <bin> run <script> --help (per-script input + required env vars)
816
963
  <bin> mcp (run as a local MCP server over stdio)
964
+ <bin> skill (print SKILL.md to stdout)
965
+ <bin> reference [<name>] (print references/<name>.md, or list all)
817
966
 
818
967
  `
819
968
  );
@@ -926,12 +1075,13 @@ async function handleIfScriptMainBody(wrappedScript, connectionResolvers, io) {
926
1075
  if (helpRequested) {
927
1076
  io.stdout.write(
928
1077
  buildHelpText(wrappedScript, connectionResolvers, {
929
- invocation: io.invocation
1078
+ invocation: io.invocation,
1079
+ env: io.env
930
1080
  })
931
1081
  );
932
1082
  return;
933
1083
  }
934
- const raw = positional ?? await new Response(io.stdin).text();
1084
+ const raw = positional ?? await readStdinInput(io.stdin, wrappedScript.name);
935
1085
  if (raw.trim().length === 0) {
936
1086
  throw new Error(
937
1087
  `Missing JSON input for "${wrappedScript.name}". Pass it as a positional argument or via stdin.`
@@ -940,9 +1090,33 @@ async function handleIfScriptMainBody(wrappedScript, connectionResolvers, io) {
940
1090
  const input = wrappedScript.inputSchema.parse(JSON.parse(raw));
941
1091
  const hasConnections = [...walkConnections(wrappedScript)].length > 0;
942
1092
  const runOpts = hasConnections ? buildRunOptionsFromEnv(wrappedScript, io.env, connectionResolvers) : void 0;
1093
+ if (runOpts !== void 0) {
1094
+ const missing = [];
1095
+ for (const { slotName, connectionKey } of walkConnections(wrappedScript)) {
1096
+ const value = slotName === void 0 ? "connection" in runOpts ? runOpts.connection : void 0 : "connections" in runOpts ? runOpts.connections[slotName] : void 0;
1097
+ if (value === void 0) {
1098
+ missing.push({
1099
+ slotName,
1100
+ connectionKey,
1101
+ resolvers: resolversForKey(connectionResolvers, connectionKey)
1102
+ });
1103
+ }
1104
+ }
1105
+ if (missing.length > 0) {
1106
+ throw buildMissingConnectionError(wrappedScript.name, missing);
1107
+ }
1108
+ }
943
1109
  const result = await wrappedScript.run(input, runOpts);
944
1110
  io.stdout.write(JSON.stringify(result, null, 2) + "\n");
945
1111
  }
1112
+ async function readStdinInput(stdin, scriptName) {
1113
+ if (stdin.isTTY) {
1114
+ throw new Error(
1115
+ `Missing JSON input for "${scriptName}". Pass it as a positional argument or pipe it via stdin.`
1116
+ );
1117
+ }
1118
+ return new Response(stdin).text();
1119
+ }
946
1120
  function buildHelpText(definition, connectionResolvers, opts = {}) {
947
1121
  const lines = [];
948
1122
  const description = definition.description.split("\n")[0]?.trim() ?? "";
@@ -966,7 +1140,8 @@ function buildHelpText(definition, connectionResolvers, opts = {}) {
966
1140
  }
967
1141
  const connectionsBlock = formatHelpForConnections(
968
1142
  definition,
969
- connectionResolvers
1143
+ connectionResolvers,
1144
+ opts.env
970
1145
  );
971
1146
  if (connectionsBlock.length > 0) {
972
1147
  lines.push(...connectionsBlock);