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