@sammorrowdrums/mcpi-ext 0.1.0

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.
Files changed (213) hide show
  1. package/.dockerignore +6 -0
  2. package/.github/dependabot.yml +25 -0
  3. package/.github/workflows/ci.yml +50 -0
  4. package/.hadolint.yaml +6 -0
  5. package/.prettierignore +4 -0
  6. package/.prettierrc +7 -0
  7. package/AGENTS.md +118 -0
  8. package/DECISIONS.md +99 -0
  9. package/Dockerfile +39 -0
  10. package/README.md +278 -0
  11. package/aube-lock.yaml +4146 -0
  12. package/dist/code-mode/code-mode-integration.test.d.ts +2 -0
  13. package/dist/code-mode/code-mode-integration.test.d.ts.map +1 -0
  14. package/dist/code-mode/code-mode-integration.test.js +116 -0
  15. package/dist/code-mode/code-mode-integration.test.js.map +1 -0
  16. package/dist/code-mode/eligibility.d.ts +10 -0
  17. package/dist/code-mode/eligibility.d.ts.map +1 -0
  18. package/dist/code-mode/eligibility.js +14 -0
  19. package/dist/code-mode/eligibility.js.map +1 -0
  20. package/dist/code-mode/eligibility.test.d.ts +2 -0
  21. package/dist/code-mode/eligibility.test.d.ts.map +1 -0
  22. package/dist/code-mode/eligibility.test.js +53 -0
  23. package/dist/code-mode/eligibility.test.js.map +1 -0
  24. package/dist/code-mode/executor.d.ts +35 -0
  25. package/dist/code-mode/executor.d.ts.map +1 -0
  26. package/dist/code-mode/executor.js +120 -0
  27. package/dist/code-mode/executor.js.map +1 -0
  28. package/dist/code-mode/executor.test.d.ts +2 -0
  29. package/dist/code-mode/executor.test.d.ts.map +1 -0
  30. package/dist/code-mode/executor.test.js +130 -0
  31. package/dist/code-mode/executor.test.js.map +1 -0
  32. package/dist/code-mode/index.d.ts +65 -0
  33. package/dist/code-mode/index.d.ts.map +1 -0
  34. package/dist/code-mode/index.js +175 -0
  35. package/dist/code-mode/index.js.map +1 -0
  36. package/dist/code-mode/tools.d.ts +45 -0
  37. package/dist/code-mode/tools.d.ts.map +1 -0
  38. package/dist/code-mode/tools.js +70 -0
  39. package/dist/code-mode/tools.js.map +1 -0
  40. package/dist/code-mode/type-hints.d.ts +20 -0
  41. package/dist/code-mode/type-hints.d.ts.map +1 -0
  42. package/dist/code-mode/type-hints.js +189 -0
  43. package/dist/code-mode/type-hints.js.map +1 -0
  44. package/dist/code-mode/type-hints.test.d.ts +2 -0
  45. package/dist/code-mode/type-hints.test.d.ts.map +1 -0
  46. package/dist/code-mode/type-hints.test.js +157 -0
  47. package/dist/code-mode/type-hints.test.js.map +1 -0
  48. package/dist/docker-e2e.d.ts +55 -0
  49. package/dist/docker-e2e.d.ts.map +1 -0
  50. package/dist/docker-e2e.js +235 -0
  51. package/dist/docker-e2e.js.map +1 -0
  52. package/dist/docker-e2e.test.d.ts +2 -0
  53. package/dist/docker-e2e.test.d.ts.map +1 -0
  54. package/dist/docker-e2e.test.js +176 -0
  55. package/dist/docker-e2e.test.js.map +1 -0
  56. package/dist/index.d.ts +3 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +128 -0
  59. package/dist/index.js.map +1 -0
  60. package/dist/index.test.d.ts +2 -0
  61. package/dist/index.test.d.ts.map +1 -0
  62. package/dist/index.test.js +8 -0
  63. package/dist/index.test.js.map +1 -0
  64. package/dist/mcp/client-manager.d.ts +46 -0
  65. package/dist/mcp/client-manager.d.ts.map +1 -0
  66. package/dist/mcp/client-manager.js +148 -0
  67. package/dist/mcp/client-manager.js.map +1 -0
  68. package/dist/mcp/client-manager.test.d.ts +2 -0
  69. package/dist/mcp/client-manager.test.d.ts.map +1 -0
  70. package/dist/mcp/client-manager.test.js +204 -0
  71. package/dist/mcp/client-manager.test.js.map +1 -0
  72. package/dist/mcp/config-loader.d.ts +8 -0
  73. package/dist/mcp/config-loader.d.ts.map +1 -0
  74. package/dist/mcp/config-loader.js +43 -0
  75. package/dist/mcp/config-loader.js.map +1 -0
  76. package/dist/mcp/config-loader.test.d.ts +2 -0
  77. package/dist/mcp/config-loader.test.d.ts.map +1 -0
  78. package/dist/mcp/config-loader.test.js +93 -0
  79. package/dist/mcp/config-loader.test.js.map +1 -0
  80. package/dist/mcp/config.d.ts +46 -0
  81. package/dist/mcp/config.d.ts.map +1 -0
  82. package/dist/mcp/config.js +28 -0
  83. package/dist/mcp/config.js.map +1 -0
  84. package/dist/mcp/index.d.ts +5 -0
  85. package/dist/mcp/index.d.ts.map +1 -0
  86. package/dist/mcp/index.js +4 -0
  87. package/dist/mcp/index.js.map +1 -0
  88. package/dist/skills/discover.d.ts +10 -0
  89. package/dist/skills/discover.d.ts.map +1 -0
  90. package/dist/skills/discover.js +60 -0
  91. package/dist/skills/discover.js.map +1 -0
  92. package/dist/skills/format.d.ts +10 -0
  93. package/dist/skills/format.d.ts.map +1 -0
  94. package/dist/skills/format.js +38 -0
  95. package/dist/skills/format.js.map +1 -0
  96. package/dist/skills/format.test.d.ts +2 -0
  97. package/dist/skills/format.test.d.ts.map +1 -0
  98. package/dist/skills/format.test.js +63 -0
  99. package/dist/skills/format.test.js.map +1 -0
  100. package/dist/skills/index.d.ts +6 -0
  101. package/dist/skills/index.d.ts.map +1 -0
  102. package/dist/skills/index.js +6 -0
  103. package/dist/skills/index.js.map +1 -0
  104. package/dist/skills/load-skill-tool.d.ts +40 -0
  105. package/dist/skills/load-skill-tool.d.ts.map +1 -0
  106. package/dist/skills/load-skill-tool.js +104 -0
  107. package/dist/skills/load-skill-tool.js.map +1 -0
  108. package/dist/skills/mcp-tool-proxy.d.ts +11 -0
  109. package/dist/skills/mcp-tool-proxy.d.ts.map +1 -0
  110. package/dist/skills/mcp-tool-proxy.js +116 -0
  111. package/dist/skills/mcp-tool-proxy.js.map +1 -0
  112. package/dist/skills/skill-integration.test.d.ts +2 -0
  113. package/dist/skills/skill-integration.test.d.ts.map +1 -0
  114. package/dist/skills/skill-integration.test.js +119 -0
  115. package/dist/skills/skill-integration.test.js.map +1 -0
  116. package/dist/skills/skill-registry.d.ts +39 -0
  117. package/dist/skills/skill-registry.d.ts.map +1 -0
  118. package/dist/skills/skill-registry.js +48 -0
  119. package/dist/skills/skill-registry.js.map +1 -0
  120. package/dist/skills/skill-registry.test.d.ts +2 -0
  121. package/dist/skills/skill-registry.test.d.ts.map +1 -0
  122. package/dist/skills/skill-registry.test.js +67 -0
  123. package/dist/skills/skill-registry.test.js.map +1 -0
  124. package/dist/test-servers/weather-server.d.ts +12 -0
  125. package/dist/test-servers/weather-server.d.ts.map +1 -0
  126. package/dist/test-servers/weather-server.js +139 -0
  127. package/dist/test-servers/weather-server.js.map +1 -0
  128. package/dist/test-servers/weather-stdio.d.ts +2 -0
  129. package/dist/test-servers/weather-stdio.d.ts.map +1 -0
  130. package/dist/test-servers/weather-stdio.js +11 -0
  131. package/dist/test-servers/weather-stdio.js.map +1 -0
  132. package/dist/tool-cli/cli.d.ts +3 -0
  133. package/dist/tool-cli/cli.d.ts.map +1 -0
  134. package/dist/tool-cli/cli.js +179 -0
  135. package/dist/tool-cli/cli.js.map +1 -0
  136. package/dist/tool-cli/constants.d.ts +7 -0
  137. package/dist/tool-cli/constants.d.ts.map +1 -0
  138. package/dist/tool-cli/constants.js +15 -0
  139. package/dist/tool-cli/constants.js.map +1 -0
  140. package/dist/tool-cli/format.d.ts +8 -0
  141. package/dist/tool-cli/format.d.ts.map +1 -0
  142. package/dist/tool-cli/format.js +57 -0
  143. package/dist/tool-cli/format.js.map +1 -0
  144. package/dist/tool-cli/format.test.d.ts +2 -0
  145. package/dist/tool-cli/format.test.d.ts.map +1 -0
  146. package/dist/tool-cli/format.test.js +30 -0
  147. package/dist/tool-cli/format.test.js.map +1 -0
  148. package/dist/tool-cli/index.d.ts +4 -0
  149. package/dist/tool-cli/index.d.ts.map +1 -0
  150. package/dist/tool-cli/index.js +4 -0
  151. package/dist/tool-cli/index.js.map +1 -0
  152. package/dist/tool-cli/rpc-client.d.ts +6 -0
  153. package/dist/tool-cli/rpc-client.d.ts.map +1 -0
  154. package/dist/tool-cli/rpc-client.js +25 -0
  155. package/dist/tool-cli/rpc-client.js.map +1 -0
  156. package/dist/tool-cli/rpc-server.d.ts +48 -0
  157. package/dist/tool-cli/rpc-server.d.ts.map +1 -0
  158. package/dist/tool-cli/rpc-server.js +220 -0
  159. package/dist/tool-cli/rpc-server.js.map +1 -0
  160. package/dist/tool-cli/rpc-server.test.d.ts +2 -0
  161. package/dist/tool-cli/rpc-server.test.d.ts.map +1 -0
  162. package/dist/tool-cli/rpc-server.test.js +154 -0
  163. package/dist/tool-cli/rpc-server.test.js.map +1 -0
  164. package/eslint.config.js +27 -0
  165. package/images/banner.webp +0 -0
  166. package/images/code-c-maude.webp +0 -0
  167. package/images/nuclear-mcp-football.webp +0 -0
  168. package/images/the-skill-dealer.webp +0 -0
  169. package/mise.toml +65 -0
  170. package/package.json +46 -0
  171. package/scripts/docker-entrypoint.sh +29 -0
  172. package/scripts/test-echo-server.mjs +28 -0
  173. package/scripts/test-mcp-integration.mjs +59 -0
  174. package/scripts/test-tool-cli-smoke.mjs +62 -0
  175. package/src/code-mode/code-mode-integration.test.ts +135 -0
  176. package/src/code-mode/eligibility.test.ts +60 -0
  177. package/src/code-mode/eligibility.ts +16 -0
  178. package/src/code-mode/executor.test.ts +168 -0
  179. package/src/code-mode/executor.ts +163 -0
  180. package/src/code-mode/index.ts +208 -0
  181. package/src/code-mode/tools.ts +110 -0
  182. package/src/code-mode/type-hints.test.ts +187 -0
  183. package/src/code-mode/type-hints.ts +249 -0
  184. package/src/docker-e2e.test.ts +195 -0
  185. package/src/docker-e2e.ts +277 -0
  186. package/src/index.test.ts +8 -0
  187. package/src/index.ts +158 -0
  188. package/src/mcp/client-manager.test.ts +250 -0
  189. package/src/mcp/client-manager.ts +199 -0
  190. package/src/mcp/config-loader.test.ts +115 -0
  191. package/src/mcp/config-loader.ts +46 -0
  192. package/src/mcp/config.ts +40 -0
  193. package/src/mcp/index.ts +4 -0
  194. package/src/skills/discover.ts +81 -0
  195. package/src/skills/format.test.ts +70 -0
  196. package/src/skills/format.ts +42 -0
  197. package/src/skills/index.ts +9 -0
  198. package/src/skills/load-skill-tool.ts +136 -0
  199. package/src/skills/mcp-tool-proxy.ts +143 -0
  200. package/src/skills/skill-integration.test.ts +139 -0
  201. package/src/skills/skill-registry.test.ts +83 -0
  202. package/src/skills/skill-registry.ts +69 -0
  203. package/src/test-servers/weather-server.ts +171 -0
  204. package/src/test-servers/weather-stdio.ts +11 -0
  205. package/src/tool-cli/cli.ts +232 -0
  206. package/src/tool-cli/constants.ts +15 -0
  207. package/src/tool-cli/format.test.ts +33 -0
  208. package/src/tool-cli/format.ts +56 -0
  209. package/src/tool-cli/index.ts +3 -0
  210. package/src/tool-cli/rpc-client.ts +39 -0
  211. package/src/tool-cli/rpc-server.test.ts +193 -0
  212. package/src/tool-cli/rpc-server.ts +287 -0
  213. package/tsconfig.json +21 -0
@@ -0,0 +1,104 @@
1
+ import { stripFrontmatter } from "@sammorrowdrums/mcpi";
2
+ import { Type } from "typebox";
3
+ const LoadSkillParams = Type.Object({
4
+ name: Type.String({ description: "Name of the MCP skill to load" }),
5
+ });
6
+ /**
7
+ * Create the load_skill tool definition.
8
+ *
9
+ * When the model calls this tool, it:
10
+ * 1. Looks up the skill in the registry
11
+ * 2. Reads the full SKILL.md content from the MCP server
12
+ * 3. Returns the SKILL.md body (the skill names its tools, and the model
13
+ * already has their schemas from the deferred tools array)
14
+ */
15
+ export function createLoadSkillTool(deps) {
16
+ const { registry, mcpManager, enabledTools } = deps;
17
+ return {
18
+ name: "load_skill",
19
+ label: "Load Skill",
20
+ description: "Load an MCP skill by name. Activates the skill's instructions and tools.",
21
+ promptSnippet: "Load an MCP skill to get specialized instructions and activate its tools.",
22
+ parameters: LoadSkillParams,
23
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
24
+ const skill = registry.get(params.name);
25
+ if (!skill) {
26
+ const available = registry
27
+ .getAll()
28
+ .map((s) => s.name)
29
+ .join(", ");
30
+ return {
31
+ content: [
32
+ {
33
+ type: "text",
34
+ text: `Skill "${params.name}" not found. Available skills: ${available || "(none)"}`,
35
+ },
36
+ ],
37
+ details: { skillName: params.name, error: "not_found" },
38
+ };
39
+ }
40
+ const client = mcpManager.getClient(skill.serverName);
41
+ if (!client) {
42
+ return {
43
+ content: [
44
+ {
45
+ type: "text",
46
+ text: `MCP server "${skill.serverName}" is not connected. Cannot load skill "${params.name}".`,
47
+ },
48
+ ],
49
+ details: { skillName: params.name, serverName: skill.serverName, error: "disconnected" },
50
+ };
51
+ }
52
+ let body;
53
+ try {
54
+ const result = await client.readResource({ uri: skill.uri });
55
+ const textContent = result.contents.find((c) => "text" in c);
56
+ if (!textContent) {
57
+ return {
58
+ content: [
59
+ {
60
+ type: "text",
61
+ text: `Skill "${params.name}" returned no text content.`,
62
+ },
63
+ ],
64
+ details: { skillName: params.name, serverName: skill.serverName, error: "no_content" },
65
+ };
66
+ }
67
+ body = stripFrontmatter(textContent.text);
68
+ }
69
+ catch (err) {
70
+ return {
71
+ content: [
72
+ {
73
+ type: "text",
74
+ text: `Failed to read skill "${params.name}" from server "${skill.serverName}": ${err.message}`,
75
+ },
76
+ ],
77
+ details: {
78
+ skillName: params.name,
79
+ serverName: skill.serverName,
80
+ error: err.message,
81
+ },
82
+ };
83
+ }
84
+ // Enable the skill's tools so the tool_call gate allows them
85
+ for (const t of skill.allowedTools) {
86
+ enabledTools.add(t);
87
+ }
88
+ return {
89
+ content: [
90
+ {
91
+ type: "text",
92
+ text: body,
93
+ },
94
+ ],
95
+ details: {
96
+ skillName: params.name,
97
+ serverName: skill.serverName,
98
+ activatedTools: skill.allowedTools,
99
+ },
100
+ };
101
+ },
102
+ };
103
+ }
104
+ //# sourceMappingURL=load-skill-tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"load-skill-tool.js","sourceRoot":"","sources":["../../src/skills/load-skill-tool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,IAAI,EAAe,MAAM,SAAS,CAAC;AAI5C,MAAM,eAAe,GAAG,IAAI,CAAC,MAAM,CAAC;IAClC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,EAAE,+BAA+B,EAAE,CAAC;CACpE,CAAC,CAAC;AAiBH;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAmB;IACrD,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAEpD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,0EAA0E;QACvF,aAAa,EAAE,2EAA2E;QAC1F,UAAU,EAAE,eAAe;QAE3B,KAAK,CAAC,OAAO,CACX,WAAmB,EACnB,MAAsB,EACtB,OAAgC,EAChC,SAAoB,EACpB,IAAsB;YAEtB,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YACxC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,SAAS,GAAG,QAAQ;qBACvB,MAAM,EAAE;qBACR,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;qBAClB,IAAI,CAAC,IAAI,CAAC,CAAC;gBACd,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,UAAU,MAAM,CAAC,IAAI,kCAAkC,SAAS,IAAI,QAAQ,EAAE;yBACrF;qBACF;oBACD,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE;iBACxD,CAAC;YACJ,CAAC;YAED,MAAM,MAAM,GAAG,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YACtD,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,eAAe,KAAK,CAAC,UAAU,0CAA0C,MAAM,CAAC,IAAI,IAAI;yBAC/F;qBACF;oBACD,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE;iBACzF,CAAC;YACJ,CAAC;YAED,IAAI,IAAY,CAAC;YACjB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,YAAY,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;gBAC7D,MAAM,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,CACtC,CAAC,CAAC,EAAsC,EAAE,CAAC,MAAM,IAAI,CAAC,CACvD,CAAC;gBACF,IAAI,CAAC,WAAW,EAAE,CAAC;oBACjB,OAAO;wBACL,OAAO,EAAE;4BACP;gCACE,IAAI,EAAE,MAAM;gCACZ,IAAI,EAAE,UAAU,MAAM,CAAC,IAAI,6BAA6B;6BACzD;yBACF;wBACD,OAAO,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,YAAY,EAAE;qBACvF,CAAC;gBACJ,CAAC;gBACD,IAAI,GAAG,gBAAgB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YAC5C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO;oBACL,OAAO,EAAE;wBACP;4BACE,IAAI,EAAE,MAAM;4BACZ,IAAI,EAAE,yBAAyB,MAAM,CAAC,IAAI,kBAAkB,KAAK,CAAC,UAAU,MAAO,GAAa,CAAC,OAAO,EAAE;yBAC3G;qBACF;oBACD,OAAO,EAAE;wBACP,SAAS,EAAE,MAAM,CAAC,IAAI;wBACtB,UAAU,EAAE,KAAK,CAAC,UAAU;wBAC5B,KAAK,EAAG,GAAa,CAAC,OAAO;qBAC9B;iBACF,CAAC;YACJ,CAAC;YAED,6DAA6D;YAC7D,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;gBACnC,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YACtB,CAAC;YAED,OAAO;gBACL,OAAO,EAAE;oBACP;wBACE,IAAI,EAAE,MAAM;wBACZ,IAAI,EAAE,IAAI;qBACX;iBACF;gBACD,OAAO,EAAE;oBACP,SAAS,EAAE,MAAM,CAAC,IAAI;oBACtB,UAAU,EAAE,KAAK,CAAC,UAAU;oBAC5B,cAAc,EAAE,KAAK,CAAC,YAAY;iBACnC;aACF,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { ExtensionAPI } from "@sammorrowdrums/mcpi";
2
+ import type { McpClientManager } from "../mcp/index.js";
3
+ /**
4
+ * Register MCP tools as Pi tool proxies.
5
+ *
6
+ * Each registered tool forwards calls to the MCP server via `client.callTool()`.
7
+ * Uses `Type.Unsafe()` to pass the MCP tool's original JSON Schema through
8
+ * to Pi, preserving property names and types for the model.
9
+ */
10
+ export declare function registerMcpToolProxies(toolNames: string[], mcpManager: McpClientManager, pi: ExtensionAPI): string[];
11
+ //# sourceMappingURL=mcp-tool-proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-tool-proxy.d.ts","sourceRoot":"","sources":["../../src/skills/mcp-tool-proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAmB,YAAY,EAAoB,MAAM,sBAAsB,CAAC;AAK5F,OAAO,KAAK,EAAE,gBAAgB,EAAW,MAAM,iBAAiB,CAAC;AAWjE;;;;;;GAMG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,EAAE,EACnB,UAAU,EAAE,gBAAgB,EAC5B,EAAE,EAAE,YAAY,GACf,MAAM,EAAE,CAqBV"}
@@ -0,0 +1,116 @@
1
+ import { writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { Type } from "typebox";
5
+ /** Threshold in chars above which tool output is written to a tmp file. */
6
+ const LARGE_OUTPUT_THRESHOLD = 10_000;
7
+ /**
8
+ * Register MCP tools as Pi tool proxies.
9
+ *
10
+ * Each registered tool forwards calls to the MCP server via `client.callTool()`.
11
+ * Uses `Type.Unsafe()` to pass the MCP tool's original JSON Schema through
12
+ * to Pi, preserving property names and types for the model.
13
+ */
14
+ export function registerMcpToolProxies(toolNames, mcpManager, pi) {
15
+ const registered = [];
16
+ const allTools = mcpManager.getTools();
17
+ // Check which tools are already registered to avoid double-registration
18
+ const existingTools = new Set(pi.getAllTools().map((t) => t.name));
19
+ for (const name of toolNames) {
20
+ if (existingTools.has(name)) {
21
+ registered.push(name);
22
+ continue;
23
+ }
24
+ const mcpTool = allTools.find((t) => t.name === name);
25
+ if (!mcpTool)
26
+ continue;
27
+ pi.registerTool(createMcpToolProxy(mcpTool, mcpManager));
28
+ registered.push(name);
29
+ }
30
+ return registered;
31
+ }
32
+ function createMcpToolProxy(mcpTool, mcpManager) {
33
+ // Pass through the MCP tool's JSON Schema directly via Type.Unsafe()
34
+ // This preserves the original property names and types for the model
35
+ const inputSchema = mcpTool.inputSchema;
36
+ const parameters = Type.Unsafe({
37
+ type: "object",
38
+ properties: inputSchema.properties ?? {},
39
+ required: inputSchema.required ?? [],
40
+ });
41
+ return {
42
+ name: mcpTool.name,
43
+ label: mcpTool.name,
44
+ description: mcpTool.description ?? `MCP tool from ${mcpTool.serverName}`,
45
+ deferred: true,
46
+ parameters,
47
+ async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
48
+ const client = mcpManager.getClient(mcpTool.serverName);
49
+ if (!client) {
50
+ return {
51
+ content: [
52
+ {
53
+ type: "text",
54
+ text: `MCP server "${mcpTool.serverName}" is not connected.`,
55
+ },
56
+ ],
57
+ details: {
58
+ serverName: mcpTool.serverName,
59
+ toolName: mcpTool.name,
60
+ error: "disconnected",
61
+ },
62
+ };
63
+ }
64
+ try {
65
+ const result = await client.callTool({
66
+ name: mcpTool.name,
67
+ arguments: params,
68
+ });
69
+ // Prefer structuredContent when available
70
+ let text;
71
+ if (result.structuredContent) {
72
+ text = JSON.stringify(result.structuredContent, null, 2);
73
+ }
74
+ else if (Array.isArray(result.content)) {
75
+ text = result.content
76
+ .map((c) => {
77
+ if (typeof c === "object" && c !== null && "text" in c) {
78
+ return String(c.text);
79
+ }
80
+ return JSON.stringify(c);
81
+ })
82
+ .join("\n");
83
+ }
84
+ else {
85
+ text = JSON.stringify(result);
86
+ }
87
+ // Write large outputs to tmp file to avoid bloating context
88
+ if (text.length > LARGE_OUTPUT_THRESHOLD) {
89
+ const tmpPath = join(tmpdir(), `mcp-${mcpTool.name}-${Date.now()}.json`);
90
+ writeFileSync(tmpPath, text, "utf-8");
91
+ text = `Output too large (${text.length} chars). Written to: ${tmpPath}`;
92
+ }
93
+ return {
94
+ content: [{ type: "text", text }],
95
+ details: { serverName: mcpTool.serverName, toolName: mcpTool.name },
96
+ };
97
+ }
98
+ catch (err) {
99
+ return {
100
+ content: [
101
+ {
102
+ type: "text",
103
+ text: `MCP tool "${mcpTool.name}" failed: ${err.message}`,
104
+ },
105
+ ],
106
+ details: {
107
+ serverName: mcpTool.serverName,
108
+ toolName: mcpTool.name,
109
+ error: err.message,
110
+ },
111
+ };
112
+ }
113
+ },
114
+ };
115
+ }
116
+ //# sourceMappingURL=mcp-tool-proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-tool-proxy.js","sourceRoot":"","sources":["../../src/skills/mcp-tool-proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=skill-integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-integration.test.d.ts","sourceRoot":"","sources":["../../src/skills/skill-integration.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,119 @@
1
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
2
+ import { McpClientManager } from "../mcp/client-manager.js";
3
+ import { SkillRegistry } from "./skill-registry.js";
4
+ import { discoverSkillsFromServer } from "./discover.js";
5
+ import { formatMcpSkillsForPrompt } from "./format.js";
6
+ import { createLoadSkillTool } from "./load-skill-tool.js";
7
+ /**
8
+ * Integration test: connect to the test weather server via stdio,
9
+ * discover skills, load a skill, verify tool schemas in result, call a gated tool.
10
+ */
11
+ describe("skill integration (weather server)", () => {
12
+ const manager = new McpClientManager();
13
+ const registry = new SkillRegistry();
14
+ const enabledTools = new Set();
15
+ let client;
16
+ beforeAll(async () => {
17
+ await manager.connectAll({
18
+ mcpServers: {
19
+ "test-weather": {
20
+ type: "stdio",
21
+ command: "node",
22
+ args: ["dist/test-servers/weather-stdio.js"],
23
+ },
24
+ },
25
+ });
26
+ const c = manager.getClient("test-weather");
27
+ if (!c)
28
+ throw new Error("Expected client for test-weather");
29
+ client = c;
30
+ });
31
+ afterAll(async () => {
32
+ await manager.disconnectAll();
33
+ });
34
+ it("connects and discovers tools", () => {
35
+ expect(manager.getConnectedServers()).toEqual(["test-weather"]);
36
+ const tools = manager.getTools();
37
+ expect(tools.map((t) => t.name).sort()).toEqual([
38
+ "check_weather_for_city",
39
+ "check_weekly_forecast_for_city",
40
+ "echo",
41
+ ]);
42
+ });
43
+ it("discovers skill:// resources with frontmatter", async () => {
44
+ const skills = await discoverSkillsFromServer(client, "test-weather");
45
+ expect(skills).toHaveLength(1);
46
+ expect(skills[0].name).toBe("weather");
47
+ expect(skills[0].description).toBe("Check current weather and weekly forecasts for any city");
48
+ expect(skills[0].allowedTools).toEqual([
49
+ "check_weather_for_city",
50
+ "check_weekly_forecast_for_city",
51
+ ]);
52
+ expect(skills[0].uri).toBe("skill://weather/SKILL.md");
53
+ expect(skills[0].serverName).toBe("test-weather");
54
+ registry.registerAll(skills);
55
+ });
56
+ it("formats skills for system prompt", () => {
57
+ const prompt = formatMcpSkillsForPrompt(registry.getAll());
58
+ expect(prompt).toContain("<available_mcp_skills>");
59
+ expect(prompt).toContain("<name>weather</name>");
60
+ expect(prompt).toContain("load_skill");
61
+ });
62
+ it("load_skill returns skill body and reports activated tools", async () => {
63
+ const tool = createLoadSkillTool({
64
+ registry,
65
+ mcpManager: manager,
66
+ enabledTools,
67
+ });
68
+ const result = await tool.execute("call-1", { name: "weather" }, undefined, undefined,
69
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
70
+ {});
71
+ expect(result.details.error).toBeUndefined();
72
+ expect(result.details.activatedTools).toEqual([
73
+ "check_weather_for_city",
74
+ "check_weekly_forecast_for_city",
75
+ ]);
76
+ const text = result.content[0];
77
+ expect(text.type).toBe("text");
78
+ // Skill body should mention the tools
79
+ expect("text" in text && text.text).toContain("check_weather_for_city");
80
+ // Tools should now be in enabledTools set
81
+ expect(enabledTools.has("check_weather_for_city")).toBe(true);
82
+ expect(enabledTools.has("check_weekly_forecast_for_city")).toBe(true);
83
+ });
84
+ it("calls gated tools via MCP client", async () => {
85
+ const weather = await client.callTool({
86
+ name: "check_weather_for_city",
87
+ arguments: { city: "Tokyo" },
88
+ });
89
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
+ expect(weather.content[0].text).toContain("26");
91
+ expect(weather.structuredContent).toEqual({
92
+ temperature: 26,
93
+ conditions: "Sunny",
94
+ humidity: 55,
95
+ city: "Tokyo",
96
+ });
97
+ const forecast = await client.callTool({
98
+ name: "check_weekly_forecast_for_city",
99
+ arguments: { city: "London" },
100
+ });
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
+ expect(forecast.content[0].text).toContain("London");
103
+ expect(forecast.structuredContent).toHaveProperty("city", "London");
104
+ expect(forecast.structuredContent).toHaveProperty("forecast");
105
+ });
106
+ it("load_skill returns error for unknown skill", async () => {
107
+ const tool = createLoadSkillTool({
108
+ registry,
109
+ mcpManager: manager,
110
+ enabledTools,
111
+ });
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ const result = await tool.execute("call-2", { name: "nope" }, undefined, undefined, {});
114
+ expect(result.details.error).toBe("not_found");
115
+ const text = result.content[0];
116
+ expect("text" in text && text.text).toContain("not found");
117
+ });
118
+ });
119
+ //# sourceMappingURL=skill-integration.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-integration.test.js","sourceRoot":"","sources":["../../src/skills/skill-integration.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ /** Metadata for a skill discovered from an MCP server. */
2
+ export interface McpSkillMetadata {
3
+ /** Skill name from SKILL.md frontmatter. */
4
+ name: string;
5
+ /** Short description from SKILL.md frontmatter. */
6
+ description: string;
7
+ /** The skill:// URI for the SKILL.md resource. */
8
+ uri: string;
9
+ /** Which MCP server this skill came from. */
10
+ serverName: string;
11
+ /** Tool names this skill gates via allowed-tools frontmatter. */
12
+ allowedTools: string[];
13
+ }
14
+ /**
15
+ * Registry for MCP-discovered skills.
16
+ *
17
+ * Holds skill metadata discovered from MCP servers. Skills are registered
18
+ * during MCP connection and looked up when the model invokes load_skill.
19
+ */
20
+ export declare class SkillRegistry {
21
+ private skills;
22
+ /** Register a skill. Overwrites any existing skill with the same name. */
23
+ register(skill: McpSkillMetadata): void;
24
+ /** Register multiple skills at once. */
25
+ registerAll(skills: McpSkillMetadata[]): void;
26
+ /** Get a skill by name. */
27
+ get(name: string): McpSkillMetadata | undefined;
28
+ /** Get all registered skills. */
29
+ getAll(): McpSkillMetadata[];
30
+ /** Remove a skill by name. */
31
+ unregister(name: string): boolean;
32
+ /** Remove all skills from a specific MCP server. */
33
+ unregisterByServer(serverName: string): void;
34
+ /** Remove all skills. */
35
+ clear(): void;
36
+ /** Number of registered skills. */
37
+ get size(): number;
38
+ }
39
+ //# sourceMappingURL=skill-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-registry.d.ts","sourceRoot":"","sources":["../../src/skills/skill-registry.ts"],"names":[],"mappings":"AAAA,0DAA0D;AAC1D,MAAM,WAAW,gBAAgB;IAC/B,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAuC;IAErD,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAIvC,wCAAwC;IACxC,WAAW,CAAC,MAAM,EAAE,gBAAgB,EAAE,GAAG,IAAI;IAM7C,2BAA2B;IAC3B,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI/C,iCAAiC;IACjC,MAAM,IAAI,gBAAgB,EAAE;IAI5B,8BAA8B;IAC9B,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIjC,oDAAoD;IACpD,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAQ5C,yBAAyB;IACzB,KAAK,IAAI,IAAI;IAIb,mCAAmC;IACnC,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Registry for MCP-discovered skills.
3
+ *
4
+ * Holds skill metadata discovered from MCP servers. Skills are registered
5
+ * during MCP connection and looked up when the model invokes load_skill.
6
+ */
7
+ export class SkillRegistry {
8
+ skills = new Map();
9
+ /** Register a skill. Overwrites any existing skill with the same name. */
10
+ register(skill) {
11
+ this.skills.set(skill.name, skill);
12
+ }
13
+ /** Register multiple skills at once. */
14
+ registerAll(skills) {
15
+ for (const skill of skills) {
16
+ this.register(skill);
17
+ }
18
+ }
19
+ /** Get a skill by name. */
20
+ get(name) {
21
+ return this.skills.get(name);
22
+ }
23
+ /** Get all registered skills. */
24
+ getAll() {
25
+ return [...this.skills.values()];
26
+ }
27
+ /** Remove a skill by name. */
28
+ unregister(name) {
29
+ return this.skills.delete(name);
30
+ }
31
+ /** Remove all skills from a specific MCP server. */
32
+ unregisterByServer(serverName) {
33
+ for (const [name, skill] of this.skills) {
34
+ if (skill.serverName === serverName) {
35
+ this.skills.delete(name);
36
+ }
37
+ }
38
+ }
39
+ /** Remove all skills. */
40
+ clear() {
41
+ this.skills.clear();
42
+ }
43
+ /** Number of registered skills. */
44
+ get size() {
45
+ return this.skills.size;
46
+ }
47
+ }
48
+ //# sourceMappingURL=skill-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-registry.js","sourceRoot":"","sources":["../../src/skills/skill-registry.ts"],"names":[],"mappings":"AAcA;;;;;GAKG;AACH,MAAM,OAAO,aAAa;IAChB,MAAM,GAAG,IAAI,GAAG,EAA4B,CAAC;IAErD,0EAA0E;IAC1E,QAAQ,CAAC,KAAuB;QAC9B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACrC,CAAC;IAED,wCAAwC;IACxC,WAAW,CAAC,MAA0B;QACpC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IAED,2BAA2B;IAC3B,GAAG,CAAC,IAAY;QACd,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC/B,CAAC;IAED,iCAAiC;IACjC,MAAM;QACJ,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;IACnC,CAAC;IAED,8BAA8B;IAC9B,UAAU,CAAC,IAAY;QACrB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;IAED,oDAAoD;IACpD,kBAAkB,CAAC,UAAkB;QACnC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YACxC,IAAI,KAAK,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;gBACpC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAED,yBAAyB;IACzB,KAAK;QACH,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;IACtB,CAAC;IAED,mCAAmC;IACnC,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAC1B,CAAC;CACF"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=skill-registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-registry.test.d.ts","sourceRoot":"","sources":["../../src/skills/skill-registry.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,67 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { SkillRegistry } from "./skill-registry.js";
3
+ function makeSkill(overrides = {}) {
4
+ return {
5
+ name: "test-skill",
6
+ description: "A test skill",
7
+ uri: "skill://test/SKILL.md",
8
+ serverName: "test-server",
9
+ allowedTools: ["tool_a"],
10
+ ...overrides,
11
+ };
12
+ }
13
+ describe("SkillRegistry", () => {
14
+ it("registers and retrieves a skill", () => {
15
+ const reg = new SkillRegistry();
16
+ const skill = makeSkill();
17
+ reg.register(skill);
18
+ expect(reg.get("test-skill")).toEqual(skill);
19
+ expect(reg.size).toBe(1);
20
+ });
21
+ it("getAll returns all registered skills", () => {
22
+ const reg = new SkillRegistry();
23
+ reg.register(makeSkill({ name: "a" }));
24
+ reg.register(makeSkill({ name: "b" }));
25
+ expect(reg.getAll()).toHaveLength(2);
26
+ });
27
+ it("overwrites on duplicate name", () => {
28
+ const reg = new SkillRegistry();
29
+ reg.register(makeSkill({ description: "first" }));
30
+ reg.register(makeSkill({ description: "second" }));
31
+ expect(reg.size).toBe(1);
32
+ expect(reg.get("test-skill")?.description).toBe("second");
33
+ });
34
+ it("unregister removes by name", () => {
35
+ const reg = new SkillRegistry();
36
+ reg.register(makeSkill());
37
+ expect(reg.unregister("test-skill")).toBe(true);
38
+ expect(reg.get("test-skill")).toBeUndefined();
39
+ expect(reg.unregister("test-skill")).toBe(false);
40
+ });
41
+ it("unregisterByServer removes all skills from a server", () => {
42
+ const reg = new SkillRegistry();
43
+ reg.register(makeSkill({ name: "a", serverName: "srv1" }));
44
+ reg.register(makeSkill({ name: "b", serverName: "srv1" }));
45
+ reg.register(makeSkill({ name: "c", serverName: "srv2" }));
46
+ reg.unregisterByServer("srv1");
47
+ expect(reg.size).toBe(1);
48
+ expect(reg.get("c")).toBeDefined();
49
+ });
50
+ it("registerAll registers multiple skills", () => {
51
+ const reg = new SkillRegistry();
52
+ reg.registerAll([makeSkill({ name: "x" }), makeSkill({ name: "y" })]);
53
+ expect(reg.size).toBe(2);
54
+ });
55
+ it("clear removes everything", () => {
56
+ const reg = new SkillRegistry();
57
+ reg.registerAll([makeSkill({ name: "a" }), makeSkill({ name: "b" })]);
58
+ reg.clear();
59
+ expect(reg.size).toBe(0);
60
+ expect(reg.getAll()).toEqual([]);
61
+ });
62
+ it("get returns undefined for unknown name", () => {
63
+ const reg = new SkillRegistry();
64
+ expect(reg.get("nonexistent")).toBeUndefined();
65
+ });
66
+ });
67
+ //# sourceMappingURL=skill-registry.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-registry.test.js","sourceRoot":"","sources":["../../src/skills/skill-registry.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Test MCP weather server — exposes a weather skill with gated tools.
3
+ *
4
+ * Registers:
5
+ * - skill://weather/SKILL.md — a skill resource with frontmatter
6
+ * - check_weather_for_city — current weather (gated by skill)
7
+ * - check_weekly_forecast_for_city — 7-day forecast (gated by skill)
8
+ * - echo — ungated baseline tool
9
+ */
10
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ export declare function createWeatherServer(): McpServer;
12
+ //# sourceMappingURL=weather-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"weather-server.d.ts","sourceRoot":"","sources":["../../src/test-servers/weather-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAuCpE,wBAAgB,mBAAmB,IAAI,SAAS,CA0H/C"}