@zapier/connectors-sdk 0.1.0-experimental.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,933 @@
1
+ // src/connection-resolvers.ts
2
+ function defineConnectionResolver(resolver) {
3
+ return resolver;
4
+ }
5
+ var zapierConnectionResolver = defineConnectionResolver({
6
+ name: "zapier-connection-id",
7
+ resolve: async (connectionId) => {
8
+ const { buildZapierFetch } = await import("./build-zapier-fetch-DWCYBAF4.js");
9
+ return buildZapierFetch(connectionId);
10
+ }
11
+ });
12
+ function defineBearerTokenResolver(opts = {}) {
13
+ const { scheme = "Bearer", name = "token" } = opts;
14
+ return defineConnectionResolver({
15
+ name,
16
+ resolve: (token) => (input, init = {}) => globalThis.fetch(input, {
17
+ ...init,
18
+ headers: {
19
+ ...init.headers ?? {},
20
+ Authorization: `${scheme} ${token}`
21
+ }
22
+ })
23
+ });
24
+ }
25
+
26
+ // src/connection-key.ts
27
+ function envFromKey(name) {
28
+ return name.toUpperCase().replace(/-/g, "_");
29
+ }
30
+
31
+ // src/normalize-connections.ts
32
+ function* walkConnections(definition) {
33
+ if (typeof definition.connection === "string") {
34
+ yield { slotName: void 0, connectionKey: definition.connection };
35
+ return;
36
+ }
37
+ if (definition.connections) {
38
+ for (const [slotName, connectionKey] of Object.entries(
39
+ definition.connections
40
+ )) {
41
+ yield { slotName, connectionKey };
42
+ }
43
+ }
44
+ }
45
+ function resolverArray(entry) {
46
+ return Array.isArray(entry) ? entry : [entry];
47
+ }
48
+ function bagKeysFor(resolver) {
49
+ const head = envFromKey(resolver.name);
50
+ if (resolver.keySuffixes === void 0) return [head];
51
+ return resolver.keySuffixes.map((s) => `${head}_${s}`);
52
+ }
53
+ function envVarsFor(slotName, connectionKey, resolver) {
54
+ const slotPrefix = slotName ? `${envFromKey(slotName)}_` : "";
55
+ const prefix = `${slotPrefix}${envFromKey(connectionKey)}_`;
56
+ return bagKeysFor(resolver).map((k) => `${prefix}${k}`);
57
+ }
58
+ function slotErrorLabel(slotName) {
59
+ return slotName ? `connections.${slotName}` : "connection";
60
+ }
61
+ function validateConnectionResolvers(scriptsByName, connectionResolvers) {
62
+ const referenced = /* @__PURE__ */ new Set();
63
+ for (const definition of Object.values(scriptsByName)) {
64
+ for (const { connectionKey } of walkConnections(definition)) {
65
+ referenced.add(connectionKey);
66
+ }
67
+ }
68
+ const missing = [];
69
+ for (const key of referenced) {
70
+ if (!(key in connectionResolvers)) missing.push(key);
71
+ }
72
+ if (missing.length > 0) {
73
+ throw new Error(
74
+ `connectionResolvers is missing entries for connection key(s): ${missing.map((k) => `"${k}"`).join(", ")}. Each connection key referenced by a script's \`connection:\` / \`connections.<slot>:\` field must have a resolver entry.`
75
+ );
76
+ }
77
+ for (const [key, entry] of Object.entries(connectionResolvers)) {
78
+ if (!referenced.has(key)) continue;
79
+ const resolvers = resolverArray(entry);
80
+ if (resolvers.length === 0) {
81
+ throw new Error(
82
+ `connectionResolvers["${key}"] is an empty array. Provide at least one resolver.`
83
+ );
84
+ }
85
+ const seen = /* @__PURE__ */ new Map();
86
+ for (let i = 0; i < resolvers.length; i++) {
87
+ const resolver = resolvers[i];
88
+ const name = resolver.name;
89
+ const previous = seen.get(name);
90
+ if (previous !== void 0) {
91
+ throw new Error(
92
+ `connectionResolvers["${key}"]: two resolvers share name "${name}" (indexes ${previous} and ${i}). Resolver names must be unique within a connection key \u2014 they compose to the env-var segment.`
93
+ );
94
+ }
95
+ seen.set(name, i);
96
+ }
97
+ }
98
+ }
99
+ function resolversForKey(connectionResolvers, connectionKey) {
100
+ const entry = connectionResolvers[connectionKey];
101
+ if (entry === void 0) {
102
+ throw new Error(
103
+ `connectionResolvers has no entry for connection key "${connectionKey}".`
104
+ );
105
+ }
106
+ return resolverArray(entry);
107
+ }
108
+ function pickResolverFromEnv(resolvers, slotName, connectionKey, env) {
109
+ for (const resolver of resolvers) {
110
+ const envVars = envVarsFor(slotName, connectionKey, resolver);
111
+ if (envVars.every((k) => typeof env[k] === "string" && env[k] !== "")) {
112
+ return { resolver, envVars };
113
+ }
114
+ }
115
+ return null;
116
+ }
117
+ function envValuesToConnectionValue(resolver, envVars, env) {
118
+ const head = envFromKey(resolver.name);
119
+ if (resolver.keySuffixes === void 0) {
120
+ return { [head]: env[envVars[0]] };
121
+ }
122
+ const bag = {};
123
+ for (let i = 0; i < resolver.keySuffixes.length; i++) {
124
+ const suffix = resolver.keySuffixes[i];
125
+ bag[`${head}_${suffix}`] = env[envVars[i]];
126
+ }
127
+ return bag;
128
+ }
129
+ function buildRunOptionsFromEnv(definition, env, connectionResolvers) {
130
+ if (definition.connection === void 0 && definition.connections === void 0) {
131
+ return {};
132
+ }
133
+ if (typeof definition.connection === "string") {
134
+ const resolvers = resolversForKey(
135
+ connectionResolvers,
136
+ definition.connection
137
+ );
138
+ const picked = pickResolverFromEnv(
139
+ resolvers,
140
+ void 0,
141
+ definition.connection,
142
+ env
143
+ );
144
+ if (picked === null) {
145
+ return { connection: void 0 };
146
+ }
147
+ return {
148
+ connection: envValuesToConnectionValue(
149
+ picked.resolver,
150
+ picked.envVars,
151
+ env
152
+ )
153
+ };
154
+ }
155
+ const wrapped = {};
156
+ for (const [slotName, connectionKey] of Object.entries(
157
+ definition.connections
158
+ )) {
159
+ const resolvers = resolversForKey(connectionResolvers, connectionKey);
160
+ const picked = pickResolverFromEnv(resolvers, slotName, connectionKey, env);
161
+ wrapped[slotName] = picked === null ? void 0 : envValuesToConnectionValue(picked.resolver, picked.envVars, env);
162
+ }
163
+ return { connections: wrapped };
164
+ }
165
+ function formatHelpForConnections(definition, connectionResolvers) {
166
+ const slots = [...walkConnections(definition)];
167
+ if (slots.length === 0) return [];
168
+ const lines = [];
169
+ lines.push(
170
+ "Connections (set as environment variables; do NOT pass via CLI argument):"
171
+ );
172
+ for (const { slotName, connectionKey } of slots) {
173
+ const resolvers = resolversForKey(connectionResolvers, connectionKey);
174
+ const heading = slotName ? ` ${slotName} (${connectionKey}):` : ` ${connectionKey}:`;
175
+ lines.push(heading);
176
+ for (const resolver of resolvers) {
177
+ lines.push(` ${resolver.name}:`);
178
+ for (const envVar of envVarsFor(slotName, connectionKey, resolver)) {
179
+ lines.push(` ${envVar}`);
180
+ }
181
+ }
182
+ }
183
+ return lines;
184
+ }
185
+
186
+ // src/resolve-fetch.ts
187
+ function isSingle(resolver) {
188
+ return resolver.keySuffixes === void 0;
189
+ }
190
+ function isMulti(resolver) {
191
+ return resolver.keySuffixes !== void 0;
192
+ }
193
+ async function resolveSlotFetch(scriptName, slotName, connectionKey, resolvers, value) {
194
+ if (typeof value === "function") return value;
195
+ if (typeof value === "string") {
196
+ const single = resolvers.find(isSingle);
197
+ if (!single) {
198
+ throw new Error(
199
+ slotContext(scriptName, slotName, connectionKey) + `\`string\` shorthand requires at least one single-credential resolver, but none are registered. Available resolvers: ${describeResolvers(resolvers)}.`
200
+ );
201
+ }
202
+ return single.resolve(value);
203
+ }
204
+ const bag = value;
205
+ const inputKeys = Object.keys(bag).sort();
206
+ for (const resolver of resolvers) {
207
+ const expected = [...bagKeysFor(resolver)].sort();
208
+ if (sameKeys(inputKeys, expected)) {
209
+ if (isSingle(resolver)) {
210
+ return resolver.resolve(bag[expected[0]]);
211
+ }
212
+ if (isMulti(resolver)) {
213
+ const head = `${resolver.name.toUpperCase().replace(/-/g, "_")}_`;
214
+ const stripped = {};
215
+ for (const k of expected) {
216
+ stripped[k.slice(head.length)] = bag[k];
217
+ }
218
+ return resolver.resolve(
219
+ stripped
220
+ );
221
+ }
222
+ }
223
+ }
224
+ if (Object.keys(bag).length === 0) {
225
+ throw new Error(
226
+ slotContext(scriptName, slotName, connectionKey) + `No resolver matched \u2014 the connection record is empty. Provide credentials via env vars OR a record bag. Available resolvers and shapes: ${describeResolvers(resolvers, slotName, connectionKey)}.`
227
+ );
228
+ }
229
+ throw new Error(
230
+ slotContext(scriptName, slotName, connectionKey) + `No resolver matched the record bag keys [${inputKeys.join(", ")}]. Available resolvers and shapes: ${describeResolvers(resolvers, slotName, connectionKey)}.`
231
+ );
232
+ }
233
+ function sameKeys(a, b) {
234
+ if (a.length !== b.length) return false;
235
+ for (let i = 0; i < a.length; i++) {
236
+ if (a[i] !== b[i]) return false;
237
+ }
238
+ return true;
239
+ }
240
+ function slotContext(scriptName, slotName, connectionKey) {
241
+ return `ToolDefinition "${scriptName}", ${slotErrorLabel(slotName)} (key: "${connectionKey}"): `;
242
+ }
243
+ function describeResolvers(resolvers, slotName, connectionKey) {
244
+ if (resolvers.length === 0) return "<none>";
245
+ return resolvers.map((resolver) => {
246
+ const bag = bagKeysFor(resolver);
247
+ const programmatic = `{ ${bag.join(", ")} }`;
248
+ if (slotName !== void 0 || connectionKey !== void 0) {
249
+ const envVars = connectionKey === void 0 ? bag : envVarsFor(slotName, connectionKey, resolver);
250
+ return `"${resolver.name}" -> programmatic ${programmatic} OR env [${envVars.join(", ")}]`;
251
+ }
252
+ return `"${resolver.name}" -> programmatic ${programmatic}`;
253
+ }).join("; ");
254
+ }
255
+
256
+ // src/build-context.ts
257
+ function ensureConnectionValue(scriptName, slotName, connectionKey, value) {
258
+ if (value === void 0) {
259
+ throw new Error(
260
+ `ToolDefinition "${scriptName}", ${slotErrorLabel(slotName)} (key: "${connectionKey}"): no connection value provided. Set the resolver's required env vars, pass an explicit \`{ ${slotName ? `connections: { ${slotName}: ... }` : "connection: ..."} }\`, or pass a pre-authed Fetch.`
261
+ );
262
+ }
263
+ return value;
264
+ }
265
+ async function buildContext(definition, opts, connectionResolvers) {
266
+ const hasConnection = "connection" in opts && opts.connection !== void 0;
267
+ const hasConnections = "connections" in opts && opts.connections !== void 0;
268
+ if (definition.connection === void 0 && definition.connections === void 0) {
269
+ if (hasConnection || hasConnections) {
270
+ throw new Error(
271
+ `ToolDefinition "${definition.name}" is credential-free \u2014 \`RunOptions\` must not include \`connection\` or \`connections\`.`
272
+ );
273
+ }
274
+ return {};
275
+ }
276
+ if (hasConnection && hasConnections) {
277
+ throw new Error(
278
+ `ToolDefinition "${definition.name}": \`RunOptions\` sets both \`connection\` and \`connections\`. Use one or the other.`
279
+ );
280
+ }
281
+ if (typeof definition.connection === "string") {
282
+ if (hasConnections) {
283
+ throw new Error(
284
+ `ToolDefinition "${definition.name}" is single-connection \u2014 call with \`{ connection: ... }\`, not \`{ connections: ... }\`.`
285
+ );
286
+ }
287
+ const connectionKey = definition.connection;
288
+ const resolvers = resolversForKey(connectionResolvers, connectionKey);
289
+ const value = ensureConnectionValue(
290
+ definition.name,
291
+ void 0,
292
+ connectionKey,
293
+ opts.connection
294
+ );
295
+ const fetch = await resolveSlotFetch(
296
+ definition.name,
297
+ void 0,
298
+ connectionKey,
299
+ resolvers,
300
+ value
301
+ );
302
+ return { fetch };
303
+ }
304
+ const declaredSlots = Object.keys(definition.connections);
305
+ if (hasConnection) {
306
+ throw new Error(
307
+ `ToolDefinition "${definition.name}" is multi-connection (slots: ${declaredSlots.join(", ")}) \u2014 call with \`{ connections: { ... } }\`, not \`{ connection: ... }\`.`
308
+ );
309
+ }
310
+ if (!hasConnections) {
311
+ throw new Error(
312
+ `ToolDefinition "${definition.name}" is multi-connection \u2014 \`RunOptions.connections\` is required.`
313
+ );
314
+ }
315
+ const provided = opts.connections;
316
+ const unknown = Object.keys(provided).filter(
317
+ (s) => !declaredSlots.includes(s)
318
+ );
319
+ if (unknown.length > 0) {
320
+ throw new Error(
321
+ `ToolDefinition "${definition.name}": unknown slot(s) in RunOptions: ${unknown.join(
322
+ ", "
323
+ )}. Declared: ${declaredSlots.join(", ")}.`
324
+ );
325
+ }
326
+ const fetches = {};
327
+ for (const [slotName, connectionKey] of Object.entries(
328
+ definition.connections
329
+ )) {
330
+ const resolvers = resolversForKey(connectionResolvers, connectionKey);
331
+ const value = ensureConnectionValue(
332
+ definition.name,
333
+ slotName,
334
+ connectionKey,
335
+ provided[slotName]
336
+ );
337
+ fetches[slotName] = await resolveSlotFetch(
338
+ definition.name,
339
+ slotName,
340
+ connectionKey,
341
+ resolvers,
342
+ value
343
+ );
344
+ }
345
+ return { connections: fetches };
346
+ }
347
+
348
+ // src/surfaces/openai-tool-fields.ts
349
+ import { z } from "zod";
350
+ function toolDefinitionOpenAIFields(definition) {
351
+ return {
352
+ name: definition.name,
353
+ description: definition.description ?? "",
354
+ parameters: z.toJSONSchema(definition.inputSchema)
355
+ };
356
+ }
357
+
358
+ // src/surfaces/to-chat-completion-tool.ts
359
+ function toChatCompletionTool(definition) {
360
+ const fields = toolDefinitionOpenAIFields(definition);
361
+ return {
362
+ type: "function",
363
+ function: fields
364
+ };
365
+ }
366
+
367
+ // src/surfaces/to-mcp-server-tool.ts
368
+ function toMcpServerTool(definition) {
369
+ const config = {
370
+ title: definition.title,
371
+ description: definition.description,
372
+ inputSchema: definition.inputSchema,
373
+ outputSchema: definition.outputSchema,
374
+ annotations: definition.annotations
375
+ };
376
+ if (definition.inputDependencies) {
377
+ config._meta = {
378
+ "zapier:inputDependencies": definition.inputDependencies
379
+ };
380
+ }
381
+ return config;
382
+ }
383
+
384
+ // src/surfaces/to-mcp-tool.ts
385
+ import { z as z2 } from "zod";
386
+ function toMcpTool(definition) {
387
+ const meta = {};
388
+ if (definition.inputDependencies) {
389
+ meta["zapier:inputDependencies"] = definition.inputDependencies;
390
+ }
391
+ return {
392
+ name: definition.name,
393
+ title: definition.title,
394
+ description: definition.description,
395
+ inputSchema: z2.toJSONSchema(definition.inputSchema),
396
+ outputSchema: z2.toJSONSchema(
397
+ definition.outputSchema
398
+ ),
399
+ annotations: definition.annotations,
400
+ ...Object.keys(meta).length > 0 ? { _meta: meta } : {}
401
+ };
402
+ }
403
+
404
+ // src/surfaces/to-responses-tool.ts
405
+ function toResponsesTool(definition) {
406
+ return {
407
+ type: "function",
408
+ ...toolDefinitionOpenAIFields(definition)
409
+ };
410
+ }
411
+
412
+ // src/define-connector.ts
413
+ function wrapScriptWithResolvers(definition, connectionResolvers) {
414
+ const authorRun = definition.run;
415
+ const wrappedRun = async (input, opts) => {
416
+ const validated = definition.inputSchema.parse(input);
417
+ if (definition.connection === void 0 && definition.connections === void 0) {
418
+ if (opts !== void 0 && (opts.connection !== void 0 || opts.connections !== void 0)) {
419
+ throw new Error(
420
+ `ToolDefinition "${definition.name}" is credential-free \u2014 \`RunOptions\` must not include \`connection\` or \`connections\`.`
421
+ );
422
+ }
423
+ return authorRun(validated);
424
+ }
425
+ if (opts === void 0) {
426
+ throw new Error(
427
+ `ToolDefinition "${definition.name}": \`RunOptions\` is required \u2014 pass \`{ connection: ... }\` or \`{ connections: ... }\`.`
428
+ );
429
+ }
430
+ const ctx = await buildContext(definition, opts, connectionResolvers);
431
+ return authorRun(validated, ctx);
432
+ };
433
+ return { ...definition, run: wrappedRun };
434
+ }
435
+ function defineConnector(config) {
436
+ const connectionResolvers = config.connectionResolvers ?? {};
437
+ const scriptsAsAny = config.scripts;
438
+ validateConnectionResolvers(scriptsAsAny, connectionResolvers);
439
+ const wrapped = {};
440
+ for (const [key, definition] of Object.entries(scriptsAsAny)) {
441
+ wrapped[key] = wrapScriptWithResolvers(definition, connectionResolvers);
442
+ }
443
+ return {
444
+ scripts: wrapped,
445
+ connectionResolvers,
446
+ toMcpTool,
447
+ toMcpServerTool,
448
+ toChatCompletionTool,
449
+ toResponsesTool,
450
+ buildRunOptionsFromEnv: (script, env) => buildRunOptionsFromEnv(script, env, connectionResolvers)
451
+ };
452
+ }
453
+
454
+ // src/define-tool.ts
455
+ var SLOT_KEY_PATTERN = /^[a-z][a-z0-9-]*$/;
456
+ var CONNECTION_KEY_PATTERN = /^[a-z][a-z0-9-]*$/;
457
+ function validateConnectionKey(scriptName, key) {
458
+ if (!CONNECTION_KEY_PATTERN.test(key)) {
459
+ throw new Error(
460
+ `defineTool("${scriptName}"): connection key "${key}" must be lowercase kebab-case (e.g. "notion", "google-drive").`
461
+ );
462
+ }
463
+ }
464
+ function validateSlotName(scriptName, slot) {
465
+ if (!SLOT_KEY_PATTERN.test(slot)) {
466
+ throw new Error(
467
+ `defineTool("${scriptName}"): slot name "${slot}" must be lowercase kebab-case (e.g. "source", "target").`
468
+ );
469
+ }
470
+ }
471
+ function defineTool(config) {
472
+ const hasSingular = "connection" in config && config.connection !== void 0;
473
+ const hasPlural = "connections" in config && config.connections !== void 0;
474
+ if (hasSingular && hasPlural) {
475
+ throw new Error(
476
+ `defineTool: config sets both \`connection\` (singular) and \`connections\` (plural). Use one or the other.`
477
+ );
478
+ }
479
+ if (hasSingular) {
480
+ if (typeof config.connection !== "string") {
481
+ throw new Error(
482
+ `defineTool("${config.name}"): \`connection\` must be a string (the connection key registered in the connector's \`connectionResolvers\`).`
483
+ );
484
+ }
485
+ validateConnectionKey(config.name, config.connection);
486
+ }
487
+ if (hasPlural) {
488
+ if (typeof config.connections !== "object" || config.connections === null || Array.isArray(config.connections)) {
489
+ throw new Error(
490
+ `defineTool("${config.name}"): \`connections\` must be an object mapping slot names to connection keys.`
491
+ );
492
+ }
493
+ const slotKeys = Object.keys(config.connections);
494
+ if (slotKeys.length === 0) {
495
+ throw new Error(
496
+ `defineTool("${config.name}"): \`connections: {}\` is empty. Declare at least one slot.`
497
+ );
498
+ }
499
+ for (const slot of slotKeys) {
500
+ validateSlotName(config.name, slot);
501
+ const value = config.connections[slot];
502
+ if (typeof value !== "string") {
503
+ throw new Error(
504
+ `defineTool("${config.name}"): slot "${slot}" must reference a connection key string.`
505
+ );
506
+ }
507
+ validateConnectionKey(config.name, value);
508
+ }
509
+ }
510
+ const base = {
511
+ kind: "tool",
512
+ name: config.name,
513
+ title: config.title,
514
+ description: config.description,
515
+ inputSchema: config.inputSchema,
516
+ outputSchema: config.outputSchema,
517
+ annotations: config.annotations,
518
+ statements: config.statements,
519
+ inputDependencies: config.inputDependencies,
520
+ run: config.run
521
+ };
522
+ if (hasSingular) base.connection = config.connection;
523
+ if (hasPlural) base.connections = config.connections;
524
+ return base;
525
+ }
526
+
527
+ // src/surfaces/handle-if-script-main.ts
528
+ import { z as z3 } from "zod";
529
+
530
+ // src/surfaces/run-dispatch-cli.ts
531
+ import * as fs2 from "fs/promises";
532
+ import * as path2 from "path";
533
+ import { fileURLToPath as fileURLToPath2 } from "url";
534
+
535
+ // src/surfaces/serve-mcp-stdio.ts
536
+ import * as fs from "fs/promises";
537
+ import * as path from "path";
538
+ import { fileURLToPath } from "url";
539
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
540
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
541
+ function buildMcpRegistry(scripts, env, connectionResolvers) {
542
+ const registry = {};
543
+ for (const script of Object.values(scripts)) {
544
+ const hasConnections = [...walkConnections(script)].length > 0;
545
+ registry[script.name] = {
546
+ script,
547
+ runOpts: hasConnections ? buildRunOptionsFromEnv(script, env, connectionResolvers) : void 0
548
+ };
549
+ }
550
+ return registry;
551
+ }
552
+ var FALLBACK_SERVER_NAME = "@zapier/connector";
553
+ var FALLBACK_SERVER_VERSION = "0.0.0";
554
+ async function resolveServerInfo(meta) {
555
+ if (!meta.url) {
556
+ return { name: FALLBACK_SERVER_NAME, version: FALLBACK_SERVER_VERSION };
557
+ }
558
+ try {
559
+ const cliPath = fileURLToPath(meta.url);
560
+ const pkgPath = path.join(path.dirname(cliPath), "package.json");
561
+ const raw = await fs.readFile(pkgPath, "utf8");
562
+ const pkg = JSON.parse(raw);
563
+ return {
564
+ name: typeof pkg.name === "string" ? pkg.name : FALLBACK_SERVER_NAME,
565
+ version: typeof pkg.version === "string" ? pkg.version : FALLBACK_SERVER_VERSION
566
+ };
567
+ } catch {
568
+ return { name: FALLBACK_SERVER_NAME, version: FALLBACK_SERVER_VERSION };
569
+ }
570
+ }
571
+ async function serveMcpStdio(meta, connector, opts = {}) {
572
+ const env = opts.env ?? process.env;
573
+ const stderr = opts.stderr ?? process.stderr;
574
+ const scripts = connector.scripts;
575
+ const connectionResolvers = connector.connectionResolvers;
576
+ const registry = buildMcpRegistry(scripts, env, connectionResolvers);
577
+ const serverInfo = opts.serverInfo ?? await resolveServerInfo(meta);
578
+ const server = new McpServer(serverInfo);
579
+ for (const { script, runOpts } of Object.values(registry)) {
580
+ server.registerTool(
581
+ script.name,
582
+ toMcpServerTool(script),
583
+ // `registerTool` runs the zod input parse internally and hands the
584
+ // parsed value to the callback — matching the wrapped run contract
585
+ // (which also runs the parse, harmlessly twice). Wide cast on
586
+ // `input` because the registry walk is polymorphic over every
587
+ // script's schema.
588
+ async (input) => {
589
+ const result = await script.run(input, runOpts);
590
+ return {
591
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
592
+ structuredContent: result
593
+ };
594
+ }
595
+ );
596
+ }
597
+ await server.connect(new StdioServerTransport());
598
+ stderr.write(`[${serverInfo.name}] MCP server ready on stdio.
599
+ `);
600
+ }
601
+
602
+ // src/surfaces/run-dispatch-cli.ts
603
+ function asAnyDefinition(value) {
604
+ return value;
605
+ }
606
+ async function runDispatchCli(meta, connector) {
607
+ if (!meta.main) return;
608
+ try {
609
+ await runDispatchCliBody(connector, {
610
+ meta,
611
+ argv: process.argv,
612
+ env: process.env,
613
+ stdin: process.stdin,
614
+ stdout: process.stdout,
615
+ stderr: process.stderr
616
+ });
617
+ } catch (err) {
618
+ reportFatalErrorAndExit(err, process.stderr, (code) => process.exit(code));
619
+ }
620
+ }
621
+ function reportFatalErrorAndExit(err, stderr, exit) {
622
+ const message = err instanceof Error ? err.stack ?? `${err.name}: ${err.message}` : String(err);
623
+ stderr.write(`${message}
624
+ `);
625
+ exit(1);
626
+ }
627
+ async function runDispatchCliBody(connector, opts) {
628
+ const args = opts.argv.slice(2);
629
+ const first = args[0];
630
+ const errOut = opts.stderr ?? opts.stdout;
631
+ const scripts = connector.scripts;
632
+ const connectionResolvers = connector.connectionResolvers;
633
+ const packageName = await resolvePackageName(opts.meta);
634
+ if (!first || first === "--help" || first === "-h") {
635
+ printUsage(scripts, opts.stdout, packageName);
636
+ return;
637
+ }
638
+ if (first === "mcp") {
639
+ const second = args[1];
640
+ if (second === "--help" || second === "-h") {
641
+ printMcpHelp(scripts, opts.stdout, packageName);
642
+ return;
643
+ }
644
+ if (args.length > 1) {
645
+ printUsage(scripts, errOut, packageName);
646
+ throw new Error(
647
+ `Unexpected argument(s) after \`mcp\`: ${args.slice(1).map((a) => `"${a}"`).join(
648
+ ", "
649
+ )}. Usage: \`<bin> mcp\` (no arguments; run \`<bin> mcp --help\` for details).`
650
+ );
651
+ }
652
+ const serve = opts.serveMcpStdio ?? serveMcpStdio;
653
+ if (!opts.meta) {
654
+ throw new Error(
655
+ "runDispatchCliBody: `mcp` requires `meta` in options (the connector's `import.meta` from `cli.ts`)."
656
+ );
657
+ }
658
+ await serve(opts.meta, connector, {
659
+ env: opts.env,
660
+ stderr: opts.stderr
661
+ });
662
+ return;
663
+ }
664
+ if (first !== "run") {
665
+ printUsage(scripts, errOut, packageName);
666
+ throw new Error(
667
+ `Unknown command "${first}". Use \`run <script>\` to dispatch a script (available: ${Object.keys(scripts).join(", ") || "<none>"}), or \`mcp\` to boot a local MCP server.`
668
+ );
669
+ }
670
+ const scriptName = args[1];
671
+ if (!scriptName) {
672
+ printUsage(scripts, errOut, packageName);
673
+ throw new Error(
674
+ `Missing script name after \`run\`. Available: ${Object.keys(scripts).join(", ") || "<none>"}.`
675
+ );
676
+ }
677
+ const rest = args.slice(2);
678
+ const script = scripts[scriptName];
679
+ if (!script) {
680
+ printUsage(scripts, errOut, packageName);
681
+ throw new Error(
682
+ `Unknown script "${scriptName}". Available: ${Object.keys(scripts).join(", ") || "<none>"}.`
683
+ );
684
+ }
685
+ const invocation = packageName ? { bin: `npx ${packageName}`, scriptName } : void 0;
686
+ await handleIfScriptMainBody(asAnyDefinition(script), connectionResolvers, {
687
+ argv: ["node", scriptName, ...rest],
688
+ env: opts.env,
689
+ stdin: opts.stdin,
690
+ stdout: opts.stdout,
691
+ invocation
692
+ });
693
+ }
694
+ async function resolvePackageName(meta) {
695
+ if (!meta?.url) return void 0;
696
+ try {
697
+ const cliPath = fileURLToPath2(meta.url);
698
+ const pkgPath = path2.join(path2.dirname(cliPath), "package.json");
699
+ const raw = await fs2.readFile(pkgPath, "utf8");
700
+ const pkg = JSON.parse(raw);
701
+ return typeof pkg.name === "string" ? pkg.name : void 0;
702
+ } catch {
703
+ return void 0;
704
+ }
705
+ }
706
+ function printUsage(scripts, out, packageName) {
707
+ const names = Object.keys(scripts);
708
+ out.write(
709
+ `Usage: <bin> run <script> [<json-input>]
710
+ <bin> run <script> --help (per-script input + required env vars)
711
+ <bin> mcp (run as a local MCP server over stdio)
712
+
713
+ `
714
+ );
715
+ const firstScript = names[0];
716
+ if (packageName && firstScript) {
717
+ out.write(`Example: npx ${packageName} run ${firstScript} --help
718
+
719
+ `);
720
+ }
721
+ if (names.length === 0) {
722
+ out.write(`Available scripts: <none>
723
+ `);
724
+ return;
725
+ }
726
+ out.write(`Available scripts:
727
+ `);
728
+ for (const name of names) {
729
+ const description = scripts[name]?.description ?? "";
730
+ const firstLine = description.split("\n")[0]?.trim() ?? "";
731
+ out.write(firstLine ? ` ${name} \u2014 ${firstLine}
732
+ ` : ` ${name}
733
+ `);
734
+ }
735
+ out.write(
736
+ `
737
+ Credentials are environment-variable only \u2014 run \`<bin> run <script> --help\` to list each script's required env vars.
738
+ `
739
+ );
740
+ }
741
+ function printMcpHelp(scripts, out, packageName) {
742
+ out.write(
743
+ `mcp \u2014 Run as a local Model Context Protocol (MCP) server over stdio.
744
+
745
+ `
746
+ );
747
+ out.write(`Usage:
748
+ <bin> mcp
749
+
750
+ `);
751
+ if (packageName) {
752
+ out.write(`Example:
753
+ npx ${packageName} mcp
754
+
755
+ `);
756
+ }
757
+ out.write(
758
+ `The server exposes every script as a native MCP tool. Credentials are
759
+ env-only \u2014 set the env vars for each script you intend to call, listed
760
+ by \`<bin> run <script> --help\`. Resolution happens once per script at
761
+ server start; credentials never traverse the MCP transport.
762
+
763
+ `
764
+ );
765
+ const names = Object.keys(scripts);
766
+ if (names.length === 0) {
767
+ out.write(`Available scripts (exposed as MCP tools): <none>
768
+ `);
769
+ return;
770
+ }
771
+ out.write(`Available scripts (exposed as MCP tools):
772
+ `);
773
+ for (const name of names) {
774
+ const description = scripts[name]?.description ?? "";
775
+ const firstLine = description.split("\n")[0]?.trim() ?? "";
776
+ out.write(firstLine ? ` ${name} \u2014 ${firstLine}
777
+ ` : ` ${name}
778
+ `);
779
+ }
780
+ }
781
+
782
+ // src/surfaces/handle-if-script-main.ts
783
+ async function handleIfScriptMain(meta, definition, opts = {}) {
784
+ if (!meta.main) return;
785
+ try {
786
+ const connectionResolvers = opts.connectionResolvers ?? {};
787
+ validateConnectionResolvers(
788
+ { [definition.name]: definition },
789
+ connectionResolvers
790
+ );
791
+ const wrapped = wrapScriptWithResolvers(definition, connectionResolvers);
792
+ await handleIfScriptMainBody(wrapped, connectionResolvers, {
793
+ argv: process.argv,
794
+ env: process.env,
795
+ stdin: process.stdin,
796
+ stdout: process.stdout
797
+ });
798
+ } catch (err) {
799
+ reportFatalErrorAndExit(err, process.stderr, (code) => process.exit(code));
800
+ }
801
+ }
802
+ async function handleIfScriptMainBody(wrappedScript, connectionResolvers, io) {
803
+ const args = io.argv.slice(2);
804
+ let positional;
805
+ let helpRequested = false;
806
+ for (const arg of args) {
807
+ if (arg === "--help" || arg === "-h") {
808
+ helpRequested = true;
809
+ } else if (arg.startsWith("--")) {
810
+ throw new Error(
811
+ `Unknown flag "${arg}". The per-script CLI accepts only \`--help\` and a positional JSON-encoded input. Credentials are env-only \u2014 set the resolver's required env vars (run with \`--help\` to list them).`
812
+ );
813
+ } else if (positional === void 0) {
814
+ positional = arg;
815
+ } else {
816
+ throw new Error(
817
+ `Unexpected extra positional argument "${arg}". The per-script CLI accepts a single positional JSON input.`
818
+ );
819
+ }
820
+ }
821
+ if (helpRequested) {
822
+ io.stdout.write(
823
+ buildHelpText(wrappedScript, connectionResolvers, {
824
+ invocation: io.invocation
825
+ })
826
+ );
827
+ return;
828
+ }
829
+ const raw = positional ?? await new Response(io.stdin).text();
830
+ if (raw.trim().length === 0) {
831
+ throw new Error(
832
+ `Missing JSON input for "${wrappedScript.name}". Pass it as a positional argument or via stdin.`
833
+ );
834
+ }
835
+ const input = wrappedScript.inputSchema.parse(JSON.parse(raw));
836
+ const hasConnections = [...walkConnections(wrappedScript)].length > 0;
837
+ const runOpts = hasConnections ? buildRunOptionsFromEnv(wrappedScript, io.env, connectionResolvers) : void 0;
838
+ const result = await wrappedScript.run(input, runOpts);
839
+ io.stdout.write(JSON.stringify(result, null, 2) + "\n");
840
+ }
841
+ function buildHelpText(definition, connectionResolvers, opts = {}) {
842
+ const lines = [];
843
+ const description = definition.description.split("\n")[0]?.trim() ?? "";
844
+ lines.push(
845
+ description ? `${definition.name} \u2014 ${description}` : definition.name
846
+ );
847
+ lines.push("");
848
+ lines.push("Usage:");
849
+ if (opts.invocation) {
850
+ lines.push(` <bin> run ${opts.invocation.scriptName} '<json-input>'`);
851
+ } else {
852
+ lines.push(` <bin> '<json-input>'`);
853
+ }
854
+ lines.push("");
855
+ if (opts.invocation) {
856
+ lines.push("Example:");
857
+ lines.push(
858
+ ` ${formatExampleCommand(definition, connectionResolvers, opts.invocation)}`
859
+ );
860
+ lines.push("");
861
+ }
862
+ const connectionsBlock = formatHelpForConnections(
863
+ definition,
864
+ connectionResolvers
865
+ );
866
+ if (connectionsBlock.length > 0) {
867
+ lines.push(...connectionsBlock);
868
+ lines.push("");
869
+ } else {
870
+ lines.push("(No connections required.)");
871
+ lines.push("");
872
+ }
873
+ const inputBlock = formatHelpForInput(definition);
874
+ if (inputBlock.length > 0) {
875
+ lines.push(...inputBlock);
876
+ lines.push("");
877
+ }
878
+ return lines.join("\n");
879
+ }
880
+ function formatExampleCommand(definition, connectionResolvers, invocation) {
881
+ const envAssignments = [];
882
+ for (const { slotName, connectionKey } of walkConnections(definition)) {
883
+ const entry = connectionResolvers[connectionKey];
884
+ if (entry === void 0) continue;
885
+ const firstResolver = resolverArray(entry)[0];
886
+ if (firstResolver === void 0) continue;
887
+ for (const envVar of envVarsFor(slotName, connectionKey, firstResolver)) {
888
+ envAssignments.push(`${envVar}=<value>`);
889
+ }
890
+ }
891
+ const parts = [
892
+ ...envAssignments,
893
+ invocation.bin,
894
+ "run",
895
+ invocation.scriptName,
896
+ "'<json-input>'"
897
+ ];
898
+ return parts.join(" ");
899
+ }
900
+ function formatHelpForInput(definition) {
901
+ let schema;
902
+ try {
903
+ schema = z3.toJSONSchema(definition.inputSchema);
904
+ } catch {
905
+ return [];
906
+ }
907
+ const lines = ["Input (JSON Schema):"];
908
+ const json = JSON.stringify(schema, null, 2);
909
+ for (const line of json.split("\n")) {
910
+ lines.push(` ${line}`);
911
+ }
912
+ return lines;
913
+ }
914
+
915
+ // src/surfaces/to-functions.ts
916
+ function toFunctions(connector) {
917
+ const out = {};
918
+ const scripts = connector.scripts;
919
+ for (const key of Object.keys(scripts)) {
920
+ out[key] = scripts[key].run;
921
+ }
922
+ return out;
923
+ }
924
+ export {
925
+ defineBearerTokenResolver,
926
+ defineConnectionResolver,
927
+ defineConnector,
928
+ defineTool,
929
+ handleIfScriptMain,
930
+ runDispatchCli,
931
+ toFunctions,
932
+ zapierConnectionResolver
933
+ };