@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.
@@ -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 registering every script as a native MCP tool (`tools/list` + `tools/call`) |
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. Connects via `StdioServerTransport` and writes a one-line ready banner to stderr.
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 fs2 = __toESM(require("fs/promises"), 1);
710
- var path2 = __toESM(require("path"), 1);
711
- var import_node_url2 = require("url");
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/serve-mcp-stdio.ts
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, import_node_url.fileURLToPath)(meta.url);
738
- const pkgPath = path.join(path.dirname(cliPath), "package.json");
739
- const raw = await fs.readFile(pkgPath, "utf8");
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 serveMcpStdio(meta, connector, opts = {}) {
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 cliPath = (0, import_node_url2.fileURLToPath)(opts.meta.url);
849
- const connectorDir = path2.dirname(cliPath);
850
- const skillPath = path2.join(connectorDir, "SKILL.md");
851
- let content;
852
- try {
853
- content = await fs2.readFile(skillPath, "utf8");
854
- } catch {
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(resolveRelativeLinks(content, connectorDir));
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 cliPath = (0, import_node_url2.fileURLToPath)(opts.meta.url);
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
- let entries;
874
- try {
875
- const files = await fs2.readdir(refsDir);
876
- entries = files.filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -".md".length));
877
- } catch {
878
- entries = [];
879
- }
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 refPath = path2.join(refsDir, `${name}.md`);
894
- let content;
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 ${refPath}. Ensure the connector ships \`references/${name}.md\`.`
1002
+ `Reference file not found at ${referencePath(connectorDir, name)}. Ensure the connector ships \`references/${name}.md\`.`
900
1003
  );
901
1004
  }
902
- opts.stdout.write(resolveRelativeLinks(content, refsDir));
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, import_node_url2.fileURLToPath)(meta.url);
950
- const pkgPath = path2.join(path2.dirname(cliPath), "package.json");
951
- const raw = await fs2.readFile(pkgPath, "utf8");
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 fs2 from "fs/promises";
604
- import * as path2 from "path";
605
- import { fileURLToPath as fileURLToPath2 } from "url";
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/serve-mcp-stdio.ts
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 = fileURLToPath(meta.url);
632
- const pkgPath = path.join(path.dirname(cliPath), "package.json");
633
- const raw = await fs.readFile(pkgPath, "utf8");
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 serveMcpStdio(meta, connector, opts = {}) {
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 cliPath = fileURLToPath2(opts.meta.url);
743
- const connectorDir = path2.dirname(cliPath);
744
- const skillPath = path2.join(connectorDir, "SKILL.md");
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(resolveRelativeLinks(content, connectorDir));
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 cliPath = fileURLToPath2(opts.meta.url);
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
- let entries;
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 refPath = path2.join(refsDir, `${name}.md`);
788
- let content;
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 ${refPath}. Ensure the connector ships \`references/${name}.md\`.`
896
+ `Reference file not found at ${referencePath(connectorDir, name)}. Ensure the connector ships \`references/${name}.md\`.`
794
897
  );
795
898
  }
796
- opts.stdout.write(resolveRelativeLinks(content, refsDir));
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 = fileURLToPath2(meta.url);
844
- const pkgPath = path2.join(path2.dirname(cliPath), "package.json");
845
- const raw = await fs2.readFile(pkgPath, "utf8");
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.11",
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",