@zapier/connectors-sdk 0.1.0-experimental.11 → 0.1.0-experimental.12
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 +148 -49
- package/dist/index.js +148 -49
- package/package.json +1 -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 {
|
|
@@ -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);
|
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 {
|
|
@@ -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);
|
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.12",
|
|
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",
|