@zapier/connectors-sdk 0.1.0-experimental.11 → 0.1.0-experimental.13
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/README.internal.md +12 -11
- package/dist/index.cjs +207 -57
- package/dist/index.d.cts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +207 -57
- package/package.json +4 -1
package/README.internal.md
CHANGED
|
@@ -48,16 +48,16 @@ import {
|
|
|
48
48
|
|
|
49
49
|
A **surface** is how a `ToolDefinition` is exposed to a consumer. **Registration surfaces** publish tool metadata; **execution surfaces** run a tool.
|
|
50
50
|
|
|
51
|
-
| Surface | Kind | Helper | Surface shape / behavior
|
|
52
|
-
| ----------------------------- | ------------ | ------------------------------ |
|
|
53
|
-
| MCP `tools/list` (descriptor) | registration | `toMcpTool` | Wire-format `Tool` (JSON Schema'd `inputSchema`, optional `_meta`)
|
|
54
|
-
| MCP `McpServer.registerTool` | registration | `toMcpServerTool` | `registerTool` config (Zod schemas pass-through, optional `_meta`)
|
|
55
|
-
| OpenAI Chat Completions | registration | `toChatCompletionTool` | `{ type: "function", function: { name, description, parameters } }`
|
|
56
|
-
| OpenAI Responses | registration | `toResponsesTool` | `{ type: "function", name, description, parameters }`
|
|
57
|
-
| Per-script CLI | execution | `handleIfScriptMain` | argv/stdin JSON in → wrapped `script.run` → JSON stdout
|
|
58
|
-
| Connector bin | execution | `runDispatchCli` | `<bin> run <script> [args…]` → per-script CLI; `<bin> mcp` → local MCP server
|
|
59
|
-
| Local MCP server | execution | `runDispatchCli` (`<bin> mcp`) | Stdio MCP server
|
|
60
|
-
| In-process run | execution | `script.run` / named export | Typed input + `RunOptions` → output
|
|
51
|
+
| Surface | Kind | Helper | Surface shape / behavior |
|
|
52
|
+
| ----------------------------- | ------------ | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
53
|
+
| MCP `tools/list` (descriptor) | registration | `toMcpTool` | Wire-format `Tool` (JSON Schema'd `inputSchema`, optional `_meta`) |
|
|
54
|
+
| MCP `McpServer.registerTool` | registration | `toMcpServerTool` | `registerTool` config (Zod schemas pass-through, optional `_meta`) |
|
|
55
|
+
| OpenAI Chat Completions | registration | `toChatCompletionTool` | `{ type: "function", function: { name, description, parameters } }` |
|
|
56
|
+
| OpenAI Responses | registration | `toResponsesTool` | `{ type: "function", name, description, parameters }` |
|
|
57
|
+
| Per-script CLI | execution | `handleIfScriptMain` | argv/stdin JSON in → wrapped `script.run` → JSON stdout |
|
|
58
|
+
| Connector bin | execution | `runDispatchCli` | `<bin> run <script> [args…]` → per-script CLI; `<bin> mcp` → local MCP server |
|
|
59
|
+
| Local MCP server | execution | `runDispatchCli` (`<bin> mcp`) | Stdio MCP server: every script as a tool (`tools/list` + `tools/call`), plus `SKILL.md` + `references/*.md` as resources (`resources/list` + `resources/read`) |
|
|
60
|
+
| In-process run | execution | `script.run` / named export | Typed input + `RunOptions` → output |
|
|
61
61
|
|
|
62
62
|
## Authoring a script — `defineTool`
|
|
63
63
|
|
|
@@ -353,7 +353,8 @@ Reached through the bundled CLI as `npx @zapier/<x>-connector mcp` — no per-ap
|
|
|
353
353
|
1. Builds an in-memory registry keyed by `script.name`, with each entry holding the script and `RunOptions` resolved once via `buildRunOptionsFromEnv` against the connector's resolvers.
|
|
354
354
|
2. Resolves server identity (`name` / `version`) from the `package.json` adjacent to the connector's `cli.ts` (via `meta.url`).
|
|
355
355
|
3. For each script, calls `server.registerTool(script.name, toMcpServerTool(script), cb)` — `McpServer` handles `tools/list` and runs the Zod input parse before dispatching to `script.run(input, runOpts)`. The callback returns the result as both `structuredContent` and a JSON-stringified text part.
|
|
356
|
-
4.
|
|
356
|
+
4. For each bundled doc — `SKILL.md` and each `references/*.md` (discovered recursively) — calls `server.registerResource(...)` with a stable `connector://<slug>/…` URI and `text/markdown` type. Content is re-read from disk on each `resources/read` (link-rewriting included, so it matches the CLI's `skill` / `reference` output). Skipped entirely when `meta.url` is absent or the connector ships no docs, so the `resources` capability is only advertised when there is something to serve.
|
|
357
|
+
5. Connects via `StdioServerTransport` and writes a one-line ready banner to stderr.
|
|
357
358
|
|
|
358
359
|
```bash
|
|
359
360
|
NOTION_TOKEN=secret_xxx npx @zapier/notion-connector mcp
|
package/dist/index.cjs
CHANGED
|
@@ -706,14 +706,74 @@ function defineTool(config) {
|
|
|
706
706
|
var import_zod3 = require("zod");
|
|
707
707
|
|
|
708
708
|
// src/surfaces/run-dispatch-cli.ts
|
|
709
|
-
var
|
|
710
|
-
var
|
|
711
|
-
var
|
|
709
|
+
var fs3 = __toESM(require("fs/promises"), 1);
|
|
710
|
+
var path3 = __toESM(require("path"), 1);
|
|
711
|
+
var import_node_url3 = require("url");
|
|
712
712
|
|
|
713
|
-
// src/surfaces/
|
|
713
|
+
// src/surfaces/connector-docs.ts
|
|
714
714
|
var fs = __toESM(require("fs/promises"), 1);
|
|
715
715
|
var path = __toESM(require("path"), 1);
|
|
716
716
|
var import_node_url = require("url");
|
|
717
|
+
var SKILL_FILENAME = "SKILL.md";
|
|
718
|
+
var REFERENCES_DIRNAME = "references";
|
|
719
|
+
function connectorDirFromMeta(meta) {
|
|
720
|
+
const dir = path.dirname((0, import_node_url.fileURLToPath)(meta.url));
|
|
721
|
+
return path.basename(dir) === "dist" ? path.dirname(dir) : dir;
|
|
722
|
+
}
|
|
723
|
+
function skillPath(connectorDir) {
|
|
724
|
+
return path.join(connectorDir, SKILL_FILENAME);
|
|
725
|
+
}
|
|
726
|
+
function referencePath(connectorDir, name) {
|
|
727
|
+
return path.join(connectorDir, REFERENCES_DIRNAME, `${name}.md`);
|
|
728
|
+
}
|
|
729
|
+
async function loadSkillDoc(connectorDir) {
|
|
730
|
+
return loadDoc(skillPath(connectorDir), connectorDir);
|
|
731
|
+
}
|
|
732
|
+
async function listReferenceNames(connectorDir) {
|
|
733
|
+
try {
|
|
734
|
+
const entries = await fs.readdir(
|
|
735
|
+
path.join(connectorDir, REFERENCES_DIRNAME),
|
|
736
|
+
{ recursive: true }
|
|
737
|
+
);
|
|
738
|
+
return entries.filter((entry) => entry.endsWith(".md")).map((entry) => toPosix(entry).slice(0, -".md".length));
|
|
739
|
+
} catch {
|
|
740
|
+
return [];
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
function toPosix(p) {
|
|
744
|
+
return p.split(path.sep).join("/");
|
|
745
|
+
}
|
|
746
|
+
async function loadReferenceDoc(connectorDir, name) {
|
|
747
|
+
return loadDoc(
|
|
748
|
+
referencePath(connectorDir, name),
|
|
749
|
+
path.join(connectorDir, REFERENCES_DIRNAME)
|
|
750
|
+
);
|
|
751
|
+
}
|
|
752
|
+
async function loadDoc(filePath, baseDir) {
|
|
753
|
+
let raw;
|
|
754
|
+
try {
|
|
755
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
756
|
+
} catch {
|
|
757
|
+
return void 0;
|
|
758
|
+
}
|
|
759
|
+
return { path: filePath, content: resolveRelativeLinks(raw, baseDir) };
|
|
760
|
+
}
|
|
761
|
+
function resolveRelativeLinks(content, baseDir) {
|
|
762
|
+
return content.replace(
|
|
763
|
+
/(!?\[([^\]]*)\])\(([^)]+)\)/g,
|
|
764
|
+
(match, prefix, _alt, url) => {
|
|
765
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file:") || url.startsWith("#") || url.startsWith("/")) {
|
|
766
|
+
return match;
|
|
767
|
+
}
|
|
768
|
+
return `${prefix}(${path.resolve(baseDir, url)})`;
|
|
769
|
+
}
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// src/surfaces/serve-mcp-stdio.ts
|
|
774
|
+
var fs2 = __toESM(require("fs/promises"), 1);
|
|
775
|
+
var path2 = __toESM(require("path"), 1);
|
|
776
|
+
var import_node_url2 = require("url");
|
|
717
777
|
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
718
778
|
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
719
779
|
function buildMcpRegistry(scripts, env, connectionResolvers) {
|
|
@@ -734,9 +794,9 @@ async function resolveServerInfo(meta) {
|
|
|
734
794
|
return { name: FALLBACK_SERVER_NAME, version: FALLBACK_SERVER_VERSION };
|
|
735
795
|
}
|
|
736
796
|
try {
|
|
737
|
-
const cliPath = (0,
|
|
738
|
-
const pkgPath =
|
|
739
|
-
const raw = await
|
|
797
|
+
const cliPath = (0, import_node_url2.fileURLToPath)(meta.url);
|
|
798
|
+
const pkgPath = path2.join(path2.dirname(cliPath), "package.json");
|
|
799
|
+
const raw = await fs2.readFile(pkgPath, "utf8");
|
|
740
800
|
const pkg = JSON.parse(raw);
|
|
741
801
|
return {
|
|
742
802
|
name: typeof pkg.name === "string" ? pkg.name : FALLBACK_SERVER_NAME,
|
|
@@ -746,9 +806,8 @@ async function resolveServerInfo(meta) {
|
|
|
746
806
|
return { name: FALLBACK_SERVER_NAME, version: FALLBACK_SERVER_VERSION };
|
|
747
807
|
}
|
|
748
808
|
}
|
|
749
|
-
async function
|
|
809
|
+
async function buildMcpServer(meta, connector, opts = {}) {
|
|
750
810
|
const env = opts.env ?? process.env;
|
|
751
|
-
const stderr = opts.stderr ?? process.stderr;
|
|
752
811
|
const scripts = connector.scripts;
|
|
753
812
|
const connectionResolvers = connector.connectionResolvers;
|
|
754
813
|
const registry = buildMcpRegistry(scripts, env, connectionResolvers);
|
|
@@ -772,10 +831,69 @@ async function serveMcpStdio(meta, connector, opts = {}) {
|
|
|
772
831
|
}
|
|
773
832
|
);
|
|
774
833
|
}
|
|
834
|
+
if (meta.url) {
|
|
835
|
+
await registerDocResources(
|
|
836
|
+
server,
|
|
837
|
+
connectorSlug(serverInfo.name),
|
|
838
|
+
connectorDirFromMeta(meta)
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
return { server, serverInfo };
|
|
842
|
+
}
|
|
843
|
+
async function serveMcpStdio(meta, connector, opts = {}) {
|
|
844
|
+
const stderr = opts.stderr ?? process.stderr;
|
|
845
|
+
const { server, serverInfo } = await buildMcpServer(meta, connector, opts);
|
|
775
846
|
await server.connect(new import_stdio.StdioServerTransport());
|
|
776
847
|
stderr.write(`[${serverInfo.name}] MCP server ready on stdio.
|
|
777
848
|
`);
|
|
778
849
|
}
|
|
850
|
+
var MARKDOWN_MIME_TYPE = "text/markdown";
|
|
851
|
+
function connectorSlug(serverName) {
|
|
852
|
+
const basename2 = serverName.split("/").pop() ?? serverName;
|
|
853
|
+
return basename2.replace(/-connector$/, "") || basename2;
|
|
854
|
+
}
|
|
855
|
+
async function registerDocResources(server, slug, connectorDir) {
|
|
856
|
+
const skill = await loadSkillDoc(connectorDir);
|
|
857
|
+
if (skill) {
|
|
858
|
+
server.registerResource(
|
|
859
|
+
SKILL_FILENAME,
|
|
860
|
+
`connector://${slug}/${SKILL_FILENAME}`,
|
|
861
|
+
{
|
|
862
|
+
title: "Skill guide",
|
|
863
|
+
description: "The connector's SKILL.md: when to use it, auth setup, and the script catalog.",
|
|
864
|
+
mimeType: MARKDOWN_MIME_TYPE
|
|
865
|
+
},
|
|
866
|
+
readDoc(() => loadSkillDoc(connectorDir), skill.path)
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
for (const name of await listReferenceNames(connectorDir)) {
|
|
870
|
+
const resourceName = `${REFERENCES_DIRNAME}/${name}.md`;
|
|
871
|
+
const uriPath = name.split("/").map(encodeURIComponent).join("/");
|
|
872
|
+
server.registerResource(
|
|
873
|
+
resourceName,
|
|
874
|
+
`connector://${slug}/${REFERENCES_DIRNAME}/${uriPath}.md`,
|
|
875
|
+
{
|
|
876
|
+
title: `Reference: ${name}`,
|
|
877
|
+
description: `Durable per-app knowledge from references/${name}.md (API gotchas, encodings, edge cases).`,
|
|
878
|
+
mimeType: MARKDOWN_MIME_TYPE
|
|
879
|
+
},
|
|
880
|
+
readDoc(() => loadReferenceDoc(connectorDir, name), resourceName)
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
function readDoc(load, label) {
|
|
885
|
+
return async (uri) => {
|
|
886
|
+
const doc = await load();
|
|
887
|
+
if (!doc) {
|
|
888
|
+
throw new Error(`Resource no longer available: ${label}.`);
|
|
889
|
+
}
|
|
890
|
+
return {
|
|
891
|
+
contents: [
|
|
892
|
+
{ uri: uri.href, mimeType: MARKDOWN_MIME_TYPE, text: doc.content }
|
|
893
|
+
]
|
|
894
|
+
};
|
|
895
|
+
};
|
|
896
|
+
}
|
|
779
897
|
|
|
780
898
|
// src/surfaces/run-dispatch-cli.ts
|
|
781
899
|
function asAnyDefinition(value) {
|
|
@@ -845,18 +963,14 @@ async function runDispatchCliBody(connector, opts) {
|
|
|
845
963
|
"runDispatchCliBody: `skill` requires `meta` in options (the connector's `import.meta` from `cli.ts`)."
|
|
846
964
|
);
|
|
847
965
|
}
|
|
848
|
-
const
|
|
849
|
-
const
|
|
850
|
-
|
|
851
|
-
let content;
|
|
852
|
-
try {
|
|
853
|
-
content = await fs2.readFile(skillPath, "utf8");
|
|
854
|
-
} catch {
|
|
966
|
+
const connectorDir = connectorDirFromMeta(opts.meta);
|
|
967
|
+
const skill = await loadSkillDoc(connectorDir);
|
|
968
|
+
if (!skill) {
|
|
855
969
|
throw new Error(
|
|
856
|
-
`SKILL.md not found at ${skillPath}. Ensure the connector ships a SKILL.md.`
|
|
970
|
+
`SKILL.md not found at ${skillPath(connectorDir)}. Ensure the connector ships a SKILL.md.`
|
|
857
971
|
);
|
|
858
972
|
}
|
|
859
|
-
opts.stdout.write(
|
|
973
|
+
opts.stdout.write(skill.content);
|
|
860
974
|
return;
|
|
861
975
|
}
|
|
862
976
|
if (first === "reference") {
|
|
@@ -865,18 +979,10 @@ async function runDispatchCliBody(connector, opts) {
|
|
|
865
979
|
"runDispatchCliBody: `reference` requires `meta` in options (the connector's `import.meta` from `cli.ts`)."
|
|
866
980
|
);
|
|
867
981
|
}
|
|
868
|
-
const
|
|
869
|
-
const connectorDir = path2.dirname(cliPath);
|
|
870
|
-
const refsDir = path2.join(connectorDir, "references");
|
|
982
|
+
const connectorDir = connectorDirFromMeta(opts.meta);
|
|
871
983
|
const name = args[1];
|
|
872
984
|
if (!name) {
|
|
873
|
-
|
|
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
|
-
}
|
|
985
|
+
const entries = await listReferenceNames(connectorDir);
|
|
880
986
|
if (entries.length === 0) {
|
|
881
987
|
opts.stdout.write(`Available references: <none>
|
|
882
988
|
`);
|
|
@@ -890,16 +996,13 @@ async function runDispatchCliBody(connector, opts) {
|
|
|
890
996
|
}
|
|
891
997
|
return;
|
|
892
998
|
}
|
|
893
|
-
const
|
|
894
|
-
|
|
895
|
-
try {
|
|
896
|
-
content = await fs2.readFile(refPath, "utf8");
|
|
897
|
-
} catch {
|
|
999
|
+
const reference = await loadReferenceDoc(connectorDir, name);
|
|
1000
|
+
if (!reference) {
|
|
898
1001
|
throw new Error(
|
|
899
|
-
`Reference file not found at ${
|
|
1002
|
+
`Reference file not found at ${referencePath(connectorDir, name)}. Ensure the connector ships \`references/${name}.md\`.`
|
|
900
1003
|
);
|
|
901
1004
|
}
|
|
902
|
-
opts.stdout.write(
|
|
1005
|
+
opts.stdout.write(reference.content);
|
|
903
1006
|
return;
|
|
904
1007
|
}
|
|
905
1008
|
if (first !== "run") {
|
|
@@ -932,23 +1035,12 @@ async function runDispatchCliBody(connector, opts) {
|
|
|
932
1035
|
invocation
|
|
933
1036
|
});
|
|
934
1037
|
}
|
|
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
|
-
}
|
|
946
1038
|
async function resolvePackageName(meta) {
|
|
947
1039
|
if (!meta?.url) return void 0;
|
|
948
1040
|
try {
|
|
949
|
-
const cliPath = (0,
|
|
950
|
-
const pkgPath =
|
|
951
|
-
const raw = await
|
|
1041
|
+
const cliPath = (0, import_node_url3.fileURLToPath)(meta.url);
|
|
1042
|
+
const pkgPath = path3.join(path3.dirname(cliPath), "package.json");
|
|
1043
|
+
const raw = await fs3.readFile(pkgPath, "utf8");
|
|
952
1044
|
const pkg = JSON.parse(raw);
|
|
953
1045
|
return typeof pkg.name === "string" ? pkg.name : void 0;
|
|
954
1046
|
} catch {
|
|
@@ -958,8 +1050,8 @@ async function resolvePackageName(meta) {
|
|
|
958
1050
|
function printUsage(scripts, out, packageName) {
|
|
959
1051
|
const names = Object.keys(scripts);
|
|
960
1052
|
out.write(
|
|
961
|
-
`Usage: <bin> run <script> [<json-input>]
|
|
962
|
-
<bin> run <script> --help (per-script input
|
|
1053
|
+
`Usage: <bin> run <script> [<json-input>] [--filter <jq>]
|
|
1054
|
+
<bin> run <script> --help (per-script input/output schema, env vars, --filter)
|
|
963
1055
|
<bin> mcp (run as a local MCP server over stdio)
|
|
964
1056
|
<bin> skill (print SKILL.md to stdout)
|
|
965
1057
|
<bin> reference [<name>] (print references/<name>.md, or list all)
|
|
@@ -988,7 +1080,7 @@ function printUsage(scripts, out, packageName) {
|
|
|
988
1080
|
}
|
|
989
1081
|
out.write(
|
|
990
1082
|
`
|
|
991
|
-
|
|
1083
|
+
Run \`<bin> run <script> --help\` for a script's input/output schema, its required env vars (credentials are environment-variable only), and the \`--filter <jq>\` flag for trimming output.
|
|
992
1084
|
`
|
|
993
1085
|
);
|
|
994
1086
|
}
|
|
@@ -1014,6 +1106,13 @@ env-only \u2014 set the env vars for each script you intend to call, listed
|
|
|
1014
1106
|
by \`<bin> run <script> --help\`. Resolution happens once per script at
|
|
1015
1107
|
server start; credentials never traverse the MCP transport.
|
|
1016
1108
|
|
|
1109
|
+
`
|
|
1110
|
+
);
|
|
1111
|
+
out.write(
|
|
1112
|
+
`The connector's docs are exposed as MCP resources (text/markdown): its
|
|
1113
|
+
SKILL.md and each references/*.md file, mirroring \`<bin> skill\` and
|
|
1114
|
+
\`<bin> reference\`. Clients read them via resources/list + resources/read.
|
|
1115
|
+
|
|
1017
1116
|
`
|
|
1018
1117
|
);
|
|
1019
1118
|
const names = Object.keys(scripts);
|
|
@@ -1057,12 +1156,31 @@ async function handleIfScriptMainBody(wrappedScript, connectionResolvers, io) {
|
|
|
1057
1156
|
const args = io.argv.slice(2);
|
|
1058
1157
|
let positional;
|
|
1059
1158
|
let helpRequested = false;
|
|
1060
|
-
|
|
1159
|
+
let filter;
|
|
1160
|
+
for (let i = 0; i < args.length; i++) {
|
|
1161
|
+
const arg = args[i];
|
|
1061
1162
|
if (arg === "--help" || arg === "-h") {
|
|
1062
1163
|
helpRequested = true;
|
|
1164
|
+
} else if (arg === "--filter") {
|
|
1165
|
+
const value = args[i + 1];
|
|
1166
|
+
if (value === void 0 || value.length === 0) {
|
|
1167
|
+
throw new Error(
|
|
1168
|
+
`\`--filter\` requires a jq expression, e.g. \`--filter '.results | length'\`.`
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
filter = value;
|
|
1172
|
+
i++;
|
|
1173
|
+
} else if (arg.startsWith("--filter=")) {
|
|
1174
|
+
const value = arg.slice("--filter=".length);
|
|
1175
|
+
if (value.length === 0) {
|
|
1176
|
+
throw new Error(
|
|
1177
|
+
`\`--filter\` requires a jq expression, e.g. \`--filter '.results | length'\`.`
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
filter = value;
|
|
1063
1181
|
} else if (arg.startsWith("--")) {
|
|
1064
1182
|
throw new Error(
|
|
1065
|
-
`Unknown flag "${arg}". The per-script CLI accepts
|
|
1183
|
+
`Unknown flag "${arg}". The per-script CLI accepts \`--help\`, \`--filter <jq>\`, and a positional JSON-encoded input. Credentials are env-only \u2014 set the resolver's required env vars (run with \`--help\` to list them).`
|
|
1066
1184
|
);
|
|
1067
1185
|
} else if (positional === void 0) {
|
|
1068
1186
|
positional = arg;
|
|
@@ -1107,7 +1225,12 @@ async function handleIfScriptMainBody(wrappedScript, connectionResolvers, io) {
|
|
|
1107
1225
|
}
|
|
1108
1226
|
}
|
|
1109
1227
|
const result = await wrappedScript.run(input, runOpts);
|
|
1110
|
-
|
|
1228
|
+
const output = filter === void 0 ? result : await applyJqFilter(result, filter);
|
|
1229
|
+
io.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
1230
|
+
}
|
|
1231
|
+
async function applyJqFilter(value, filter) {
|
|
1232
|
+
const { json: runJq } = await import("jq-wasm");
|
|
1233
|
+
return runJq(value, filter);
|
|
1111
1234
|
}
|
|
1112
1235
|
async function readStdinInput(stdin, scriptName) {
|
|
1113
1236
|
if (stdin.isTTY) {
|
|
@@ -1126,11 +1249,19 @@ function buildHelpText(definition, connectionResolvers, opts = {}) {
|
|
|
1126
1249
|
lines.push("");
|
|
1127
1250
|
lines.push("Usage:");
|
|
1128
1251
|
if (opts.invocation) {
|
|
1129
|
-
lines.push(
|
|
1252
|
+
lines.push(
|
|
1253
|
+
` <bin> run ${opts.invocation.scriptName} '<json-input>' [--filter <jq>]`
|
|
1254
|
+
);
|
|
1130
1255
|
} else {
|
|
1131
|
-
lines.push(` <bin> '<json-input>'`);
|
|
1256
|
+
lines.push(` <bin> '<json-input>' [--filter <jq>]`);
|
|
1132
1257
|
}
|
|
1133
1258
|
lines.push("");
|
|
1259
|
+
lines.push(
|
|
1260
|
+
" --filter <jq> Transform the JSON output through a jq expression."
|
|
1261
|
+
);
|
|
1262
|
+
lines.push(" Useful for trimming large outputs, e.g.");
|
|
1263
|
+
lines.push(" --filter '.results | length'.");
|
|
1264
|
+
lines.push("");
|
|
1134
1265
|
if (opts.invocation) {
|
|
1135
1266
|
lines.push("Example:");
|
|
1136
1267
|
lines.push(
|
|
@@ -1155,6 +1286,11 @@ function buildHelpText(definition, connectionResolvers, opts = {}) {
|
|
|
1155
1286
|
lines.push(...inputBlock);
|
|
1156
1287
|
lines.push("");
|
|
1157
1288
|
}
|
|
1289
|
+
const outputBlock = formatHelpForOutput(definition);
|
|
1290
|
+
if (outputBlock.length > 0) {
|
|
1291
|
+
lines.push(...outputBlock);
|
|
1292
|
+
lines.push("");
|
|
1293
|
+
}
|
|
1158
1294
|
return lines.join("\n");
|
|
1159
1295
|
}
|
|
1160
1296
|
function formatExampleCommand(definition, connectionResolvers, invocation) {
|
|
@@ -1191,6 +1327,20 @@ function formatHelpForInput(definition) {
|
|
|
1191
1327
|
}
|
|
1192
1328
|
return lines;
|
|
1193
1329
|
}
|
|
1330
|
+
function formatHelpForOutput(definition) {
|
|
1331
|
+
let schema;
|
|
1332
|
+
try {
|
|
1333
|
+
schema = import_zod3.z.toJSONSchema(definition.outputSchema);
|
|
1334
|
+
} catch {
|
|
1335
|
+
return [];
|
|
1336
|
+
}
|
|
1337
|
+
const lines = ["Output (JSON Schema):"];
|
|
1338
|
+
const json = JSON.stringify(schema, null, 2);
|
|
1339
|
+
for (const line of json.split("\n")) {
|
|
1340
|
+
lines.push(` ${line}`);
|
|
1341
|
+
}
|
|
1342
|
+
return lines;
|
|
1343
|
+
}
|
|
1194
1344
|
|
|
1195
1345
|
// src/surfaces/to-functions.ts
|
|
1196
1346
|
function toFunctions(connector) {
|
package/dist/index.d.cts
CHANGED
|
@@ -463,8 +463,9 @@ declare function defineTool<TIn extends z.ZodObject<z.ZodRawShape>, TOut extends
|
|
|
463
463
|
* from `process.env` via `buildRunOptionsFromEnv`, calls the wrapped
|
|
464
464
|
* script, writes JSON.
|
|
465
465
|
*
|
|
466
|
-
* **Credentials are env-only by design.** The CLI takes
|
|
467
|
-
* JSON (the script's `inputSchema`) and `--
|
|
466
|
+
* **Credentials are env-only by design.** The CLI takes positional
|
|
467
|
+
* JSON (the script's `inputSchema`), `--help`, and `--filter <jq>` (a jq
|
|
468
|
+
* expression that post-processes the JSON output). Passing secrets via
|
|
468
469
|
* argv would leak them through shell history, `ps`, audit logs, and CI
|
|
469
470
|
* runner echo.
|
|
470
471
|
*
|
package/dist/index.d.ts
CHANGED
|
@@ -463,8 +463,9 @@ declare function defineTool<TIn extends z.ZodObject<z.ZodRawShape>, TOut extends
|
|
|
463
463
|
* from `process.env` via `buildRunOptionsFromEnv`, calls the wrapped
|
|
464
464
|
* script, writes JSON.
|
|
465
465
|
*
|
|
466
|
-
* **Credentials are env-only by design.** The CLI takes
|
|
467
|
-
* JSON (the script's `inputSchema`) and `--
|
|
466
|
+
* **Credentials are env-only by design.** The CLI takes positional
|
|
467
|
+
* JSON (the script's `inputSchema`), `--help`, and `--filter <jq>` (a jq
|
|
468
|
+
* expression that post-processes the JSON output). Passing secrets via
|
|
468
469
|
* argv would leak them through shell history, `ps`, audit logs, and CI
|
|
469
470
|
* runner echo.
|
|
470
471
|
*
|
package/dist/index.js
CHANGED
|
@@ -600,14 +600,74 @@ function defineTool(config) {
|
|
|
600
600
|
import { z as z3 } from "zod";
|
|
601
601
|
|
|
602
602
|
// src/surfaces/run-dispatch-cli.ts
|
|
603
|
-
import * as
|
|
604
|
-
import * as
|
|
605
|
-
import { fileURLToPath as
|
|
603
|
+
import * as fs3 from "fs/promises";
|
|
604
|
+
import * as path3 from "path";
|
|
605
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
606
606
|
|
|
607
|
-
// src/surfaces/
|
|
607
|
+
// src/surfaces/connector-docs.ts
|
|
608
608
|
import * as fs from "fs/promises";
|
|
609
609
|
import * as path from "path";
|
|
610
610
|
import { fileURLToPath } from "url";
|
|
611
|
+
var SKILL_FILENAME = "SKILL.md";
|
|
612
|
+
var REFERENCES_DIRNAME = "references";
|
|
613
|
+
function connectorDirFromMeta(meta) {
|
|
614
|
+
const dir = path.dirname(fileURLToPath(meta.url));
|
|
615
|
+
return path.basename(dir) === "dist" ? path.dirname(dir) : dir;
|
|
616
|
+
}
|
|
617
|
+
function skillPath(connectorDir) {
|
|
618
|
+
return path.join(connectorDir, SKILL_FILENAME);
|
|
619
|
+
}
|
|
620
|
+
function referencePath(connectorDir, name) {
|
|
621
|
+
return path.join(connectorDir, REFERENCES_DIRNAME, `${name}.md`);
|
|
622
|
+
}
|
|
623
|
+
async function loadSkillDoc(connectorDir) {
|
|
624
|
+
return loadDoc(skillPath(connectorDir), connectorDir);
|
|
625
|
+
}
|
|
626
|
+
async function listReferenceNames(connectorDir) {
|
|
627
|
+
try {
|
|
628
|
+
const entries = await fs.readdir(
|
|
629
|
+
path.join(connectorDir, REFERENCES_DIRNAME),
|
|
630
|
+
{ recursive: true }
|
|
631
|
+
);
|
|
632
|
+
return entries.filter((entry) => entry.endsWith(".md")).map((entry) => toPosix(entry).slice(0, -".md".length));
|
|
633
|
+
} catch {
|
|
634
|
+
return [];
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
function toPosix(p) {
|
|
638
|
+
return p.split(path.sep).join("/");
|
|
639
|
+
}
|
|
640
|
+
async function loadReferenceDoc(connectorDir, name) {
|
|
641
|
+
return loadDoc(
|
|
642
|
+
referencePath(connectorDir, name),
|
|
643
|
+
path.join(connectorDir, REFERENCES_DIRNAME)
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
async function loadDoc(filePath, baseDir) {
|
|
647
|
+
let raw;
|
|
648
|
+
try {
|
|
649
|
+
raw = await fs.readFile(filePath, "utf8");
|
|
650
|
+
} catch {
|
|
651
|
+
return void 0;
|
|
652
|
+
}
|
|
653
|
+
return { path: filePath, content: resolveRelativeLinks(raw, baseDir) };
|
|
654
|
+
}
|
|
655
|
+
function resolveRelativeLinks(content, baseDir) {
|
|
656
|
+
return content.replace(
|
|
657
|
+
/(!?\[([^\]]*)\])\(([^)]+)\)/g,
|
|
658
|
+
(match, prefix, _alt, url) => {
|
|
659
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file:") || url.startsWith("#") || url.startsWith("/")) {
|
|
660
|
+
return match;
|
|
661
|
+
}
|
|
662
|
+
return `${prefix}(${path.resolve(baseDir, url)})`;
|
|
663
|
+
}
|
|
664
|
+
);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/surfaces/serve-mcp-stdio.ts
|
|
668
|
+
import * as fs2 from "fs/promises";
|
|
669
|
+
import * as path2 from "path";
|
|
670
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
611
671
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
612
672
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
613
673
|
function buildMcpRegistry(scripts, env, connectionResolvers) {
|
|
@@ -628,9 +688,9 @@ async function resolveServerInfo(meta) {
|
|
|
628
688
|
return { name: FALLBACK_SERVER_NAME, version: FALLBACK_SERVER_VERSION };
|
|
629
689
|
}
|
|
630
690
|
try {
|
|
631
|
-
const cliPath =
|
|
632
|
-
const pkgPath =
|
|
633
|
-
const raw = await
|
|
691
|
+
const cliPath = fileURLToPath2(meta.url);
|
|
692
|
+
const pkgPath = path2.join(path2.dirname(cliPath), "package.json");
|
|
693
|
+
const raw = await fs2.readFile(pkgPath, "utf8");
|
|
634
694
|
const pkg = JSON.parse(raw);
|
|
635
695
|
return {
|
|
636
696
|
name: typeof pkg.name === "string" ? pkg.name : FALLBACK_SERVER_NAME,
|
|
@@ -640,9 +700,8 @@ async function resolveServerInfo(meta) {
|
|
|
640
700
|
return { name: FALLBACK_SERVER_NAME, version: FALLBACK_SERVER_VERSION };
|
|
641
701
|
}
|
|
642
702
|
}
|
|
643
|
-
async function
|
|
703
|
+
async function buildMcpServer(meta, connector, opts = {}) {
|
|
644
704
|
const env = opts.env ?? process.env;
|
|
645
|
-
const stderr = opts.stderr ?? process.stderr;
|
|
646
705
|
const scripts = connector.scripts;
|
|
647
706
|
const connectionResolvers = connector.connectionResolvers;
|
|
648
707
|
const registry = buildMcpRegistry(scripts, env, connectionResolvers);
|
|
@@ -666,10 +725,69 @@ async function serveMcpStdio(meta, connector, opts = {}) {
|
|
|
666
725
|
}
|
|
667
726
|
);
|
|
668
727
|
}
|
|
728
|
+
if (meta.url) {
|
|
729
|
+
await registerDocResources(
|
|
730
|
+
server,
|
|
731
|
+
connectorSlug(serverInfo.name),
|
|
732
|
+
connectorDirFromMeta(meta)
|
|
733
|
+
);
|
|
734
|
+
}
|
|
735
|
+
return { server, serverInfo };
|
|
736
|
+
}
|
|
737
|
+
async function serveMcpStdio(meta, connector, opts = {}) {
|
|
738
|
+
const stderr = opts.stderr ?? process.stderr;
|
|
739
|
+
const { server, serverInfo } = await buildMcpServer(meta, connector, opts);
|
|
669
740
|
await server.connect(new StdioServerTransport());
|
|
670
741
|
stderr.write(`[${serverInfo.name}] MCP server ready on stdio.
|
|
671
742
|
`);
|
|
672
743
|
}
|
|
744
|
+
var MARKDOWN_MIME_TYPE = "text/markdown";
|
|
745
|
+
function connectorSlug(serverName) {
|
|
746
|
+
const basename2 = serverName.split("/").pop() ?? serverName;
|
|
747
|
+
return basename2.replace(/-connector$/, "") || basename2;
|
|
748
|
+
}
|
|
749
|
+
async function registerDocResources(server, slug, connectorDir) {
|
|
750
|
+
const skill = await loadSkillDoc(connectorDir);
|
|
751
|
+
if (skill) {
|
|
752
|
+
server.registerResource(
|
|
753
|
+
SKILL_FILENAME,
|
|
754
|
+
`connector://${slug}/${SKILL_FILENAME}`,
|
|
755
|
+
{
|
|
756
|
+
title: "Skill guide",
|
|
757
|
+
description: "The connector's SKILL.md: when to use it, auth setup, and the script catalog.",
|
|
758
|
+
mimeType: MARKDOWN_MIME_TYPE
|
|
759
|
+
},
|
|
760
|
+
readDoc(() => loadSkillDoc(connectorDir), skill.path)
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
for (const name of await listReferenceNames(connectorDir)) {
|
|
764
|
+
const resourceName = `${REFERENCES_DIRNAME}/${name}.md`;
|
|
765
|
+
const uriPath = name.split("/").map(encodeURIComponent).join("/");
|
|
766
|
+
server.registerResource(
|
|
767
|
+
resourceName,
|
|
768
|
+
`connector://${slug}/${REFERENCES_DIRNAME}/${uriPath}.md`,
|
|
769
|
+
{
|
|
770
|
+
title: `Reference: ${name}`,
|
|
771
|
+
description: `Durable per-app knowledge from references/${name}.md (API gotchas, encodings, edge cases).`,
|
|
772
|
+
mimeType: MARKDOWN_MIME_TYPE
|
|
773
|
+
},
|
|
774
|
+
readDoc(() => loadReferenceDoc(connectorDir, name), resourceName)
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
function readDoc(load, label) {
|
|
779
|
+
return async (uri) => {
|
|
780
|
+
const doc = await load();
|
|
781
|
+
if (!doc) {
|
|
782
|
+
throw new Error(`Resource no longer available: ${label}.`);
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
contents: [
|
|
786
|
+
{ uri: uri.href, mimeType: MARKDOWN_MIME_TYPE, text: doc.content }
|
|
787
|
+
]
|
|
788
|
+
};
|
|
789
|
+
};
|
|
790
|
+
}
|
|
673
791
|
|
|
674
792
|
// src/surfaces/run-dispatch-cli.ts
|
|
675
793
|
function asAnyDefinition(value) {
|
|
@@ -739,18 +857,14 @@ async function runDispatchCliBody(connector, opts) {
|
|
|
739
857
|
"runDispatchCliBody: `skill` requires `meta` in options (the connector's `import.meta` from `cli.ts`)."
|
|
740
858
|
);
|
|
741
859
|
}
|
|
742
|
-
const
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
let content;
|
|
746
|
-
try {
|
|
747
|
-
content = await fs2.readFile(skillPath, "utf8");
|
|
748
|
-
} catch {
|
|
860
|
+
const connectorDir = connectorDirFromMeta(opts.meta);
|
|
861
|
+
const skill = await loadSkillDoc(connectorDir);
|
|
862
|
+
if (!skill) {
|
|
749
863
|
throw new Error(
|
|
750
|
-
`SKILL.md not found at ${skillPath}. Ensure the connector ships a SKILL.md.`
|
|
864
|
+
`SKILL.md not found at ${skillPath(connectorDir)}. Ensure the connector ships a SKILL.md.`
|
|
751
865
|
);
|
|
752
866
|
}
|
|
753
|
-
opts.stdout.write(
|
|
867
|
+
opts.stdout.write(skill.content);
|
|
754
868
|
return;
|
|
755
869
|
}
|
|
756
870
|
if (first === "reference") {
|
|
@@ -759,18 +873,10 @@ async function runDispatchCliBody(connector, opts) {
|
|
|
759
873
|
"runDispatchCliBody: `reference` requires `meta` in options (the connector's `import.meta` from `cli.ts`)."
|
|
760
874
|
);
|
|
761
875
|
}
|
|
762
|
-
const
|
|
763
|
-
const connectorDir = path2.dirname(cliPath);
|
|
764
|
-
const refsDir = path2.join(connectorDir, "references");
|
|
876
|
+
const connectorDir = connectorDirFromMeta(opts.meta);
|
|
765
877
|
const name = args[1];
|
|
766
878
|
if (!name) {
|
|
767
|
-
|
|
768
|
-
try {
|
|
769
|
-
const files = await fs2.readdir(refsDir);
|
|
770
|
-
entries = files.filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -".md".length));
|
|
771
|
-
} catch {
|
|
772
|
-
entries = [];
|
|
773
|
-
}
|
|
879
|
+
const entries = await listReferenceNames(connectorDir);
|
|
774
880
|
if (entries.length === 0) {
|
|
775
881
|
opts.stdout.write(`Available references: <none>
|
|
776
882
|
`);
|
|
@@ -784,16 +890,13 @@ async function runDispatchCliBody(connector, opts) {
|
|
|
784
890
|
}
|
|
785
891
|
return;
|
|
786
892
|
}
|
|
787
|
-
const
|
|
788
|
-
|
|
789
|
-
try {
|
|
790
|
-
content = await fs2.readFile(refPath, "utf8");
|
|
791
|
-
} catch {
|
|
893
|
+
const reference = await loadReferenceDoc(connectorDir, name);
|
|
894
|
+
if (!reference) {
|
|
792
895
|
throw new Error(
|
|
793
|
-
`Reference file not found at ${
|
|
896
|
+
`Reference file not found at ${referencePath(connectorDir, name)}. Ensure the connector ships \`references/${name}.md\`.`
|
|
794
897
|
);
|
|
795
898
|
}
|
|
796
|
-
opts.stdout.write(
|
|
899
|
+
opts.stdout.write(reference.content);
|
|
797
900
|
return;
|
|
798
901
|
}
|
|
799
902
|
if (first !== "run") {
|
|
@@ -826,23 +929,12 @@ async function runDispatchCliBody(connector, opts) {
|
|
|
826
929
|
invocation
|
|
827
930
|
});
|
|
828
931
|
}
|
|
829
|
-
function resolveRelativeLinks(content, baseDir) {
|
|
830
|
-
return content.replace(
|
|
831
|
-
/(!?\[([^\]]*)\])\(([^)]+)\)/g,
|
|
832
|
-
(match, prefix, _alt, url) => {
|
|
833
|
-
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("file:") || url.startsWith("#") || url.startsWith("/")) {
|
|
834
|
-
return match;
|
|
835
|
-
}
|
|
836
|
-
return `${prefix}(${path2.resolve(baseDir, url)})`;
|
|
837
|
-
}
|
|
838
|
-
);
|
|
839
|
-
}
|
|
840
932
|
async function resolvePackageName(meta) {
|
|
841
933
|
if (!meta?.url) return void 0;
|
|
842
934
|
try {
|
|
843
|
-
const cliPath =
|
|
844
|
-
const pkgPath =
|
|
845
|
-
const raw = await
|
|
935
|
+
const cliPath = fileURLToPath3(meta.url);
|
|
936
|
+
const pkgPath = path3.join(path3.dirname(cliPath), "package.json");
|
|
937
|
+
const raw = await fs3.readFile(pkgPath, "utf8");
|
|
846
938
|
const pkg = JSON.parse(raw);
|
|
847
939
|
return typeof pkg.name === "string" ? pkg.name : void 0;
|
|
848
940
|
} catch {
|
|
@@ -852,8 +944,8 @@ async function resolvePackageName(meta) {
|
|
|
852
944
|
function printUsage(scripts, out, packageName) {
|
|
853
945
|
const names = Object.keys(scripts);
|
|
854
946
|
out.write(
|
|
855
|
-
`Usage: <bin> run <script> [<json-input>]
|
|
856
|
-
<bin> run <script> --help (per-script input
|
|
947
|
+
`Usage: <bin> run <script> [<json-input>] [--filter <jq>]
|
|
948
|
+
<bin> run <script> --help (per-script input/output schema, env vars, --filter)
|
|
857
949
|
<bin> mcp (run as a local MCP server over stdio)
|
|
858
950
|
<bin> skill (print SKILL.md to stdout)
|
|
859
951
|
<bin> reference [<name>] (print references/<name>.md, or list all)
|
|
@@ -882,7 +974,7 @@ function printUsage(scripts, out, packageName) {
|
|
|
882
974
|
}
|
|
883
975
|
out.write(
|
|
884
976
|
`
|
|
885
|
-
|
|
977
|
+
Run \`<bin> run <script> --help\` for a script's input/output schema, its required env vars (credentials are environment-variable only), and the \`--filter <jq>\` flag for trimming output.
|
|
886
978
|
`
|
|
887
979
|
);
|
|
888
980
|
}
|
|
@@ -908,6 +1000,13 @@ env-only \u2014 set the env vars for each script you intend to call, listed
|
|
|
908
1000
|
by \`<bin> run <script> --help\`. Resolution happens once per script at
|
|
909
1001
|
server start; credentials never traverse the MCP transport.
|
|
910
1002
|
|
|
1003
|
+
`
|
|
1004
|
+
);
|
|
1005
|
+
out.write(
|
|
1006
|
+
`The connector's docs are exposed as MCP resources (text/markdown): its
|
|
1007
|
+
SKILL.md and each references/*.md file, mirroring \`<bin> skill\` and
|
|
1008
|
+
\`<bin> reference\`. Clients read them via resources/list + resources/read.
|
|
1009
|
+
|
|
911
1010
|
`
|
|
912
1011
|
);
|
|
913
1012
|
const names = Object.keys(scripts);
|
|
@@ -951,12 +1050,31 @@ async function handleIfScriptMainBody(wrappedScript, connectionResolvers, io) {
|
|
|
951
1050
|
const args = io.argv.slice(2);
|
|
952
1051
|
let positional;
|
|
953
1052
|
let helpRequested = false;
|
|
954
|
-
|
|
1053
|
+
let filter;
|
|
1054
|
+
for (let i = 0; i < args.length; i++) {
|
|
1055
|
+
const arg = args[i];
|
|
955
1056
|
if (arg === "--help" || arg === "-h") {
|
|
956
1057
|
helpRequested = true;
|
|
1058
|
+
} else if (arg === "--filter") {
|
|
1059
|
+
const value = args[i + 1];
|
|
1060
|
+
if (value === void 0 || value.length === 0) {
|
|
1061
|
+
throw new Error(
|
|
1062
|
+
`\`--filter\` requires a jq expression, e.g. \`--filter '.results | length'\`.`
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
filter = value;
|
|
1066
|
+
i++;
|
|
1067
|
+
} else if (arg.startsWith("--filter=")) {
|
|
1068
|
+
const value = arg.slice("--filter=".length);
|
|
1069
|
+
if (value.length === 0) {
|
|
1070
|
+
throw new Error(
|
|
1071
|
+
`\`--filter\` requires a jq expression, e.g. \`--filter '.results | length'\`.`
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
filter = value;
|
|
957
1075
|
} else if (arg.startsWith("--")) {
|
|
958
1076
|
throw new Error(
|
|
959
|
-
`Unknown flag "${arg}". The per-script CLI accepts
|
|
1077
|
+
`Unknown flag "${arg}". The per-script CLI accepts \`--help\`, \`--filter <jq>\`, and a positional JSON-encoded input. Credentials are env-only \u2014 set the resolver's required env vars (run with \`--help\` to list them).`
|
|
960
1078
|
);
|
|
961
1079
|
} else if (positional === void 0) {
|
|
962
1080
|
positional = arg;
|
|
@@ -1001,7 +1119,12 @@ async function handleIfScriptMainBody(wrappedScript, connectionResolvers, io) {
|
|
|
1001
1119
|
}
|
|
1002
1120
|
}
|
|
1003
1121
|
const result = await wrappedScript.run(input, runOpts);
|
|
1004
|
-
|
|
1122
|
+
const output = filter === void 0 ? result : await applyJqFilter(result, filter);
|
|
1123
|
+
io.stdout.write(JSON.stringify(output, null, 2) + "\n");
|
|
1124
|
+
}
|
|
1125
|
+
async function applyJqFilter(value, filter) {
|
|
1126
|
+
const { json: runJq } = await import("jq-wasm");
|
|
1127
|
+
return runJq(value, filter);
|
|
1005
1128
|
}
|
|
1006
1129
|
async function readStdinInput(stdin, scriptName) {
|
|
1007
1130
|
if (stdin.isTTY) {
|
|
@@ -1020,11 +1143,19 @@ function buildHelpText(definition, connectionResolvers, opts = {}) {
|
|
|
1020
1143
|
lines.push("");
|
|
1021
1144
|
lines.push("Usage:");
|
|
1022
1145
|
if (opts.invocation) {
|
|
1023
|
-
lines.push(
|
|
1146
|
+
lines.push(
|
|
1147
|
+
` <bin> run ${opts.invocation.scriptName} '<json-input>' [--filter <jq>]`
|
|
1148
|
+
);
|
|
1024
1149
|
} else {
|
|
1025
|
-
lines.push(` <bin> '<json-input>'`);
|
|
1150
|
+
lines.push(` <bin> '<json-input>' [--filter <jq>]`);
|
|
1026
1151
|
}
|
|
1027
1152
|
lines.push("");
|
|
1153
|
+
lines.push(
|
|
1154
|
+
" --filter <jq> Transform the JSON output through a jq expression."
|
|
1155
|
+
);
|
|
1156
|
+
lines.push(" Useful for trimming large outputs, e.g.");
|
|
1157
|
+
lines.push(" --filter '.results | length'.");
|
|
1158
|
+
lines.push("");
|
|
1028
1159
|
if (opts.invocation) {
|
|
1029
1160
|
lines.push("Example:");
|
|
1030
1161
|
lines.push(
|
|
@@ -1049,6 +1180,11 @@ function buildHelpText(definition, connectionResolvers, opts = {}) {
|
|
|
1049
1180
|
lines.push(...inputBlock);
|
|
1050
1181
|
lines.push("");
|
|
1051
1182
|
}
|
|
1183
|
+
const outputBlock = formatHelpForOutput(definition);
|
|
1184
|
+
if (outputBlock.length > 0) {
|
|
1185
|
+
lines.push(...outputBlock);
|
|
1186
|
+
lines.push("");
|
|
1187
|
+
}
|
|
1052
1188
|
return lines.join("\n");
|
|
1053
1189
|
}
|
|
1054
1190
|
function formatExampleCommand(definition, connectionResolvers, invocation) {
|
|
@@ -1085,6 +1221,20 @@ function formatHelpForInput(definition) {
|
|
|
1085
1221
|
}
|
|
1086
1222
|
return lines;
|
|
1087
1223
|
}
|
|
1224
|
+
function formatHelpForOutput(definition) {
|
|
1225
|
+
let schema;
|
|
1226
|
+
try {
|
|
1227
|
+
schema = z3.toJSONSchema(definition.outputSchema);
|
|
1228
|
+
} catch {
|
|
1229
|
+
return [];
|
|
1230
|
+
}
|
|
1231
|
+
const lines = ["Output (JSON Schema):"];
|
|
1232
|
+
const json = JSON.stringify(schema, null, 2);
|
|
1233
|
+
for (const line of json.split("\n")) {
|
|
1234
|
+
lines.push(` ${line}`);
|
|
1235
|
+
}
|
|
1236
|
+
return lines;
|
|
1237
|
+
}
|
|
1088
1238
|
|
|
1089
1239
|
// src/surfaces/to-functions.ts
|
|
1090
1240
|
function toFunctions(connector) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zapier/connectors-sdk",
|
|
3
|
-
"version": "0.1.0-experimental.
|
|
3
|
+
"version": "0.1.0-experimental.13",
|
|
4
4
|
"description": "SDK for building Zapier connectors. Provides the authoring primitives and execution surfaces for connector scripts.",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,9 @@
|
|
|
24
24
|
"README.md",
|
|
25
25
|
"LICENSE"
|
|
26
26
|
],
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"jq-wasm": "1.1.0-jq-1.8.1"
|
|
29
|
+
},
|
|
27
30
|
"peerDependencies": {
|
|
28
31
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
29
32
|
"@zapier/zapier-sdk": "^0.59.0",
|