@introspection-ai/recipes 0.13.0 → 0.14.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 (222) hide show
  1. package/README.md +45 -58
  2. package/dist/{agent-tool.d.ts → agents.d.ts} +4 -1
  3. package/dist/agents.d.ts.map +1 -0
  4. package/dist/{agent-tool.js → agents.js} +1 -1
  5. package/dist/agents.js.map +1 -0
  6. package/dist/api/extensions.d.ts +3 -0
  7. package/dist/api/extensions.d.ts.map +1 -0
  8. package/dist/api/extensions.js +2 -0
  9. package/dist/api/extensions.js.map +1 -0
  10. package/dist/api/mcp.d.ts +4 -0
  11. package/dist/api/mcp.d.ts.map +1 -0
  12. package/dist/api/mcp.js +2 -0
  13. package/dist/api/mcp.js.map +1 -0
  14. package/dist/api/session.d.ts +3 -0
  15. package/dist/api/session.d.ts.map +1 -0
  16. package/dist/api/session.js +2 -0
  17. package/dist/api/session.js.map +1 -0
  18. package/dist/child-agent.d.ts +5 -4
  19. package/dist/child-agent.d.ts.map +1 -1
  20. package/dist/child-agent.js +26 -117
  21. package/dist/child-agent.js.map +1 -1
  22. package/dist/child-session.d.ts +23 -0
  23. package/dist/child-session.d.ts.map +1 -0
  24. package/dist/child-session.js +48 -0
  25. package/dist/child-session.js.map +1 -0
  26. package/dist/extensions.d.ts +29 -0
  27. package/dist/extensions.d.ts.map +1 -0
  28. package/dist/extensions.js +119 -0
  29. package/dist/extensions.js.map +1 -0
  30. package/dist/index.d.ts +4 -13
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -13
  33. package/dist/index.js.map +1 -1
  34. package/dist/inspect.d.ts +38 -5
  35. package/dist/inspect.d.ts.map +1 -1
  36. package/dist/inspect.js +92 -41
  37. package/dist/inspect.js.map +1 -1
  38. package/dist/interactions.d.ts +18 -2
  39. package/dist/interactions.d.ts.map +1 -1
  40. package/dist/interactions.js +41 -10
  41. package/dist/interactions.js.map +1 -1
  42. package/dist/mcp-catalog.d.ts +1 -0
  43. package/dist/mcp-catalog.d.ts.map +1 -1
  44. package/dist/mcp-catalog.js +14 -6
  45. package/dist/mcp-catalog.js.map +1 -1
  46. package/dist/mcp-chunks/auth-command-ZLGYIEUY.js +273 -0
  47. package/dist/mcp-chunks/call-arguments-LU3W6D3K.js +13 -0
  48. package/dist/mcp-chunks/call-command-LKTKSYTH.js +612 -0
  49. package/dist/mcp-chunks/chunk-2U4BSDC4.js +3951 -0
  50. package/dist/mcp-chunks/chunk-2XL7MG7R.js +272 -0
  51. package/dist/mcp-chunks/chunk-3BJA27PY.js +2321 -0
  52. package/dist/mcp-chunks/chunk-4SQKWYFF.js +15 -0
  53. package/dist/mcp-chunks/chunk-5QOSCSQB.js +290 -0
  54. package/dist/mcp-chunks/chunk-6W5QFASN.js +77 -0
  55. package/dist/mcp-chunks/chunk-6XCZGU3G.js +1004 -0
  56. package/dist/mcp-chunks/chunk-7FHEBT5G.js +149 -0
  57. package/dist/mcp-chunks/chunk-7KA2VJVZ.js +114 -0
  58. package/dist/mcp-chunks/chunk-7UDVSOOF.js +138 -0
  59. package/dist/mcp-chunks/chunk-BNV3TPE4.js +66 -0
  60. package/dist/mcp-chunks/chunk-BTLAWHTA.js +129 -0
  61. package/dist/mcp-chunks/chunk-BV56DXPW.js +13 -0
  62. package/dist/mcp-chunks/chunk-BWHIA4PC.js +90 -0
  63. package/dist/mcp-chunks/chunk-BZIQOWE6.js +267 -0
  64. package/dist/mcp-chunks/chunk-D3MRAZ6H.js +16140 -0
  65. package/dist/mcp-chunks/chunk-DHBE7UBH.js +750 -0
  66. package/dist/mcp-chunks/chunk-E22DYOW5.js +251 -0
  67. package/dist/mcp-chunks/chunk-ECIVB7P4.js +139 -0
  68. package/dist/mcp-chunks/chunk-HKRJ6O36.js +1076 -0
  69. package/dist/mcp-chunks/chunk-IABRYROW.js +44 -0
  70. package/dist/mcp-chunks/chunk-J6LYIZXN.js +45 -0
  71. package/dist/mcp-chunks/chunk-L7NTQVYN.js +161 -0
  72. package/dist/mcp-chunks/chunk-LEZSACDE.js +141 -0
  73. package/dist/mcp-chunks/chunk-LKIBAYKU.js +208 -0
  74. package/dist/mcp-chunks/chunk-LNH5LAJD.js +152 -0
  75. package/dist/mcp-chunks/chunk-LQQEYD76.js +124 -0
  76. package/dist/mcp-chunks/chunk-LRKZP7DJ.js +47 -0
  77. package/dist/mcp-chunks/chunk-LS7PMWSR.js +109 -0
  78. package/dist/mcp-chunks/chunk-M3TPODXI.js +95 -0
  79. package/dist/mcp-chunks/chunk-MZ2NARG7.js +400 -0
  80. package/dist/mcp-chunks/chunk-N46NEFAF.js +45 -0
  81. package/dist/mcp-chunks/chunk-NNOEGEUZ.js +17273 -0
  82. package/dist/mcp-chunks/chunk-NRAJX5E4.js +69 -0
  83. package/dist/mcp-chunks/chunk-P3PSC54P.js +869 -0
  84. package/dist/mcp-chunks/chunk-PIIYJR44.js +6341 -0
  85. package/dist/mcp-chunks/chunk-PWYFKGKG.js +385 -0
  86. package/dist/mcp-chunks/chunk-SJCCBNZ4.js +45 -0
  87. package/dist/mcp-chunks/chunk-TATVT5IT.js +194 -0
  88. package/dist/mcp-chunks/chunk-TEUKFFQL.js +29 -0
  89. package/dist/mcp-chunks/chunk-UKYNQGNE.js +150 -0
  90. package/dist/mcp-chunks/chunk-UNKB4SBV.js +37 -0
  91. package/dist/mcp-chunks/chunk-WD6BOD24.js +138 -0
  92. package/dist/mcp-chunks/chunk-WQJZMCJN.js +73 -0
  93. package/dist/mcp-chunks/chunk-XANPGTLI.js +364 -0
  94. package/dist/mcp-chunks/chunk-XQK2JSW4.js +35 -0
  95. package/dist/mcp-chunks/chunk-YHQUTU6L.js +571 -0
  96. package/dist/mcp-chunks/chunk-YVZKMV5H.js +57 -0
  97. package/dist/mcp-chunks/cli-UV4QJZZM.js +929 -0
  98. package/dist/mcp-chunks/client-S7RQUG5U.js +13 -0
  99. package/dist/mcp-chunks/config-command-NLAGKID5.js +1101 -0
  100. package/dist/mcp-chunks/daemon-command-U242Z6VY.js +26 -0
  101. package/dist/mcp-chunks/dist-LK3SHAMX.js +6478 -0
  102. package/dist/mcp-chunks/emit-ts-command-VBXVXO6V.js +386 -0
  103. package/dist/mcp-chunks/generate-cli-runner-6CAZZA3P.js +1426 -0
  104. package/dist/mcp-chunks/inspect-cli-command-3ZORTOR7.js +111 -0
  105. package/dist/mcp-chunks/launch-YKRXHXJI.js +10 -0
  106. package/dist/mcp-chunks/lifecycle-XVKRHPH3.js +16 -0
  107. package/dist/mcp-chunks/list-command-2MEGGNFQ.js +4190 -0
  108. package/dist/mcp-chunks/output-utils-NKESLAJ6.js +12 -0
  109. package/dist/mcp-chunks/prompt-B1Yc1NPt-VGOTWJMN.js +918 -0
  110. package/dist/mcp-chunks/record-command-YXHTTTT5.js +17 -0
  111. package/dist/mcp-chunks/replay-command-25HJ6AQU.js +85 -0
  112. package/dist/mcp-chunks/resource-command-OO4NYF5C.js +97 -0
  113. package/dist/mcp-chunks/result-utils-MOHHLNM7.js +15 -0
  114. package/dist/mcp-chunks/runtime-5WJ3G25Y.js +33 -0
  115. package/dist/mcp-chunks/runtime-wrapper-RPWDIDBF.js +10 -0
  116. package/dist/mcp-chunks/serve-command-DHXJZMMN.js +3302 -0
  117. package/dist/mcp-chunks/timeouts-W2XWCCXR.js +18 -0
  118. package/dist/mcp-chunks/vault-command-PIYIUEZL.js +154 -0
  119. package/dist/mcp-daemon-client.d.ts +6 -1
  120. package/dist/mcp-daemon-client.d.ts.map +1 -1
  121. package/dist/mcp-daemon-client.js +119 -2
  122. package/dist/mcp-daemon-client.js.map +1 -1
  123. package/dist/mcp-daemon-protocol.d.ts +18 -1
  124. package/dist/mcp-daemon-protocol.d.ts.map +1 -1
  125. package/dist/mcp-daemon-protocol.js +31 -0
  126. package/dist/mcp-daemon-protocol.js.map +1 -1
  127. package/dist/mcp-daemon.js +169 -76602
  128. package/dist/mcp-daemon.js.map +1 -1
  129. package/dist/mcp-policy.d.ts +11 -0
  130. package/dist/mcp-policy.d.ts.map +1 -0
  131. package/dist/mcp-policy.js +27 -0
  132. package/dist/mcp-policy.js.map +1 -0
  133. package/dist/mcp-run-worker.js +38 -76540
  134. package/dist/mcp-tools.d.ts +39 -0
  135. package/dist/mcp-tools.d.ts.map +1 -0
  136. package/dist/mcp-tools.js +493 -0
  137. package/dist/mcp-tools.js.map +1 -0
  138. package/dist/mcp.d.ts +21 -9
  139. package/dist/mcp.d.ts.map +1 -1
  140. package/dist/mcp.js +101 -31
  141. package/dist/mcp.js.map +1 -1
  142. package/dist/model-binding.d.ts +39 -0
  143. package/dist/model-binding.d.ts.map +1 -0
  144. package/dist/model-binding.js +103 -0
  145. package/dist/model-binding.js.map +1 -0
  146. package/dist/pi-extension.d.ts +2 -22
  147. package/dist/pi-extension.d.ts.map +1 -1
  148. package/dist/pi-extension.js +296 -71
  149. package/dist/pi-extension.js.map +1 -1
  150. package/dist/recipe/resolve.d.ts +43 -20
  151. package/dist/recipe/resolve.d.ts.map +1 -1
  152. package/dist/recipe/resolve.js +215 -70
  153. package/dist/recipe/resolve.js.map +1 -1
  154. package/dist/recipe-agent.d.ts +24 -27
  155. package/dist/recipe-agent.d.ts.map +1 -1
  156. package/dist/recipe-agent.js +278 -230
  157. package/dist/recipe-agent.js.map +1 -1
  158. package/dist/recipe-check.d.ts +17 -0
  159. package/dist/recipe-check.d.ts.map +1 -0
  160. package/dist/recipe-check.js +258 -0
  161. package/dist/recipe-check.js.map +1 -0
  162. package/dist/recipe-extensions.d.ts.map +1 -1
  163. package/dist/recipe-extensions.js +0 -3
  164. package/dist/recipe-extensions.js.map +1 -1
  165. package/dist/recipe-model.d.ts +3 -1
  166. package/dist/recipe-model.d.ts.map +1 -1
  167. package/dist/recipe-model.js +15 -12
  168. package/dist/recipe-model.js.map +1 -1
  169. package/dist/recipe-package.d.ts +6 -28
  170. package/dist/recipe-package.d.ts.map +1 -1
  171. package/dist/recipe-package.js +248 -191
  172. package/dist/recipe-package.js.map +1 -1
  173. package/dist/recipe-skills.d.ts.map +1 -1
  174. package/dist/recipe-skills.js +42 -29
  175. package/dist/recipe-skills.js.map +1 -1
  176. package/dist/run-controller.d.ts +10 -9
  177. package/dist/run-controller.d.ts.map +1 -1
  178. package/dist/run-controller.js +28 -21
  179. package/dist/run-controller.js.map +1 -1
  180. package/dist/session.d.ts +40 -53
  181. package/dist/session.d.ts.map +1 -1
  182. package/dist/session.js +312 -173
  183. package/dist/session.js.map +1 -1
  184. package/dist/test-utils.d.ts +7 -7
  185. package/dist/test-utils.d.ts.map +1 -1
  186. package/dist/test-utils.js +32 -15
  187. package/dist/test-utils.js.map +1 -1
  188. package/docs/agent-composition.md +41 -35
  189. package/docs/host-api.md +201 -0
  190. package/docs/index.md +8 -14
  191. package/docs/interactions.md +12 -11
  192. package/docs/mcp-auth.md +6 -6
  193. package/docs/mcp-configuration.md +72 -24
  194. package/docs/pi-extension.md +39 -11
  195. package/docs/recipe-flow.md +9 -9
  196. package/docs/recipe-format.md +90 -41
  197. package/package.json +23 -17
  198. package/vendor/mcp-client/darwin-arm64/mcp-client +0 -0
  199. package/vendor/mcp-client/darwin-x64/mcp-client +0 -0
  200. package/vendor/mcp-client/linux-arm64/mcp-client +0 -0
  201. package/vendor/recipe-check/darwin-arm64/recipe-check +0 -0
  202. package/vendor/recipe-check/darwin-x64/recipe-check +0 -0
  203. package/vendor/recipe-check/linux-arm64/recipe-check +0 -0
  204. package/vendor/recipe-check/linux-x64/recipe-check +0 -0
  205. package/vendor/recipe-check/linux-x64-musl/recipe-check +0 -0
  206. package/vendor/recipe-check/win32-x64/recipe-check.exe +0 -0
  207. package/dist/agent-tool.d.ts.map +0 -1
  208. package/dist/agent-tool.js.map +0 -1
  209. package/dist/run.d.ts +0 -28
  210. package/dist/run.d.ts.map +0 -1
  211. package/dist/run.js +0 -84
  212. package/dist/run.js.map +0 -1
  213. package/dist/testing.d.ts +0 -25
  214. package/dist/testing.d.ts.map +0 -1
  215. package/dist/testing.js +0 -96
  216. package/dist/testing.js.map +0 -1
  217. package/docs/deployment-configuration.md +0 -65
  218. package/docs/migration.md +0 -70
  219. package/docs/python-bindings-release.md +0 -47
  220. package/docs/recipe-evals.md +0 -70
  221. package/docs/recipe-judges.md +0 -152
  222. package/docs/runtime-library.md +0 -171
@@ -0,0 +1,2321 @@
1
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
2
+ import {
3
+ createCallResult
4
+ } from "./chunk-5QOSCSQB.js";
5
+ import {
6
+ createRuntime
7
+ } from "./chunk-NNOEGEUZ.js";
8
+
9
+ // src/mcp-command-context.ts
10
+ import { AsyncLocalStorage } from "node:async_hooks";
11
+ var storage = new AsyncLocalStorage();
12
+ function currentMcpCommandContext() {
13
+ return storage.getStore();
14
+ }
15
+ function runWithMcpCommandContext(context, task) {
16
+ return storage.run(context, task);
17
+ }
18
+ function installMcpCommandIoRouting() {
19
+ const stdoutWrite = process.stdout.write.bind(process.stdout);
20
+ const stderrWrite = process.stderr.write.bind(process.stderr);
21
+ process.stdout.write = ((chunk, ...args) => {
22
+ const target = currentMcpCommandContext()?.stdout;
23
+ return target ? target.write(chunk) : stdoutWrite(chunk, ...args);
24
+ });
25
+ process.stderr.write = ((chunk, ...args) => {
26
+ const target = currentMcpCommandContext()?.stderr;
27
+ return target ? target.write(chunk) : stderrWrite(chunk, ...args);
28
+ });
29
+ return () => {
30
+ process.stdout.write = stdoutWrite;
31
+ process.stderr.write = stderrWrite;
32
+ };
33
+ }
34
+
35
+ // src/mcp-daemon-protocol.ts
36
+ var MCP_DAEMON_SOCKET_ENV = "PI_RECIPES_MCP_DAEMON_SOCKET";
37
+ var MCP_DAEMON_TOKEN_ENV = "PI_RECIPES_MCP_DAEMON_TOKEN";
38
+ var MCP_DAEMON_FINGERPRINT_ENV = "PI_RECIPES_MCP_DAEMON_FINGERPRINT";
39
+ var MCP_DAEMON_PARENT_PID_ENV = "PI_RECIPES_MCP_DAEMON_PARENT_PID";
40
+ var MCP_DAEMON_MAX_FRAME_BYTES = 10 * 1024 * 1024;
41
+ function serializeMcpDaemonEnvelope(envelope, maxBytes = MCP_DAEMON_MAX_FRAME_BYTES) {
42
+ let estimatedBytes = 0;
43
+ const seen = /* @__PURE__ */ new WeakSet();
44
+ const serialized = JSON.stringify(envelope, (key, value) => {
45
+ estimatedBytes += Buffer.byteLength(key, "utf8") + 4;
46
+ if (typeof value === "string") {
47
+ estimatedBytes += Buffer.byteLength(value, "utf8") + 2;
48
+ } else if (typeof value === "number" || typeof value === "boolean" || value === null) {
49
+ estimatedBytes += 16;
50
+ } else if (value && typeof value === "object") {
51
+ if (seen.has(value)) {
52
+ throw new Error("MCP daemon envelope contains a circular value.");
53
+ }
54
+ seen.add(value);
55
+ }
56
+ if (estimatedBytes > maxBytes) {
57
+ throw new Error("MCP daemon envelope exceeded the frame limit.");
58
+ }
59
+ return value;
60
+ });
61
+ if (Buffer.byteLength(serialized, "utf8") > maxBytes) {
62
+ throw new Error("MCP daemon envelope exceeded the frame limit.");
63
+ }
64
+ return `${serialized}
65
+ `;
66
+ }
67
+
68
+ // src/mcp-cli-core.ts
69
+ import { createHash, randomUUID } from "node:crypto";
70
+ import { existsSync } from "node:fs";
71
+ import { mkdir, open, readFile, rename, rm, stat, writeFile } from "node:fs/promises";
72
+ import { dirname as dirname2, join as join2 } from "node:path";
73
+ import { setTimeout as delay } from "node:timers/promises";
74
+ import { stdin as input, stderr, stdout } from "node:process";
75
+ import { isMainThread, Worker } from "node:worker_threads";
76
+
77
+ // src/mcp-contract.ts
78
+ var MAX_EXAMPLE_ARRAY_ITEMS = 3;
79
+ var SAFE_CLI_KEY = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
80
+ var SAFE_SHELL_TOKEN = /^[A-Za-z0-9_./:@+-]+$/;
81
+ function record(value) {
82
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
83
+ }
84
+ function number(value) {
85
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
86
+ }
87
+ function quoted(value) {
88
+ return JSON.stringify(value);
89
+ }
90
+ function renderSchema(value) {
91
+ const schema = record(value);
92
+ if (!schema || !Array.isArray(schema.allOf)) return schema;
93
+ const branches = schema.allOf.map(renderSchema).filter((branch) => Boolean(branch));
94
+ const baseProperties = record(schema.properties) ?? {};
95
+ const objectLike = branches.every(
96
+ (branch) => branch.type === "object" || Boolean(record(branch.properties))
97
+ );
98
+ if (!objectLike) return { ...schema, allOf: branches };
99
+ const properties = Object.assign(
100
+ {},
101
+ baseProperties,
102
+ ...branches.map((branch) => record(branch.properties) ?? {})
103
+ );
104
+ const required = new Set(
105
+ Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === "string") : []
106
+ );
107
+ for (const branch of branches) {
108
+ if (!Array.isArray(branch.required)) continue;
109
+ for (const entry of branch.required) {
110
+ if (typeof entry === "string") required.add(entry);
111
+ }
112
+ }
113
+ const { allOf: _allOf, ...rest } = schema;
114
+ return {
115
+ ...rest,
116
+ type: "object",
117
+ properties,
118
+ ...required.size > 0 ? { required: [...required] } : {}
119
+ };
120
+ }
121
+ function constraintSuffix(schema) {
122
+ const constraints = [];
123
+ const minimum = number(schema.minimum);
124
+ const maximum = number(schema.maximum);
125
+ if (minimum !== void 0 || maximum !== void 0) {
126
+ constraints.push(`${minimum ?? ""}..${maximum ?? ""}`);
127
+ }
128
+ const minLength = number(schema.minLength);
129
+ const maxLength = number(schema.maxLength);
130
+ if (minLength !== void 0) constraints.push(`minLength=${minLength}`);
131
+ if (maxLength !== void 0) constraints.push(`maxLength=${maxLength}`);
132
+ const minItems = number(schema.minItems);
133
+ const maxItems = number(schema.maxItems);
134
+ if (minItems !== void 0) constraints.push(`minItems=${minItems}`);
135
+ if (maxItems !== void 0) constraints.push(`maxItems=${maxItems}`);
136
+ if (schema.default !== void 0) constraints.push(`default=${quoted(schema.default)}`);
137
+ return constraints.length > 0 ? ` [${constraints.join(", ")}]` : "";
138
+ }
139
+ function compactSchemaType(value) {
140
+ const schema = renderSchema(value);
141
+ if (!schema) return "unknown";
142
+ if (Array.isArray(schema.allOf)) {
143
+ return schema.allOf.map(compactSchemaType).join(" & ");
144
+ }
145
+ if (schema.const !== void 0) return quoted(schema.const);
146
+ if (Array.isArray(schema.enum)) return schema.enum.map(quoted).join(" | ");
147
+ const union = Array.isArray(schema.anyOf) ? schema.anyOf : Array.isArray(schema.oneOf) ? schema.oneOf : void 0;
148
+ if (union) return union.map(compactSchemaType).join(" | ");
149
+ if (schema.type === "array") {
150
+ return `${compactSchemaType(schema.items)}[]${constraintSuffix(schema)}`;
151
+ }
152
+ if (schema.type === "object" || record(schema.properties)) {
153
+ const properties = record(schema.properties);
154
+ if (!properties || Object.keys(properties).length === 0) return "object";
155
+ const required = new Set(
156
+ Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === "string") : []
157
+ );
158
+ const fields = Object.entries(properties).map(
159
+ ([name, descriptor]) => `${name}${required.has(name) ? "" : "?"}: ${compactSchemaType(descriptor)}`
160
+ );
161
+ return `{${fields.join(", ")}}`;
162
+ }
163
+ const type = schema.type === "number" ? "number" : schema.type;
164
+ return `${typeof type === "string" ? type : "unknown"}${constraintSuffix(schema)}`;
165
+ }
166
+ function descriptionText(value, verbose) {
167
+ if (typeof value !== "string" || !value.trim()) return "";
168
+ const normalized = value.trim().replace(/\s+/g, " ");
169
+ if (verbose || normalized.length <= 160) return normalized;
170
+ return `${normalized.slice(0, 159).trimEnd()}\u2026`;
171
+ }
172
+ function hasLongDescription(value) {
173
+ if (Array.isArray(value)) return value.some(hasLongDescription);
174
+ const object = record(value);
175
+ if (!object) return false;
176
+ if (typeof object.description === "string" && object.description.trim().replace(/\s+/g, " ").length > 160) {
177
+ return true;
178
+ }
179
+ return Object.entries(object).some(
180
+ ([key, nested]) => key !== "description" && hasLongDescription(nested)
181
+ );
182
+ }
183
+ function schemaLines(value, indent, options = {}) {
184
+ const schema = renderSchema(value);
185
+ if (!schema) return [`${indent}unknown`];
186
+ const properties = record(schema.properties);
187
+ if (!properties) return [`${indent}${compactSchemaType(schema)}`];
188
+ const required = new Set(
189
+ Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === "string") : []
190
+ );
191
+ const lines = [];
192
+ for (const [name, descriptorValue] of Object.entries(properties)) {
193
+ const descriptor = record(descriptorValue) ?? {};
194
+ const optional = required.has(name) ? "" : "?";
195
+ const compactDescription = descriptionText(
196
+ descriptor.description,
197
+ options.verboseDescriptions === true
198
+ );
199
+ const description = compactDescription ? ` \u2014 ${compactDescription}` : "";
200
+ const nested = record(descriptor.properties);
201
+ if (nested) {
202
+ lines.push(`${indent}${name}${optional}: {`);
203
+ lines.push(...schemaLines(descriptor, `${indent} `, options));
204
+ lines.push(`${indent}}${description}`);
205
+ } else {
206
+ lines.push(`${indent}${name}${optional}: ${compactSchemaType(descriptor)}${description}`);
207
+ }
208
+ }
209
+ return lines.length > 0 ? lines : [`${indent}{}`];
210
+ }
211
+ function schemaObject(value) {
212
+ return record(value);
213
+ }
214
+ function shellQuote(value) {
215
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
216
+ }
217
+ function shellToken(value) {
218
+ return SAFE_SHELL_TOKEN.test(value) ? value : shellQuote(value);
219
+ }
220
+ function toolSelector(server, tool) {
221
+ return shellToken(`${server}.${tool}`);
222
+ }
223
+ function exampleValue(value, name) {
224
+ const schema = renderSchema(value) ?? {};
225
+ const union = Array.isArray(schema.anyOf) ? schema.anyOf : Array.isArray(schema.oneOf) ? schema.oneOf : void 0;
226
+ if (union?.length) return exampleValue(union[0], name);
227
+ if (schema.const !== void 0) return schema.const;
228
+ if (Array.isArray(schema.enum) && schema.enum.length > 0) return schema.enum[0];
229
+ if (schema.type === "boolean") return true;
230
+ if (schema.type === "number" || schema.type === "integer") {
231
+ return number(schema.minimum) ?? 1;
232
+ }
233
+ if (schema.type === "array") {
234
+ const count = Math.min(
235
+ MAX_EXAMPLE_ARRAY_ITEMS,
236
+ Math.max(1, number(schema.minItems) ?? 1)
237
+ );
238
+ return Array.from({ length: count }, () => exampleValue(schema.items, "value"));
239
+ }
240
+ if (schema.type === "object" || record(schema.properties)) {
241
+ const properties = record(schema.properties) ?? {};
242
+ const required = Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === "string") : [];
243
+ return Object.fromEntries(
244
+ required.map((field) => [field, exampleValue(properties[field], field)])
245
+ );
246
+ }
247
+ return `<${name}>`;
248
+ }
249
+ function callValue(value, name) {
250
+ const example = exampleValue(value, name);
251
+ return typeof example === "string" ? shellQuote(example) : typeof example === "object" ? shellQuote(JSON.stringify(example)) : String(example);
252
+ }
253
+ function callExample(server, tool) {
254
+ const schema = renderSchema(tool.inputSchema);
255
+ const properties = record(schema?.properties) ?? {};
256
+ const required = Array.isArray(schema?.required) ? schema.required.filter((entry) => typeof entry === "string") : [];
257
+ if (required.some(
258
+ (name) => !SAFE_CLI_KEY.test(name) || Boolean(
259
+ structuredBranch(properties[name], "object") ?? structuredBranch(properties[name], "array")
260
+ )
261
+ )) {
262
+ const json = JSON.stringify(
263
+ Object.fromEntries(required.map((name) => [name, exampleValue(properties[name], name)]))
264
+ );
265
+ return `mcp call ${toolSelector(server, tool.name)} --json ${shellQuote(json)}`;
266
+ }
267
+ const args = required.map((name) => {
268
+ const descriptor = record(properties[name]) ?? {};
269
+ return `${name}=${callValue(descriptor, name)}`;
270
+ });
271
+ return `mcp call ${toolSelector(server, tool.name)}${args.length > 0 ? ` ${args.join(" ")}` : ""}`;
272
+ }
273
+ function structuredBranch(value, kind) {
274
+ const schema = renderSchema(value);
275
+ if (!schema) return void 0;
276
+ if (kind === "array" && schema.type === "array" || kind === "object" && (schema.type === "object" || record(schema.properties))) {
277
+ return schema;
278
+ }
279
+ const union = Array.isArray(schema.anyOf) ? schema.anyOf : Array.isArray(schema.oneOf) ? schema.oneOf : [];
280
+ return union.map((branch) => structuredBranch(branch, kind)).find(Boolean);
281
+ }
282
+ function structuredCallExample(server, tool) {
283
+ const schema = renderSchema(tool.inputSchema);
284
+ const properties = record(schema?.properties) ?? {};
285
+ const required = new Set(
286
+ Array.isArray(schema?.required) ? schema.required.filter((entry) => typeof entry === "string") : []
287
+ );
288
+ const requiredValues = Object.fromEntries(
289
+ [...required].map((name) => [name, exampleValue(properties[name], name)])
290
+ );
291
+ for (const kind of ["object", "array"]) {
292
+ for (const [name, descriptor] of Object.entries(properties)) {
293
+ const branch = structuredBranch(descriptor, kind);
294
+ if (!branch) continue;
295
+ const json = JSON.stringify({
296
+ ...requiredValues,
297
+ ...!required.has(name) ? { [name]: exampleValue(branch, name) } : {}
298
+ });
299
+ return `mcp call ${toolSelector(server, tool.name)} --json ${shellQuote(json)}`;
300
+ }
301
+ }
302
+ return void 0;
303
+ }
304
+ function renderToolContract(server, tool, options = {}) {
305
+ const lines = [`${server}.${tool.name}`];
306
+ const description = descriptionText(
307
+ tool.description,
308
+ options.verboseDescriptions === true
309
+ );
310
+ if (description) {
311
+ lines.push(description);
312
+ }
313
+ if (!options.verboseDescriptions && (descriptionText(tool.description, true).length > 160 || hasLongDescription(tool.inputSchema) || hasLongDescription(tool.outputSchema))) {
314
+ lines.push("Descriptions shortened; use --verbose for full text.");
315
+ }
316
+ lines.push("", "input", ...schemaLines(tool.inputSchema, " ", options));
317
+ lines.push(
318
+ "",
319
+ "output",
320
+ ...schemaObject(tool.outputSchema) ? schemaLines(tool.outputSchema, " ", options) : [" unspecified"]
321
+ );
322
+ lines.push("", "call", ` ${callExample(server, tool)}`);
323
+ const structured = structuredCallExample(server, tool);
324
+ if (structured) lines.push(` structured: ${structured}`);
325
+ return `${lines.join("\n")}
326
+ `;
327
+ }
328
+ function renderToolSignature(server, tool, options = {}) {
329
+ const schema = renderSchema(tool.inputSchema);
330
+ const properties = record(schema?.properties) ?? {};
331
+ const required = new Set(
332
+ Array.isArray(schema?.required) ? schema.required.filter((entry) => typeof entry === "string") : []
333
+ );
334
+ const entries = Object.entries(properties);
335
+ const included = new Set(
336
+ entries.filter(([name]) => required.has(name)).map(([name]) => name)
337
+ );
338
+ if (!options.allParameters) {
339
+ for (const [name] of entries) {
340
+ if (included.size >= 5) break;
341
+ included.add(name);
342
+ }
343
+ }
344
+ const visible = options.allParameters ? entries : entries.filter(([name]) => included.has(name));
345
+ const parameters = visible.map(([name, descriptor]) => {
346
+ const optional = required.has(name) ? "" : "?";
347
+ return `${name}${optional}: ${compactSchemaType(descriptor)}`;
348
+ });
349
+ const hidden = entries.length - visible.length;
350
+ if (hidden > 0) parameters.push(`\u2026 +${hidden} optional`);
351
+ return `${server}.${tool.name}(${parameters.join(", ")})`;
352
+ }
353
+ function catalogOutputSchema(tool) {
354
+ return tool?.output_schema;
355
+ }
356
+
357
+ // src/mcp-cli-help.ts
358
+ function mcpCliHelpText() {
359
+ return [
360
+ "mcp - use available MCP tools",
361
+ "Supported: search, list, call, and run.",
362
+ "Run `mcp <command> --help` for complete command syntax.",
363
+ "Use this session-local `mcp` command, not `mcporter` or `npx mcporter`; it enforces the materialized recipe capabilities.",
364
+ "",
365
+ "Recommended flow:",
366
+ ' mcp search "what you need"',
367
+ " mcp list <server.tool> --schema",
368
+ " mcp call <server>.<tool> key=value ...",
369
+ " Search for a tool, inspect its compact contract, then call it.",
370
+ " Metadata is compact text; actual structured tool results are JSON.",
371
+ "",
372
+ "Batch or compose multiple calls in JavaScript:",
373
+ " mcp run <<'JS'",
374
+ ' const ids = ["id-1", "id-2", "id-3"]',
375
+ ' const results = await Promise.all(ids.map(id => tools["server"]["tool"]({ id })))',
376
+ " console.log(JSON.stringify(results.map(result => ({ id: result.id, name: result.name })), null, 2))",
377
+ " JS",
378
+ " Keep the heredoc quoted (<<'JS'). Use run for batching, parallel calls, or dependent workflows\u2014not for a single call.",
379
+ "",
380
+ "Notes:",
381
+ " Use mcp list or mcp list <server> only when you need to browse the available inventory.",
382
+ " Run `mcp call --help` or `mcp run --help` for advanced argument, output, and workflow options.",
383
+ " Tool commands are always headless. If authentication is required, ask the user to authenticate the MCP connection outside the agent session, then retry.",
384
+ "",
385
+ "Availability:",
386
+ " Search and list expose only tools callable in this session.",
387
+ " Only exact tool names returned by mcp list are callable.",
388
+ " Descriptions may mention related tools that are not exposed; mentions do not grant access.",
389
+ " If no listed tool supports an action, report that the connected capability is unavailable.",
390
+ " MCP resources and administrative or ad-hoc connection commands are not exposed."
391
+ ].join("\n");
392
+ }
393
+
394
+ // src/mcp.ts
395
+ import { delimiter, dirname, join, resolve } from "node:path";
396
+
397
+ // src/recipe-package.ts
398
+ var RESOURCE_KEYS = [
399
+ "agents",
400
+ "extensions",
401
+ "skills",
402
+ "prompts"
403
+ ];
404
+ var PI_KEYS = /* @__PURE__ */ new Set([...RESOURCE_KEYS, "mcp"]);
405
+
406
+ // src/mcp-policy.ts
407
+ function mcpSelectionAllowsTool(selection, toolName) {
408
+ const included = selection.include?.some((selector) => {
409
+ const trimmed = selector.trim();
410
+ return trimmed === "*" || trimmed === toolName;
411
+ }) ?? false;
412
+ if (!included) return false;
413
+ return !(selection.exclude ?? []).some(
414
+ (selector) => selector.trim() === toolName
415
+ );
416
+ }
417
+
418
+ // src/mcp-daemon-client.ts
419
+ var MAX_DAEMON_FRAME_BYTES = 10 * 1024 * 1024;
420
+
421
+ // src/mcp.ts
422
+ var RECIPE_ENV_PREFIX = "PI_RECIPES_";
423
+ var MCP_SESSION_ENV = `${RECIPE_ENV_PREFIX}MCP_SESSION`;
424
+ var MCP_LOCAL_CONFIG_ENV = `${RECIPE_ENV_PREFIX}MCP_LOCAL_CONFIG`;
425
+ var MCP_BIN_DIR_ENV = `${RECIPE_ENV_PREFIX}MCP_BIN_DIR`;
426
+ function defaultMcpSessionPath(cwd) {
427
+ return join(cwd, ".pi", "mcp-session.json");
428
+ }
429
+ function defaultMcporterConfigPath(cwd) {
430
+ return join(cwd, ".pi", "mcporter.json");
431
+ }
432
+ function mcpSessionAllowsTool(server, toolName) {
433
+ return mcpSelectionAllowsTool(server.package_tools, toolName) && server.agent_tools.some(
434
+ (selection) => mcpSelectionAllowsTool(selection, toolName)
435
+ );
436
+ }
437
+ function escapeRegExp(value) {
438
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
439
+ }
440
+ function filterMcpCatalog(server, catalog) {
441
+ const allowed = catalog.filter(
442
+ (tool) => mcpSessionAllowsTool(server, tool.name)
443
+ );
444
+ const hiddenNames = catalog.filter((tool) => !mcpSessionAllowsTool(server, tool.name)).map((tool) => tool.name).filter(Boolean).sort((left, right) => right.length - left.length);
445
+ if (hiddenNames.length === 0) return allowed;
446
+ return allowed.map((tool) => ({
447
+ ...tool,
448
+ ...tool.description ? {
449
+ description: hiddenNames.reduce(
450
+ (description, name) => description.replace(
451
+ new RegExp(
452
+ `(^|[^A-Za-z0-9_-])${escapeRegExp(name)}(?=$|[^A-Za-z0-9_-])`,
453
+ "g"
454
+ ),
455
+ "$1[unavailable MCP tool]"
456
+ ),
457
+ tool.description
458
+ )
459
+ } : {}
460
+ }));
461
+ }
462
+
463
+ // src/mcp-cli-policy.ts
464
+ var FORBIDDEN_DELEGATED_FLAGS = /* @__PURE__ */ new Set([
465
+ "--config",
466
+ "--root",
467
+ "--log-level",
468
+ "--http-url",
469
+ "--sse",
470
+ "--stdio",
471
+ "--stdio-bin",
472
+ "--stdio-arg",
473
+ "--header",
474
+ "--env",
475
+ "--cwd",
476
+ "--name",
477
+ "--description",
478
+ "--persist",
479
+ "--allow-http",
480
+ "--insecure",
481
+ "--yes",
482
+ "--server",
483
+ "--tool",
484
+ "--tail-log",
485
+ "--brief",
486
+ "--signatures",
487
+ "--args",
488
+ "--no-oauth",
489
+ "--oauth-timeout",
490
+ "--raw-strings",
491
+ "--no-coerce"
492
+ ]);
493
+ var LIST_OUTPUT_MODES = /* @__PURE__ */ new Map([
494
+ ["--schema", "schema"],
495
+ ["--all-parameters", "all-parameters"],
496
+ ["--status", "status"]
497
+ ]);
498
+ function flagName(value) {
499
+ const equals = value.indexOf("=");
500
+ return equals === -1 ? value : value.slice(0, equals);
501
+ }
502
+ function toolSelector2(value) {
503
+ const ref = value.trim();
504
+ if (/[()]/.test(ref)) return null;
505
+ const dot = ref.indexOf(".");
506
+ if (dot < 1 || dot === ref.length - 1) return null;
507
+ return { server: ref.slice(0, dot), tool: ref.slice(dot + 1) };
508
+ }
509
+ function withNoOAuth(args) {
510
+ const literalSeparator = args.indexOf("--");
511
+ if (literalSeparator === -1) return [...args, "--no-oauth"];
512
+ return [
513
+ ...args.slice(0, literalSeparator),
514
+ "--no-oauth",
515
+ ...args.slice(literalSeparator)
516
+ ];
517
+ }
518
+ function forbiddenFlag(args) {
519
+ const literalSeparator = args.indexOf("--");
520
+ const options = literalSeparator === -1 ? args : args.slice(0, literalSeparator);
521
+ return options.map(flagName).find((flag) => FORBIDDEN_DELEGATED_FLAGS.has(flag));
522
+ }
523
+ function listOutputModeError(args) {
524
+ const literalSeparator = args.indexOf("--");
525
+ const options = literalSeparator === -1 ? args : args.slice(0, literalSeparator);
526
+ const modes = new Set(
527
+ options.map((arg) => LIST_OUTPUT_MODES.get(flagName(arg))).filter((mode) => Boolean(mode))
528
+ );
529
+ return modes.size > 1 ? "mcp list accepts only one output mode: --schema, --all-parameters, or --status." : void 0;
530
+ }
531
+ function closestName(input2, candidates) {
532
+ const normalize = (value) => value.replace(/[^a-z0-9]/gi, "").toLowerCase();
533
+ const source = normalize(input2);
534
+ const distance = (left, right) => {
535
+ let previous = Array.from({ length: right.length + 1 }, (_, index) => index);
536
+ for (let i = 1; i <= left.length; i += 1) {
537
+ const current = [i];
538
+ for (let j = 1; j <= right.length; j += 1) {
539
+ current[j] = Math.min(
540
+ current[j - 1] + 1,
541
+ previous[j] + 1,
542
+ previous[j - 1] + (left[i - 1] === right[j - 1] ? 0 : 1)
543
+ );
544
+ }
545
+ previous = current;
546
+ }
547
+ return previous[right.length];
548
+ };
549
+ let best;
550
+ let bestDistance = Number.POSITIVE_INFINITY;
551
+ for (const candidate of candidates) {
552
+ const candidateDistance = distance(source, normalize(candidate));
553
+ if (candidateDistance < bestDistance) {
554
+ best = candidate;
555
+ bestDistance = candidateDistance;
556
+ }
557
+ }
558
+ if (!best) return void 0;
559
+ const baseline = Math.max(source.length, normalize(best).length, 1);
560
+ return bestDistance <= Math.max(2, Math.floor(baseline / 3)) ? best : void 0;
561
+ }
562
+ function validateExactTarget(policy, server, tool) {
563
+ const configured = policy.servers.get(server);
564
+ if (!configured) {
565
+ const available = [...policy.servers.keys()];
566
+ const suggestion = closestName(server, available);
567
+ return available.length > 0 ? `MCP server '${server}' is not available in this session.${suggestion ? ` Did you mean '${suggestion}'?` : ""} Available servers: ${available.join(", ")}.` : `MCP server '${server}' is not available in this session. No MCP servers are configured.`;
568
+ }
569
+ if (tool !== void 0 && !mcpSessionAllowsTool(configured, tool)) {
570
+ const known = configured.catalog?.map((entry) => entry.name) ?? [];
571
+ const suggestion = closestName(tool, known);
572
+ return `Tool '${tool}' is not available on server '${server}' in this session.${suggestion ? ` Did you mean '${suggestion}'?` : ""} Run \`mcp list ${server}\` to inspect the callable tools.`;
573
+ }
574
+ return null;
575
+ }
576
+ function validateList(args, policy) {
577
+ const blocked = forbiddenFlag(args.slice(1));
578
+ if (blocked) return { error: `mcp list option '${blocked}' is unavailable in recipe sessions.` };
579
+ const outputModeError = listOutputModeError(args.slice(1));
580
+ if (outputModeError) return { error: outputModeError };
581
+ const adHocTarget = args.slice(1).find((arg) => !arg.startsWith("-") && /^(?:https?:\/\/|[^/]+\/)/i.test(arg));
582
+ if (adHocTarget) {
583
+ return { error: "mcp list accepts only servers materialized for this recipe session; URLs and ad-hoc servers are unavailable." };
584
+ }
585
+ const target = args[1] && !args[1].startsWith("-") ? args[1] : void 0;
586
+ if (target !== void 0) {
587
+ const selector = toolSelector2(target);
588
+ const server = selector?.server ?? target;
589
+ const error = validateExactTarget(policy, server, selector?.tool);
590
+ if (error) return { error };
591
+ }
592
+ return {
593
+ command: {
594
+ args: withNoOAuth(args)
595
+ }
596
+ };
597
+ }
598
+ var CALL_FLAGS_WITH_VALUE = /* @__PURE__ */ new Set([
599
+ "--json",
600
+ "--output",
601
+ "--timeout"
602
+ ]);
603
+ function callSyntaxError(args) {
604
+ for (let index = 0; index < args.length; index += 1) {
605
+ const arg = args[index];
606
+ if (arg === "--") {
607
+ return "mcp call positional arguments are unavailable; use key=value or --json instead.";
608
+ }
609
+ if (arg.startsWith("--")) {
610
+ const flag = flagName(arg);
611
+ if (!CALL_FLAGS_WITH_VALUE.has(flag)) {
612
+ return `mcp call option '${flag}' is unavailable; use key=value or --json for tool arguments.`;
613
+ }
614
+ if (!arg.includes("=")) index += 1;
615
+ continue;
616
+ }
617
+ if (/^[A-Za-z_][A-Za-z0-9_.-]*=/.test(arg)) continue;
618
+ return `mcp call argument '${arg}' must use key=value syntax or be supplied through --json.`;
619
+ }
620
+ return void 0;
621
+ }
622
+ function validateCall(args, policy) {
623
+ const rawSelector = args[1];
624
+ const selector = rawSelector ? toolSelector2(rawSelector) : null;
625
+ if (!selector) {
626
+ const splitTool = rawSelector ? args[2] : void 0;
627
+ if (rawSelector && splitTool && policy.servers.has(rawSelector) && mcpSessionAllowsTool(policy.servers.get(rawSelector), splitTool)) {
628
+ return {
629
+ error: `mcp call requires a dotted tool selector. Use mcp call ${rawSelector}.${splitTool} key=value or --json for structured arguments.`
630
+ };
631
+ }
632
+ return {
633
+ error: "mcp call requires an exact session tool selector: mcp call <server>.<tool> key=value ..."
634
+ };
635
+ }
636
+ if (/^(?:https?:\/\/|[^/]+\/)/i.test(selector.server)) {
637
+ return { error: "mcp call accepts only tools materialized for this recipe session; URLs and ad-hoc servers are unavailable." };
638
+ }
639
+ const targetError = validateExactTarget(policy, selector.server, selector.tool);
640
+ if (targetError) return { error: targetError };
641
+ const blocked = forbiddenFlag(args.slice(2));
642
+ if (blocked === "--args") {
643
+ return {
644
+ error: "mcp call option '--args' was removed; use --json <object|-> for structured tool arguments."
645
+ };
646
+ }
647
+ if (blocked) return { error: `mcp call option '${blocked}' is unavailable in recipe sessions.` };
648
+ const syntaxError = callSyntaxError(args.slice(2));
649
+ if (syntaxError) return { error: syntaxError };
650
+ return {
651
+ command: {
652
+ args: withNoOAuth(args)
653
+ }
654
+ };
655
+ }
656
+ function createMcpCliSessionPolicy(session) {
657
+ return {
658
+ servers: new Map(
659
+ session.servers.map((server) => [server.id, server])
660
+ )
661
+ };
662
+ }
663
+ function validateDelegatedMcpCommand(args, policy) {
664
+ if (args[0] === "list") return validateList(args, policy);
665
+ if (args[0] === "call") return validateCall(args, policy);
666
+ return {
667
+ error: args[0] === "auth" ? "Interactive authentication is unavailable in the agent MCP CLI. Ask the user to authenticate this MCP connection outside the agent session, then retry." : `mcp command '${args[0] ?? ""}' is unavailable in recipe sessions. Use mcp search, list, call, or run.`
668
+ };
669
+ }
670
+
671
+ // src/mcp-cli-core.ts
672
+ var DEFAULT_RUN_TIMEOUT_MS = 12e4;
673
+ var DEFAULT_TOOL_CALL_TIMEOUT_MS = 6e4;
674
+ var DEFAULT_LIST_TIMEOUT_MS = 3e4;
675
+ var DEFAULT_MAX_RUN_TOOL_CALLS = 100;
676
+ var DEFAULT_MAX_CONCURRENT_TOOL_CALLS = 16;
677
+ var MAX_SEARCH_DESCRIPTION_CHARS = 600;
678
+ var CATALOG_LOCK_POLL_MS = 50;
679
+ var CATALOG_LOCK_INCOMPLETE_GRACE_MS = 1e3;
680
+ var MCP_SESSION_ENV2 = "PI_RECIPES_MCP_SESSION";
681
+ async function acquireRuntime() {
682
+ const shared = currentMcpCommandContext()?.runtime;
683
+ return shared ? { runtime: shared, owned: false } : { runtime: await createRuntime(), owned: true };
684
+ }
685
+ async function closeOwnedRuntime(runtime, owned) {
686
+ if (owned) await runtime.close();
687
+ }
688
+ var McpRunUsageError = class extends Error {
689
+ };
690
+ var McpRunTimeoutError = class extends Error {
691
+ outcome = "unknown";
692
+ constructor(message) {
693
+ super(message);
694
+ this.name = "McpRunTimeoutError";
695
+ }
696
+ };
697
+ var McpRemoteToolResultError = class extends Error {
698
+ constructor(details) {
699
+ super(details.message);
700
+ this.details = details;
701
+ this.name = "McpRemoteToolResultError";
702
+ }
703
+ details;
704
+ };
705
+ var McpRunToolError = class extends Error {
706
+ constructor(server, tool, message, options) {
707
+ super(message, options);
708
+ this.server = server;
709
+ this.tool = tool;
710
+ this.name = "McpRunToolError";
711
+ this.details = options?.details;
712
+ this.code = options?.details?.code;
713
+ this.retryable = options?.details?.retryable;
714
+ this.action = options?.details?.action;
715
+ this.outcome = options?.details?.outcome;
716
+ }
717
+ server;
718
+ tool;
719
+ kind = "tool_execution";
720
+ details;
721
+ code;
722
+ retryable;
723
+ action;
724
+ outcome;
725
+ };
726
+ function mcpListHelpText() {
727
+ return [
728
+ "Usage: mcp list [server | server.tool] [flags]",
729
+ "",
730
+ "Shows a compact view of tools materialized in this recipe session.",
731
+ "",
732
+ "Flags:",
733
+ " --all-parameters Include every optional parameter.",
734
+ " --schema Show a compact input/output contract for one exact tool.",
735
+ " --verbose Include full tool and parameter descriptions.",
736
+ " --status Show concise status for an exact server target.",
737
+ " --quiet, --exit-code Health checks for an exact server target.",
738
+ " --timeout <ms> Override discovery timeout for an exact target.",
739
+ " JSON is reserved for actual tool results; metadata is compact text.",
740
+ "",
741
+ "URLs, ad-hoc transports, config overrides, and persistence are unavailable in recipe sessions."
742
+ ].join("\n");
743
+ }
744
+ function mcpCallHelpText() {
745
+ return [
746
+ "Usage: mcp call <server>.<tool> [arguments] [flags]",
747
+ "",
748
+ "Calls exact tools materialized in this recipe session.",
749
+ "",
750
+ "Arguments:",
751
+ " key=value Named arguments with schema-aware coercion.",
752
+ " key=@path Read an exact UTF-8 string; use @@ for a literal @.",
753
+ " --json <json|-> Supply a structured JSON object directly or from stdin.",
754
+ ` Array example: mcp call server.tool --json '{"tags":["a","b"]}'.`,
755
+ " Quote argument tokens containing shell operators such as |, <, >, &, or ;. JSON stdin avoids nested shell quoting.",
756
+ "",
757
+ "Output/runtime flags:",
758
+ " --output text|markdown|json|raw",
759
+ " --timeout <ms>",
760
+ " Machine-readable output is forwarded unchanged.",
761
+ " When parsing JSON, keep stderr separate and do not truncate stdout with head or sed.",
762
+ "",
763
+ "URLs, ad-hoc transports, config overrides, and persistence are unavailable in recipe sessions."
764
+ ].join("\n");
765
+ }
766
+ function mcpSearchHelpText() {
767
+ return [
768
+ 'Usage: mcp search "what you need" [--limit N] [--regex]',
769
+ "",
770
+ "Searches only MCP tools available in this session.",
771
+ "Results include the exact tool ref, description, and required fields.",
772
+ "Try broader or alternate terms when no result matches.",
773
+ "Use `mcp list <server>` only to identify exact tool names, then inspect one candidate with `mcp list <server.tool> --schema`."
774
+ ].join("\n");
775
+ }
776
+ function mcpRunHelpText() {
777
+ return [
778
+ "Usage: mcp run [--var KEY=value] [--json-errors] [file]",
779
+ "",
780
+ 'Runs a short JavaScript workflow with available MCP tools such as `tools["server"]["tool"]`.',
781
+ "With no file, code is read from stdin. Keep heredocs quoted and pass dynamic values with --var.",
782
+ "Scripts are killed after 120s by default (override with PI_RECIPES_MCP_RUN_TIMEOUT_MS).",
783
+ "Each tool call is capped at 60s; workflows allow at most 100 calls and run 16 at a time by default.",
784
+ "Extra calls wait in a FIFO queue and inherit the remaining workflow deadline.",
785
+ "Always await or return tool-call chains.",
786
+ "Detached .then/.catch calls fail if still pending when the script exits.",
787
+ "Structured MCP errors retain code, retryable, action, request_id, and outcome fields when supplied.",
788
+ "Calls return decoded JSON by default, so read response fields directly from the awaited value.",
789
+ "Only when a tool documents another response type, call the format on the tool itself: tool.text(args), tool.markdown(args), tool.images(args), tool.content(args), tool.structuredContent(args), or tool.raw(args).",
790
+ "Use Promise.all for independent reads. Await dependent calls and mutations in order.",
791
+ "Do not loop over mcp call in the shell. Use mcp run for repeated calls and print only the fields needed.",
792
+ "Use --var/vars for dynamic input; process.argv is intentionally unavailable inside workflows.",
793
+ "--json-errors emits a structured error object on stderr while preserving the nonzero exit code.",
794
+ "MCP calls are always headless. If authentication is required, ask the user to authenticate the connection outside the agent session, then retry.",
795
+ "A synchronous busy-loop is force-killed at the deadline.",
796
+ "Code runs with the same OS privileges as the active shell sandbox; mcp run is not a separate security boundary.",
797
+ "",
798
+ "Example \u2014 batch or compose multiple calls:",
799
+ " mcp run <<'JS'",
800
+ ' const ids = ["id-1", "id-2", "id-3"]',
801
+ ' const results = await Promise.all(ids.map(id => tools["server"]["tool"]({ id })))',
802
+ " console.log(JSON.stringify(results.map(result => ({ id: result.id, name: result.name })), null, 2))",
803
+ " JS"
804
+ ].join("\n");
805
+ }
806
+ function isHelpArg(value) {
807
+ return value === "--help" || value === "-h" || value === "help";
808
+ }
809
+ function readStdin() {
810
+ const source = currentMcpCommandContext()?.stdin ?? input;
811
+ source.setEncoding("utf8");
812
+ return new Promise((resolve2, reject) => {
813
+ let data = "";
814
+ source.on("data", (chunk) => {
815
+ data += chunk;
816
+ });
817
+ source.on("error", reject);
818
+ source.on("end", () => resolve2(data));
819
+ });
820
+ }
821
+ function checkedCallResult(callResult) {
822
+ if (asRecord(callResult.raw).isError === true) {
823
+ const parsed = callResult.json();
824
+ const parsedRecord = asRecord(parsed);
825
+ const parsedError = Object.prototype.hasOwnProperty.call(parsedRecord, "error") ? parsedRecord.error : parsedRecord;
826
+ const errorObject = asRecord(parsedError);
827
+ const message = typeof parsedError === "string" ? parsedError : typeof errorObject.message === "string" ? errorObject.message : callResult.text() ?? "MCP tool call failed.";
828
+ throw new McpRemoteToolResultError({ ...errorObject, message });
829
+ }
830
+ return callResult;
831
+ }
832
+ function decodeCallResult(callResult, format, ref) {
833
+ switch (format) {
834
+ case "json": {
835
+ const decoded = callResult.json();
836
+ if (decoded === null && callResult.structuredContent() == null && callResult.text() != null) {
837
+ throw new McpRunUsageError(
838
+ `${ref} did not return JSON. Its documentation should name the response type; for plain text call tools[${JSON.stringify(ref.split(".")[0])}][${JSON.stringify(ref.slice(ref.indexOf(".") + 1))}].text(args).`
839
+ );
840
+ }
841
+ return decoded;
842
+ }
843
+ case "text":
844
+ return callResult.text();
845
+ case "markdown":
846
+ return callResult.markdown();
847
+ case "images":
848
+ return callResult.images();
849
+ case "content":
850
+ return callResult.content();
851
+ case "structuredContent":
852
+ return callResult.structuredContent();
853
+ case "raw":
854
+ return callResult.raw;
855
+ }
856
+ }
857
+ function validateRunToolArgs(server, tool, args) {
858
+ const cliStyleKey = Object.keys(args).find((key) => key.includes(":=") || key.includes("="));
859
+ if (!cliStyleKey) return;
860
+ const suggestedKey = cliStyleKey.split(/:=|=/, 1)[0];
861
+ throw new McpRunUsageError(
862
+ `Invalid JavaScript argument key '${cliStyleKey}' for ${server}.${tool}. mcp run uses normal JavaScript objects: write { ${suggestedKey}: value }, not mcporter CLI key=value or key:=value syntax.`
863
+ );
864
+ }
865
+ var ToolCallQueue = class {
866
+ constructor(limit) {
867
+ this.limit = limit;
868
+ }
869
+ limit;
870
+ active = 0;
871
+ waiting = [];
872
+ cancelled;
873
+ acquire() {
874
+ if (this.cancelled !== void 0) return Promise.reject(this.cancelled);
875
+ if (this.active < this.limit) {
876
+ this.active += 1;
877
+ return Promise.resolve(this.releaseHandle());
878
+ }
879
+ return new Promise((resolve2, reject) => this.waiting.push({ resolve: resolve2, reject }));
880
+ }
881
+ cancel(error) {
882
+ if (this.cancelled !== void 0) return;
883
+ this.cancelled = error;
884
+ for (const waiter of this.waiting.splice(0)) waiter.reject(error);
885
+ }
886
+ releaseHandle() {
887
+ let released = false;
888
+ return () => {
889
+ if (released) return;
890
+ released = true;
891
+ const next = this.waiting.shift();
892
+ if (next && this.cancelled === void 0) {
893
+ next.resolve(this.releaseHandle());
894
+ return;
895
+ }
896
+ this.active -= 1;
897
+ };
898
+ }
899
+ };
900
+ function asRecord(value) {
901
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
902
+ }
903
+ function words(value) {
904
+ return value.toLowerCase().split(/[^a-z0-9_/-]+/g).map((token) => token.trim()).filter(Boolean);
905
+ }
906
+ function toolProperties(tool) {
907
+ const schema = asRecord(tool.input_schema);
908
+ const properties = asRecord(schema.properties);
909
+ return Object.entries(properties).map(([name, value]) => {
910
+ const property = asRecord(value);
911
+ const description = typeof property.description === "string" ? property.description : typeof property.title === "string" ? property.title : "";
912
+ return { name, description };
913
+ });
914
+ }
915
+ function toolRequired(tool) {
916
+ const schema = asRecord(tool.input_schema);
917
+ return Array.isArray(schema.required) ? schema.required.filter((item) => typeof item === "string") : [];
918
+ }
919
+ function includesTerm(value, term) {
920
+ return value.toLowerCase().includes(term.toLowerCase());
921
+ }
922
+ function scoreTool(opts) {
923
+ const ref = `${opts.serverId}.${opts.tool.name}`;
924
+ const description = opts.tool.description ?? "";
925
+ const properties = toolProperties(opts.tool);
926
+ const propertyNames = properties.map((property) => property.name).join(" ");
927
+ const propertyDescriptions = properties.map((property) => property.description).join(" ");
928
+ const searchable = [
929
+ ref,
930
+ opts.serverId,
931
+ opts.serverName,
932
+ opts.tool.name,
933
+ description,
934
+ propertyNames,
935
+ propertyDescriptions
936
+ ].join("\n");
937
+ if (opts.regex) return opts.regex.test(searchable) ? 100 : 0;
938
+ const query = opts.query.toLowerCase();
939
+ const toolName = opts.tool.name.toLowerCase();
940
+ const refName = ref.toLowerCase();
941
+ let score = 0;
942
+ if (refName === query) score += 500;
943
+ if (toolName === query) score += 300;
944
+ if (refName.includes(query)) score += 120;
945
+ if (toolName.includes(query)) score += 100;
946
+ if (description.toLowerCase().includes(query)) score += 60;
947
+ const toolNameTokens = new Set(words(opts.tool.name));
948
+ const serverTokens = new Set(words(`${opts.serverId} ${opts.serverName}`));
949
+ const descriptionText2 = description.toLowerCase();
950
+ const propertyNameTokens = new Set(words(propertyNames));
951
+ const propertyDescriptionText = propertyDescriptions.toLowerCase();
952
+ for (const term of opts.queryTerms) {
953
+ if (toolNameTokens.has(term)) score += 40;
954
+ else if (includesTerm(opts.tool.name, term)) score += 20;
955
+ if (descriptionText2.includes(term)) score += 16;
956
+ if (propertyNameTokens.has(term)) score += 12;
957
+ else if (includesTerm(propertyNames, term)) score += 6;
958
+ if (propertyDescriptionText.includes(term)) score += 6;
959
+ if (serverTokens.has(term)) score += 4;
960
+ }
961
+ return score;
962
+ }
963
+ function exampleValue2(name) {
964
+ if (/^(q|query|search)$/i.test(name)) return `="example query"`;
965
+ if (/limit|count|max/i.test(name)) return "=10";
966
+ if (/^(id|.*Id)$/i.test(name)) return '="<id>"';
967
+ return '="<value>"';
968
+ }
969
+ function callExample2(serverId, tool) {
970
+ const required = toolRequired(tool);
971
+ const args = required.slice(0, 4).map((name) => `${name}${exampleValue2(name)}`);
972
+ return ["mcp call", `${serverId}.${tool.name}`, ...args].join(" ");
973
+ }
974
+ function searchMcpTools(session, query, opts = {}) {
975
+ const trimmed = query.trim();
976
+ if (!trimmed) return [];
977
+ const regex = opts.regex ? new RegExp(trimmed, "i") : void 0;
978
+ const queryTerms = words(trimmed);
979
+ const matches = [];
980
+ for (const server of session.servers) {
981
+ for (const tool of server.catalog ?? []) {
982
+ const score = scoreTool({
983
+ query: trimmed,
984
+ queryTerms,
985
+ regex,
986
+ serverId: server.id,
987
+ serverName: server.name ?? "",
988
+ tool
989
+ });
990
+ if (score <= 0) continue;
991
+ const ref = `${server.id}.${tool.name}`;
992
+ matches.push({
993
+ ref,
994
+ server: server.id,
995
+ tool: tool.name,
996
+ description: (tool.description ?? "").length > MAX_SEARCH_DESCRIPTION_CHARS ? `${(tool.description ?? "").slice(0, MAX_SEARCH_DESCRIPTION_CHARS - 14).trimEnd()}\u2026 [truncated]` : tool.description ?? "",
997
+ required: toolRequired(tool),
998
+ score,
999
+ inspect: `mcp list ${ref} --schema`,
1000
+ call: callExample2(server.id, tool),
1001
+ ...tool.annotations ? { annotations: tool.annotations } : {}
1002
+ });
1003
+ }
1004
+ }
1005
+ return matches.sort((a, b) => b.score - a.score || a.ref.localeCompare(b.ref)).slice(0, opts.limit ?? 8);
1006
+ }
1007
+ async function readSession() {
1008
+ const path = sessionPolicyPath();
1009
+ const data = await readFile(path, "utf8");
1010
+ const parsed = JSON.parse(data);
1011
+ if (parsed.version !== 1 || !Array.isArray(parsed.servers)) {
1012
+ throw new Error(`Invalid MCP session policy at ${path}`);
1013
+ }
1014
+ return parsed;
1015
+ }
1016
+ function sessionPolicyPath() {
1017
+ const workspacePath = defaultMcpSessionPath(sessionRoot());
1018
+ return existsSync(workspacePath) ? workspacePath : process.env[MCP_SESSION_ENV2] || workspacePath;
1019
+ }
1020
+ function sessionMcporterConfigPath() {
1021
+ const workspacePath = defaultMcporterConfigPath(sessionRoot());
1022
+ return existsSync(workspacePath) ? workspacePath : process.env.MCPORTER_CONFIG || workspacePath;
1023
+ }
1024
+ function sessionRoot() {
1025
+ return process.env.PI_RECIPES_MCP_SESSION_ROOT || process.cwd();
1026
+ }
1027
+ function pinSessionMcporterConfig() {
1028
+ process.env.MCPORTER_CONFIG = sessionMcporterConfigPath();
1029
+ }
1030
+ async function sessionCliPolicy() {
1031
+ return createMcpCliSessionPolicy(await readSession());
1032
+ }
1033
+ function catalogFingerprint(server) {
1034
+ return createHash("sha256").update(
1035
+ JSON.stringify({
1036
+ id: server.id,
1037
+ baseUrl: server.base_url,
1038
+ packageTools: server.package_tools,
1039
+ agentTools: server.agent_tools
1040
+ })
1041
+ ).digest("hex").slice(0, 20);
1042
+ }
1043
+ function catalogCachePath(server) {
1044
+ return join2(
1045
+ sessionRoot(),
1046
+ ".pi",
1047
+ "mcp-catalogs",
1048
+ `${server.id}-${catalogFingerprint(server)}.json`
1049
+ );
1050
+ }
1051
+ async function readCachedCatalog(server) {
1052
+ if (server.catalog) return server.catalog;
1053
+ try {
1054
+ const parsed = JSON.parse(
1055
+ await readFile(catalogCachePath(server), "utf8")
1056
+ );
1057
+ return Array.isArray(parsed.tools) ? parsed.tools : null;
1058
+ } catch {
1059
+ return null;
1060
+ }
1061
+ }
1062
+ async function writeCachedCatalog(server, tools) {
1063
+ const directory = join2(sessionRoot(), ".pi", "mcp-catalogs");
1064
+ await mkdir(directory, { recursive: true });
1065
+ const path = catalogCachePath(server);
1066
+ const temporary = `${path}.${process.pid}.${randomUUID()}.tmp`;
1067
+ await writeFile(temporary, `${JSON.stringify({ tools })}
1068
+ `, { mode: 384 });
1069
+ await rename(temporary, path);
1070
+ }
1071
+ function processExists(pid) {
1072
+ try {
1073
+ process.kill(pid, 0);
1074
+ return true;
1075
+ } catch (error) {
1076
+ return error.code === "EPERM";
1077
+ }
1078
+ }
1079
+ async function catalogLockIsStale(path, timeoutMs) {
1080
+ try {
1081
+ const owner = JSON.parse(await readFile(path, "utf8"));
1082
+ if (typeof owner.pid === "number" && Number.isInteger(owner.pid) && owner.pid > 0 && typeof owner.createdAt === "number" && Number.isFinite(owner.createdAt)) {
1083
+ return !processExists(owner.pid) || Date.now() - owner.createdAt > timeoutMs + CATALOG_LOCK_INCOMPLETE_GRACE_MS;
1084
+ }
1085
+ } catch (error) {
1086
+ if (error.code === "ENOENT") return false;
1087
+ }
1088
+ try {
1089
+ const metadata = await stat(path);
1090
+ return Date.now() - metadata.mtimeMs > CATALOG_LOCK_INCOMPLETE_GRACE_MS;
1091
+ } catch {
1092
+ return false;
1093
+ }
1094
+ }
1095
+ async function acquireCatalogLock(path) {
1096
+ let handle;
1097
+ try {
1098
+ handle = await open(path, "wx", 384);
1099
+ } catch (error) {
1100
+ if (error.code === "EEXIST") return false;
1101
+ throw error;
1102
+ }
1103
+ try {
1104
+ await handle.writeFile(
1105
+ `${JSON.stringify({ pid: process.pid, createdAt: Date.now() })}
1106
+ `
1107
+ );
1108
+ } catch (error) {
1109
+ await handle.close();
1110
+ await rm(path, { force: true });
1111
+ throw error;
1112
+ }
1113
+ await handle.close();
1114
+ return true;
1115
+ }
1116
+ function catalogEntry(tool) {
1117
+ const annotations = asRecord(tool).annotations;
1118
+ return {
1119
+ name: tool.name,
1120
+ ...tool.description ? { description: tool.description } : {},
1121
+ ...tool.inputSchema && typeof tool.inputSchema === "object" ? { input_schema: tool.inputSchema } : {},
1122
+ ...tool.outputSchema && typeof tool.outputSchema === "object" ? { output_schema: tool.outputSchema } : {},
1123
+ ...annotations && typeof annotations === "object" ? { annotations } : {}
1124
+ };
1125
+ }
1126
+ async function discoverServerCatalog(runtime, server, timeoutMs) {
1127
+ const cached = await readCachedCatalog(server);
1128
+ if (cached) return cached;
1129
+ const lock = `${catalogCachePath(server)}.lock`;
1130
+ await mkdir(dirname2(lock), { recursive: true });
1131
+ let ownsLock = false;
1132
+ const deadline = Date.now() + timeoutMs;
1133
+ while (!ownsLock && Date.now() < deadline) {
1134
+ ownsLock = await acquireCatalogLock(lock);
1135
+ if (ownsLock) break;
1136
+ const populated = await readCachedCatalog(server);
1137
+ if (populated) return populated;
1138
+ if (await catalogLockIsStale(lock, timeoutMs)) {
1139
+ await rm(lock, { force: true });
1140
+ continue;
1141
+ }
1142
+ const remainingMs = deadline - Date.now();
1143
+ if (remainingMs > 0) {
1144
+ await delay(Math.min(CATALOG_LOCK_POLL_MS, remainingMs));
1145
+ }
1146
+ }
1147
+ if (!ownsLock) {
1148
+ const populated = await readCachedCatalog(server);
1149
+ if (populated) return populated;
1150
+ throw new Error(
1151
+ `timed out after ${timeoutMs}ms waiting for MCP catalog discovery for '${server.id}'`
1152
+ );
1153
+ }
1154
+ try {
1155
+ const populated = await readCachedCatalog(server);
1156
+ if (populated) return populated;
1157
+ const remainingMs = deadline - Date.now();
1158
+ if (remainingMs <= 0) {
1159
+ throw new Error(
1160
+ `timed out after ${timeoutMs}ms before MCP catalog discovery for '${server.id}' started`
1161
+ );
1162
+ }
1163
+ const discovered = await withListTimeout(
1164
+ runtime.listTools(server.id, {
1165
+ includeSchema: true,
1166
+ autoAuthorize: false,
1167
+ allowCachedAuth: true,
1168
+ disableOAuth: true
1169
+ }),
1170
+ remainingMs
1171
+ );
1172
+ const tools = filterMcpCatalog(server, discovered.map(catalogEntry));
1173
+ await writeCachedCatalog(server, tools);
1174
+ return tools;
1175
+ } finally {
1176
+ if (ownsLock) await rm(lock, { recursive: true, force: true });
1177
+ }
1178
+ }
1179
+ async function discoverMcpCatalogs(runtime, timeoutMs) {
1180
+ const session = await readSession();
1181
+ return await Promise.all(
1182
+ session.servers.map(async (server) => {
1183
+ try {
1184
+ return {
1185
+ id: server.id,
1186
+ name: server.name,
1187
+ tools: await discoverServerCatalog(runtime, server, timeoutMs)
1188
+ };
1189
+ } catch (error) {
1190
+ return {
1191
+ id: server.id,
1192
+ name: server.name,
1193
+ tools: [],
1194
+ error: error instanceof Error ? error.message : String(error)
1195
+ };
1196
+ }
1197
+ })
1198
+ );
1199
+ }
1200
+ function parseSearchArgs(args) {
1201
+ const queryParts = [];
1202
+ let limit = 8;
1203
+ let regex = false;
1204
+ for (let index = 0; index < args.length; index += 1) {
1205
+ const arg = args[index];
1206
+ if (arg === "--json") return { error: "mcp search metadata is compact text; JSON is reserved for tool results." };
1207
+ if (arg === "--regex") {
1208
+ regex = true;
1209
+ continue;
1210
+ }
1211
+ if (arg === "--limit" || arg.startsWith("--limit=")) {
1212
+ const raw = arg === "--limit" ? args[++index] : arg.slice("--limit=".length);
1213
+ const value = Number(raw);
1214
+ if (!Number.isInteger(value) || value <= 0) {
1215
+ return { error: `--limit expects a positive integer, got '${raw ?? ""}'.` };
1216
+ }
1217
+ limit = Math.floor(value);
1218
+ continue;
1219
+ }
1220
+ if (arg.startsWith("-")) return { error: `Unknown mcp search option '${arg}'.` };
1221
+ queryParts.push(arg);
1222
+ }
1223
+ return { query: queryParts.join(" "), limit, regex };
1224
+ }
1225
+ async function searchCatalog(args) {
1226
+ const parsed = parseSearchArgs(args);
1227
+ if (parsed.error !== void 0) {
1228
+ stderr.write(`${parsed.error}
1229
+ `);
1230
+ return 2;
1231
+ }
1232
+ const { query, limit, regex } = parsed;
1233
+ if (!query.trim()) {
1234
+ stderr.write('Usage: mcp search "what you need" [--limit N] [--regex]\n');
1235
+ return 2;
1236
+ }
1237
+ let matches;
1238
+ const { runtime, owned } = await acquireRuntime();
1239
+ try {
1240
+ const session = await readSession();
1241
+ const results = await Promise.all(
1242
+ session.servers.map(async (server) => {
1243
+ try {
1244
+ return {
1245
+ catalog: await discoverServerCatalog(
1246
+ runtime,
1247
+ server,
1248
+ DEFAULT_LIST_TIMEOUT_MS
1249
+ )
1250
+ };
1251
+ } catch (error) {
1252
+ return { error };
1253
+ }
1254
+ })
1255
+ );
1256
+ const failed = results.filter((result) => "error" in result).length;
1257
+ if (failed > 0) {
1258
+ stderr.write(
1259
+ `mcp search: ${failed} of ${results.length} server(s) unavailable; searched the remaining catalogs.
1260
+ `
1261
+ );
1262
+ }
1263
+ matches = searchMcpTools(
1264
+ {
1265
+ ...session,
1266
+ servers: session.servers.map((server, index) => ({
1267
+ ...server,
1268
+ catalog: "catalog" in results[index] ? results[index].catalog : []
1269
+ }))
1270
+ },
1271
+ query,
1272
+ { limit, regex }
1273
+ );
1274
+ } catch (err) {
1275
+ if (regex && err instanceof SyntaxError) {
1276
+ stderr.write(`Invalid --regex pattern: ${query}
1277
+ `);
1278
+ return 2;
1279
+ }
1280
+ throw err;
1281
+ } finally {
1282
+ await closeOwnedRuntime(runtime, owned);
1283
+ }
1284
+ if (matches.length === 0) {
1285
+ stdout.write(`No matching tools found for "${query}".
1286
+ `);
1287
+ stdout.write("Try broader or alternate terms.\n");
1288
+ stdout.write(
1289
+ "Use `mcp list <server>` only to identify exact tool names, then inspect one candidate with `mcp list <server.tool> --schema`.\n"
1290
+ );
1291
+ return 0;
1292
+ }
1293
+ for (const match of matches) {
1294
+ stdout.write(`${match.ref}${match.description ? ` \u2014 ${match.description}` : ""}
1295
+ `);
1296
+ if (match.required.length > 0) stdout.write(` required: ${match.required.join(", ")}
1297
+ `);
1298
+ }
1299
+ return 0;
1300
+ }
1301
+ function exactToolTarget(value) {
1302
+ if (!value) return null;
1303
+ const dot = value.indexOf(".");
1304
+ if (dot < 1 || dot === value.length - 1) return null;
1305
+ return { server: value.slice(0, dot), tool: value.slice(dot + 1) };
1306
+ }
1307
+ function catalogTool(session, server, tool) {
1308
+ return session.servers?.find((entry) => entry.id === server)?.catalog?.find((entry) => entry.name === tool);
1309
+ }
1310
+ function toolCount(count) {
1311
+ return `${count} tool${count === 1 ? "" : "s"}`;
1312
+ }
1313
+ function parseListTimeoutMs(args) {
1314
+ const configured = process.env.MCPORTER_LIST_TIMEOUT;
1315
+ let raw = configured && /^[1-9]\d*$/.test(configured) ? configured : String(DEFAULT_LIST_TIMEOUT_MS);
1316
+ for (let index = 1; index < args.length; index += 1) {
1317
+ const arg = args[index];
1318
+ if (arg === "--timeout") {
1319
+ const value = args[index + 1];
1320
+ if (!value) return "mcp list: --timeout requires a value.";
1321
+ raw = value;
1322
+ break;
1323
+ }
1324
+ if (arg?.startsWith("--timeout=")) {
1325
+ raw = arg.slice("--timeout=".length);
1326
+ break;
1327
+ }
1328
+ }
1329
+ if (!/^[1-9]\d*$/.test(raw)) {
1330
+ return "mcp list: --timeout must be a positive integer (milliseconds).";
1331
+ }
1332
+ const timeout = Number(raw);
1333
+ return Number.isSafeInteger(timeout) ? timeout : "mcp list: --timeout must be a positive integer (milliseconds).";
1334
+ }
1335
+ function withListTimeout(promise, timeoutMs) {
1336
+ return new Promise((resolve2, reject) => {
1337
+ const timer = setTimeout(
1338
+ () => reject(new Error(`timed out after ${timeoutMs}ms`)),
1339
+ timeoutMs
1340
+ );
1341
+ promise.then(
1342
+ (value) => {
1343
+ clearTimeout(timer);
1344
+ resolve2(value);
1345
+ },
1346
+ (error) => {
1347
+ clearTimeout(timer);
1348
+ reject(error);
1349
+ }
1350
+ );
1351
+ });
1352
+ }
1353
+ function aggregateListExitCode(hadFailure, args) {
1354
+ return hadFailure && (args.includes("--quiet") || args.includes("--exit-code")) ? 1 : 0;
1355
+ }
1356
+ function writeCompactListError(error) {
1357
+ const filter = createDelegatedErrorFilter((text) => stderr.write(text));
1358
+ const message = error instanceof Error ? error.stack ?? error.message : String(error);
1359
+ filter.push(`mcp list: ${message.endsWith("\n") ? message : `${message}
1360
+ `}`);
1361
+ filter.flush();
1362
+ }
1363
+ function compactListArgumentError(args) {
1364
+ const flags = /* @__PURE__ */ new Set([
1365
+ "--all-parameters",
1366
+ "--schema",
1367
+ "--verbose",
1368
+ "--status",
1369
+ "--quiet",
1370
+ "--exit-code",
1371
+ "--no-oauth"
1372
+ ]);
1373
+ const start = args[1] && !args[1].startsWith("-") ? 2 : 1;
1374
+ for (let index = start; index < args.length; index += 1) {
1375
+ const arg = args[index];
1376
+ if (arg === "--timeout") {
1377
+ index += 1;
1378
+ continue;
1379
+ }
1380
+ if (arg.startsWith("--timeout=")) continue;
1381
+ if (flags.has(arg)) continue;
1382
+ return arg.startsWith("-") ? `Unknown mcp list option '${arg}'.` : `Unexpected mcp list argument '${arg}'.`;
1383
+ }
1384
+ return void 0;
1385
+ }
1386
+ async function compactList(args) {
1387
+ if (args.includes("--json")) {
1388
+ stderr.write("mcp list metadata is compact text; JSON is reserved for tool results.\n");
1389
+ return 2;
1390
+ }
1391
+ const argumentError = compactListArgumentError(args);
1392
+ if (argumentError) {
1393
+ stderr.write(`${argumentError}
1394
+ `);
1395
+ return 2;
1396
+ }
1397
+ const target = args[1] && !args[1].startsWith("-") ? args[1] : void 0;
1398
+ const schema = args.includes("--schema");
1399
+ const status = args.includes("--status");
1400
+ const verbose = args.includes("--verbose");
1401
+ const allParameters = args.includes("--all-parameters");
1402
+ const quiet = args.includes("--quiet");
1403
+ const timeout = parseListTimeoutMs(args);
1404
+ if (typeof timeout === "string") {
1405
+ stderr.write(`${timeout}
1406
+ `);
1407
+ return 2;
1408
+ }
1409
+ if (schema && !target) {
1410
+ stderr.write("mcp list --schema requires one exact tool: mcp list <server>.<tool> --schema\n");
1411
+ return 2;
1412
+ }
1413
+ const session = await readSession();
1414
+ if (!target) {
1415
+ const { runtime: runtime2, owned: owned2 } = await acquireRuntime();
1416
+ try {
1417
+ const results = await Promise.all(
1418
+ session.servers.map(async (server2) => {
1419
+ try {
1420
+ const tools = await discoverServerCatalog(runtime2, server2, timeout);
1421
+ return { server: server2, tools };
1422
+ } catch (error) {
1423
+ return { server: server2, error };
1424
+ }
1425
+ })
1426
+ );
1427
+ let hadFailure = false;
1428
+ for (const result of results) {
1429
+ if ("error" in result) {
1430
+ hadFailure = true;
1431
+ if (!quiet) stdout.write(`${result.server.id} \u2014 unavailable
1432
+ `);
1433
+ } else if (!quiet) {
1434
+ stdout.write(
1435
+ `${result.server.id} \u2014 ${toolCount(result.tools.length)}
1436
+ `
1437
+ );
1438
+ }
1439
+ }
1440
+ return aggregateListExitCode(hadFailure, args);
1441
+ } finally {
1442
+ await closeOwnedRuntime(runtime2, owned2);
1443
+ }
1444
+ }
1445
+ const exact = exactToolTarget(target);
1446
+ const server = exact?.server ?? target;
1447
+ if (schema && !exact) {
1448
+ stderr.write("mcp list --schema requires one exact tool: mcp list <server>.<tool> --schema\n");
1449
+ return 2;
1450
+ }
1451
+ const { runtime, owned } = await acquireRuntime();
1452
+ try {
1453
+ const sessionServer = session.servers.find((entry) => entry.id === server);
1454
+ if (!sessionServer) {
1455
+ stderr.write(`MCP server '${server}' is not available in this session.
1456
+ `);
1457
+ return 1;
1458
+ }
1459
+ const catalog = await discoverServerCatalog(runtime, sessionServer, timeout);
1460
+ const tools = catalog.map((tool) => ({
1461
+ name: tool.name,
1462
+ description: tool.description,
1463
+ inputSchema: tool.input_schema,
1464
+ outputSchema: tool.output_schema,
1465
+ annotations: tool.annotations
1466
+ }));
1467
+ if (quiet) return 0;
1468
+ if (status) {
1469
+ stdout.write(`${server} ok \u2014 ${toolCount(tools.length)}
1470
+ `);
1471
+ return 0;
1472
+ }
1473
+ const selected = exact ? tools.filter((tool) => tool.name === exact.tool) : tools;
1474
+ if (exact && selected.length === 0) {
1475
+ stderr.write(`Tool '${exact.tool}' is not available on server '${server}'.
1476
+ `);
1477
+ return 1;
1478
+ }
1479
+ if (schema && exact) {
1480
+ const tool = selected[0];
1481
+ if (!tool) return 1;
1482
+ const outputSchema = tool.outputSchema ?? catalogOutputSchema(catalogTool(
1483
+ { ...session, servers: session.servers.map(
1484
+ (entry) => entry.id === server ? { ...entry, catalog } : entry
1485
+ ) },
1486
+ server,
1487
+ exact.tool
1488
+ ));
1489
+ stdout.write(
1490
+ renderToolContract(
1491
+ server,
1492
+ { ...tool, outputSchema },
1493
+ { verboseDescriptions: verbose }
1494
+ )
1495
+ );
1496
+ return 0;
1497
+ }
1498
+ for (const tool of selected) {
1499
+ stdout.write(`${renderToolSignature(server, tool, { allParameters })}
1500
+ `);
1501
+ if (verbose && tool.description?.trim()) {
1502
+ stdout.write(` ${tool.description.trim().replace(/\s+/g, " ")}
1503
+ `);
1504
+ }
1505
+ }
1506
+ return 0;
1507
+ } catch (error) {
1508
+ if (!quiet) writeCompactListError(error);
1509
+ return 1;
1510
+ } finally {
1511
+ await closeOwnedRuntime(runtime, owned);
1512
+ }
1513
+ }
1514
+ var PROXY_PROBE_PROPS = /* @__PURE__ */ new Set(["then", "toJSON", "constructor", "inspect"]);
1515
+ function normalizeName(value) {
1516
+ return value.replace(/[^a-z0-9]/gi, "").toLowerCase();
1517
+ }
1518
+ function levenshtein(a, b) {
1519
+ if (a === b) return 0;
1520
+ if (a.length === 0) return b.length;
1521
+ if (b.length === 0) return a.length;
1522
+ let previous = Array.from({ length: b.length + 1 }, (_, index) => index);
1523
+ for (let i = 1; i <= a.length; i += 1) {
1524
+ const current = [i];
1525
+ for (let j = 1; j <= b.length; j += 1) {
1526
+ current[j] = Math.min(
1527
+ current[j - 1] + 1,
1528
+ previous[j] + 1,
1529
+ previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1)
1530
+ );
1531
+ }
1532
+ previous = current;
1533
+ }
1534
+ return previous[b.length];
1535
+ }
1536
+ function closestName2(input2, candidates) {
1537
+ const normalizedInput = normalizeName(input2);
1538
+ let best;
1539
+ let bestScore = Number.POSITIVE_INFINITY;
1540
+ for (const candidate of candidates) {
1541
+ const score = levenshtein(normalizedInput, normalizeName(candidate));
1542
+ if (score < bestScore) {
1543
+ bestScore = score;
1544
+ best = candidate;
1545
+ }
1546
+ }
1547
+ if (best === void 0) return void 0;
1548
+ const baseline = Math.max(normalizedInput.length, normalizeName(best).length, 1);
1549
+ return bestScore <= Math.max(2, Math.floor(baseline / 3)) ? best : void 0;
1550
+ }
1551
+ function describeUnknownRunServer(server, servers) {
1552
+ const suggestion = closestName2(server, servers);
1553
+ const parts = [`Unknown MCP server '${server}'.`];
1554
+ if (suggestion) parts.push(`Did you mean 'tools.${suggestion}'?`);
1555
+ parts.push(
1556
+ servers.length > 0 ? `Available servers: ${servers.join(", ")}.` : "No MCP servers are configured."
1557
+ );
1558
+ return parts.join(" ");
1559
+ }
1560
+ var RUN_TOOL_REJECTED_PATTERNS = [
1561
+ /\btool\s+[\w.-]+\s+not found\b/i,
1562
+ /\bunknown tool\b/i
1563
+ ];
1564
+ var RUN_TOOL_BLOCKED_PATTERN = /is not accessible on server '[^']+' \(blocked by configuration\)/;
1565
+ var RUN_AUTH_REQUIRED_PATTERN = /\b(?:authentication required|unauthenticated|HTTP 401|missing (?:access )?token|invalid (?:access )?token|failed to resolve header ['"]Authorization)/i;
1566
+ function describeUnavailableRunTool(server, tool, knownTools) {
1567
+ const suggestion = closestName2(tool, knownTools);
1568
+ const parts = [`Tool '${tool}' is not available on server '${server}'.`];
1569
+ if (suggestion) parts.push(`Did you mean '${suggestion}'?`);
1570
+ parts.push(`Run \`mcp list ${server}\` to see available tools.`);
1571
+ return parts.join(" ");
1572
+ }
1573
+ function improveRunToolError(error, server, tool, knownTools) {
1574
+ const message = error instanceof Error ? error.message : String(error);
1575
+ if (RUN_AUTH_REQUIRED_PATTERN.test(message)) {
1576
+ return new McpRemoteToolResultError({
1577
+ code: "authentication_required",
1578
+ message: `Authentication is required for MCP server '${server}'. Ask the user to authenticate this MCP connection outside the agent session, then retry.`,
1579
+ retryable: false,
1580
+ action: "ask_user_to_authenticate"
1581
+ });
1582
+ }
1583
+ if (RUN_TOOL_BLOCKED_PATTERN.test(message)) {
1584
+ return new Error(
1585
+ `Tool '${tool}' is not enabled for server '${server}' in this session. Run \`mcp list ${server}\` to inspect the allowlist; if absent, the recipe configuration must grant it.`,
1586
+ { cause: error }
1587
+ );
1588
+ }
1589
+ if (!RUN_TOOL_REJECTED_PATTERNS.some((pattern) => pattern.test(message))) return error;
1590
+ return new Error(describeUnavailableRunTool(server, tool, knownTools), { cause: error });
1591
+ }
1592
+ async function createTools(opts) {
1593
+ const { runtime, owned } = await acquireRuntime();
1594
+ const session = await readSession();
1595
+ const knownTools = /* @__PURE__ */ new Map();
1596
+ await Promise.all(
1597
+ session.servers.map(async (server) => {
1598
+ knownTools.set(
1599
+ server.id,
1600
+ (await readCachedCatalog(server))?.map((tool) => tool.name) ?? []
1601
+ );
1602
+ })
1603
+ );
1604
+ const configuredServers = new Set(runtime.listServers());
1605
+ const servers = session.servers.filter(
1606
+ (server) => configuredServers.has(server.id)
1607
+ );
1608
+ const calls = [];
1609
+ let callCount = 0;
1610
+ const queue = new ToolCallQueue(opts.maxConcurrentCalls);
1611
+ const toolsByServer = /* @__PURE__ */ Object.create(null);
1612
+ for (const serverPolicy of servers) {
1613
+ const server = serverPolicy.id;
1614
+ toolsByServer[server] = new Proxy(/* @__PURE__ */ Object.create(null), {
1615
+ get(_target, property) {
1616
+ if (typeof property !== "string" || PROXY_PROBE_PROPS.has(property)) return void 0;
1617
+ const startCall = (args, format) => {
1618
+ validateRunToolArgs(server, property, args);
1619
+ const allowedToolNames = knownTools.get(server) ?? [];
1620
+ if (!mcpSessionAllowsTool(serverPolicy, property)) {
1621
+ throw new McpRunToolError(
1622
+ server,
1623
+ property,
1624
+ describeUnavailableRunTool(server, property, allowedToolNames)
1625
+ );
1626
+ }
1627
+ if (callCount >= opts.maxCalls) {
1628
+ throw new McpRunUsageError(
1629
+ `mcp run tool-call limit exceeded (${opts.maxCalls}). Split the workflow or raise PI_RECIPES_MCP_RUN_MAX_CALLS.`
1630
+ );
1631
+ }
1632
+ callCount += 1;
1633
+ const rawCall = (async () => {
1634
+ let release;
1635
+ try {
1636
+ release = await queue.acquire();
1637
+ const remainingMs = opts.deadlineMs - Date.now();
1638
+ if (remainingMs <= 0) {
1639
+ throw new McpRunTimeoutError(
1640
+ `mcp run deadline reached before ${server}.${property} started.`
1641
+ );
1642
+ }
1643
+ const result = checkedCallResult(
1644
+ createCallResult(
1645
+ await runtime.callTool(server, property, {
1646
+ args,
1647
+ // A queued call must never outlive the workflow that owns
1648
+ // it. mcporter forwards this timeout to the MCP SDK and
1649
+ // resets the transport when it fires.
1650
+ timeoutMs: Math.min(opts.callTimeoutMs, remainingMs),
1651
+ disableOAuth: true
1652
+ })
1653
+ )
1654
+ );
1655
+ return decodeCallResult(result, format, `${server}.${property}`);
1656
+ } catch (error) {
1657
+ const improved = improveRunToolError(
1658
+ error,
1659
+ server,
1660
+ property,
1661
+ allowedToolNames ?? []
1662
+ );
1663
+ if (improved instanceof McpRunUsageError) throw improved;
1664
+ const message = improved instanceof Error ? improved.message : String(improved);
1665
+ const remoteDetails = improved instanceof McpRemoteToolResultError ? improved.details : error instanceof McpRemoteToolResultError ? error.details : void 0;
1666
+ const timeoutDetails = !remoteDetails && (improved instanceof McpRunTimeoutError || /\b(?:timed?\s*out|timeout)\b/i.test(message)) ? {
1667
+ code: "timeout",
1668
+ message: improved instanceof McpRunTimeoutError ? message : `MCP tool call ${server}.${property} timed out.`,
1669
+ retryable: true,
1670
+ action: "inspect_state",
1671
+ outcome: improved instanceof McpRunTimeoutError ? "not_started" : "unknown"
1672
+ } : void 0;
1673
+ throw new McpRunToolError(
1674
+ server,
1675
+ property,
1676
+ remoteDetails?.message ?? timeoutDetails?.message ?? message,
1677
+ { cause: improved, details: remoteDetails ?? timeoutDetails }
1678
+ );
1679
+ } finally {
1680
+ release?.();
1681
+ }
1682
+ })();
1683
+ const record2 = {
1684
+ ref: `${server}.${property}`,
1685
+ observed: false,
1686
+ outcome: "pending",
1687
+ promise: rawCall
1688
+ };
1689
+ calls.push(record2);
1690
+ rawCall.then(
1691
+ () => {
1692
+ record2.outcome = "succeeded";
1693
+ },
1694
+ (error) => {
1695
+ record2.outcome = "failed";
1696
+ record2.error = error instanceof Error ? error.message : String(error);
1697
+ }
1698
+ );
1699
+ return new Proxy(rawCall, {
1700
+ get(target, key) {
1701
+ if (key === "toJSON") {
1702
+ return () => `[pending tool call ${server}.${property} \u2014 did you forget await?]`;
1703
+ }
1704
+ if (key === "then" || key === "catch" || key === "finally") {
1705
+ record2.observed = true;
1706
+ }
1707
+ const value = Reflect.get(target, key, target);
1708
+ return typeof value === "function" ? value.bind(target) : value;
1709
+ }
1710
+ });
1711
+ };
1712
+ const tool = ((args = {}) => startCall(args, "json"));
1713
+ tool.text = (args = {}) => startCall(args, "text");
1714
+ tool.markdown = (args = {}) => startCall(args, "markdown");
1715
+ tool.images = (args = {}) => startCall(args, "images");
1716
+ tool.content = (args = {}) => startCall(args, "content");
1717
+ tool.structuredContent = (args = {}) => startCall(args, "structuredContent");
1718
+ tool.raw = (args = {}) => startCall(args, "raw");
1719
+ return tool;
1720
+ }
1721
+ });
1722
+ }
1723
+ const tools = new Proxy(toolsByServer, {
1724
+ get(target, property) {
1725
+ if (typeof property !== "string" || PROXY_PROBE_PROPS.has(property)) return void 0;
1726
+ if (property in target) return target[property];
1727
+ throw new Error(
1728
+ describeUnknownRunServer(
1729
+ property,
1730
+ servers.map((server) => server.id)
1731
+ )
1732
+ );
1733
+ }
1734
+ });
1735
+ return {
1736
+ runtime,
1737
+ owned,
1738
+ tools,
1739
+ calls,
1740
+ cancelQueued: (error) => queue.cancel(error)
1741
+ };
1742
+ }
1743
+ function createVars(vars) {
1744
+ return new Proxy({ ...vars }, {
1745
+ get(target, property) {
1746
+ if (typeof property !== "string" || PROXY_PROBE_PROPS.has(property)) return void 0;
1747
+ if (property in target) return target[property];
1748
+ const defined = Object.keys(target);
1749
+ throw new Error(
1750
+ `vars.${property} is not defined. Pass it with --var ${property}=value` + (defined.length > 0 ? ` (defined vars: ${defined.join(", ")}).` : " (no vars were passed).") + ` Use \`"${property}" in vars\` to test for optional vars.`
1751
+ );
1752
+ }
1753
+ });
1754
+ }
1755
+ function createRunProcess() {
1756
+ return new Proxy(globalThis.process, {
1757
+ get(target, property) {
1758
+ if (property === "argv") {
1759
+ throw new McpRunUsageError(
1760
+ "process.argv is unavailable in mcp run. Pass dynamic values with --var KEY=value and read them as vars.KEY."
1761
+ );
1762
+ }
1763
+ const value = Reflect.get(target, property, target);
1764
+ return typeof value === "function" ? value.bind(target) : value;
1765
+ }
1766
+ });
1767
+ }
1768
+ function concurrentOutcomeError(error, calls) {
1769
+ if (calls.length < 2) return error;
1770
+ const outcomes = calls.map((call) => ({
1771
+ ref: call.ref,
1772
+ outcome: call.outcome,
1773
+ ...call.error ? { error: call.error } : {}
1774
+ }));
1775
+ const summary = outcomes.map((call) => `${call.ref}=${call.outcome}`).join(", ");
1776
+ const message = `${error.message} Workflow call outcomes: ${summary}. Inspect state before retrying side-effecting calls.`;
1777
+ return new McpRunToolError(error.server, error.tool, message, {
1778
+ cause: error,
1779
+ details: { ...error.details, message, calls: outcomes }
1780
+ });
1781
+ }
1782
+ function positiveInteger(value, label, fallback) {
1783
+ const parsed = value === void 0 ? fallback : Number(value);
1784
+ if (!Number.isSafeInteger(parsed) || parsed <= 0) {
1785
+ throw new McpRunUsageError(`${label} expects a positive integer, got '${value ?? ""}'.`);
1786
+ }
1787
+ return parsed;
1788
+ }
1789
+ async function runMcpJavaScript(code, opts = {}) {
1790
+ const timeoutMs = positiveInteger(
1791
+ opts.timeoutMs ?? process.env.PI_RECIPES_MCP_RUN_TIMEOUT_MS,
1792
+ "PI_RECIPES_MCP_RUN_TIMEOUT_MS",
1793
+ DEFAULT_RUN_TIMEOUT_MS
1794
+ );
1795
+ const callTimeoutMs = positiveInteger(
1796
+ opts.callTimeoutMs ?? process.env.PI_RECIPES_MCP_RUN_CALL_TIMEOUT_MS,
1797
+ "PI_RECIPES_MCP_RUN_CALL_TIMEOUT_MS",
1798
+ Math.min(DEFAULT_TOOL_CALL_TIMEOUT_MS, timeoutMs)
1799
+ );
1800
+ const maxCalls = positiveInteger(
1801
+ opts.maxCalls ?? process.env.PI_RECIPES_MCP_RUN_MAX_CALLS,
1802
+ "PI_RECIPES_MCP_RUN_MAX_CALLS",
1803
+ DEFAULT_MAX_RUN_TOOL_CALLS
1804
+ );
1805
+ const maxConcurrentCalls = positiveInteger(
1806
+ opts.maxConcurrentCalls ?? process.env.PI_RECIPES_MCP_RUN_MAX_CONCURRENCY,
1807
+ "PI_RECIPES_MCP_RUN_MAX_CONCURRENCY",
1808
+ DEFAULT_MAX_CONCURRENT_TOOL_CALLS
1809
+ );
1810
+ const deadlineMs = Date.now() + timeoutMs;
1811
+ const { runtime, owned, tools, calls, cancelQueued } = await createTools({
1812
+ callTimeoutMs,
1813
+ maxCalls,
1814
+ maxConcurrentCalls,
1815
+ deadlineMs
1816
+ });
1817
+ const AsyncFunction = Object.getPrototypeOf(async function() {
1818
+ }).constructor;
1819
+ const run = new AsyncFunction("tools", "vars", "process", code);
1820
+ let timeout;
1821
+ let primaryError;
1822
+ try {
1823
+ let scriptError;
1824
+ try {
1825
+ await Promise.race([
1826
+ run(tools, createVars(opts.vars ?? {}), createRunProcess()),
1827
+ new Promise((_resolve, reject) => {
1828
+ timeout = setTimeout(() => {
1829
+ reject(
1830
+ new McpRunTimeoutError(
1831
+ `mcp run timed out after ${timeoutMs}ms. Remote side effects may already have occurred; inspect state before retrying.`
1832
+ )
1833
+ );
1834
+ }, timeoutMs);
1835
+ })
1836
+ ]);
1837
+ } catch (error) {
1838
+ scriptError = error;
1839
+ }
1840
+ if (scriptError instanceof McpRunTimeoutError) {
1841
+ cancelQueued(scriptError);
1842
+ for (const call of calls) {
1843
+ if (call.outcome === "pending") call.outcome = "outcome_unknown";
1844
+ }
1845
+ throw scriptError;
1846
+ }
1847
+ const unsafeAtExit = calls.filter(
1848
+ (call) => !call.observed || call.outcome === "pending"
1849
+ );
1850
+ if (unsafeAtExit.length > 0) {
1851
+ const remainingSettleMs = Math.max(0, Math.min(15e3, deadlineMs - Date.now()));
1852
+ if (remainingSettleMs > 0) {
1853
+ let settleTimer;
1854
+ try {
1855
+ await Promise.race([
1856
+ Promise.allSettled(unsafeAtExit.map((call) => call.promise)),
1857
+ new Promise((resolve2) => {
1858
+ settleTimer = setTimeout(resolve2, remainingSettleMs);
1859
+ })
1860
+ ]);
1861
+ } finally {
1862
+ if (settleTimer) clearTimeout(settleTimer);
1863
+ }
1864
+ }
1865
+ for (const call of unsafeAtExit) {
1866
+ if (call.outcome === "pending") call.outcome = "outcome_unknown";
1867
+ }
1868
+ const remainingUnsafe = unsafeAtExit.filter(
1869
+ (call) => !call.observed || call.outcome === "outcome_unknown"
1870
+ );
1871
+ if (scriptError instanceof McpRunToolError && remainingUnsafe.length === 0) {
1872
+ throw concurrentOutcomeError(scriptError, calls);
1873
+ }
1874
+ const summary = unsafeAtExit.map(
1875
+ (call) => `${call.ref}=${call.outcome}` + (call.error ? ` (${call.error})` : "")
1876
+ ).join(", ");
1877
+ throw new McpRunUsageError(
1878
+ `mcp run: ${unsafeAtExit.length} tool call(s) were not awaited: ${summary}. Await or return every tool-call chain before the script exits; attaching .then/.catch without awaiting the chain is not enough. Remote side effects may already have occurred.` + (scriptError ? ` The script also failed: ${scriptError instanceof Error ? scriptError.message : String(scriptError)}.` : "")
1879
+ );
1880
+ }
1881
+ if (scriptError) throw scriptError;
1882
+ } catch (error) {
1883
+ primaryError = error;
1884
+ throw error;
1885
+ } finally {
1886
+ if (timeout) clearTimeout(timeout);
1887
+ try {
1888
+ await closeOwnedRuntime(runtime, owned);
1889
+ } catch (closeError) {
1890
+ if (primaryError === void 0) throw closeError;
1891
+ }
1892
+ }
1893
+ }
1894
+ var RUN_WATCHDOG_GRACE_MS = 2e3;
1895
+ function startRunWatchdog(timeoutMs) {
1896
+ const worker = new Worker(
1897
+ `const { workerData } = require("node:worker_threads");
1898
+ const { writeSync } = require("node:fs");
1899
+ setTimeout(() => {
1900
+ writeSync(
1901
+ 2,
1902
+ "mcp run: killed after " + workerData.limitMs + "ms. The script never yielded " +
1903
+ "(synchronous busy-loop?) or ignored the timeout. Use await inside loops and " +
1904
+ "set PI_RECIPES_MCP_RUN_TIMEOUT_MS to adjust the limit. Remote side effects " +
1905
+ "may already have occurred; inspect state before retrying.\\n"
1906
+ );
1907
+ process.kill(process.pid, "SIGKILL");
1908
+ }, workerData.limitMs);`,
1909
+ { eval: true, workerData: { limitMs: timeoutMs + RUN_WATCHDOG_GRACE_MS } }
1910
+ );
1911
+ worker.unref();
1912
+ return () => void worker.terminate();
1913
+ }
1914
+ async function runCode(args) {
1915
+ const vars = {};
1916
+ const positional = [];
1917
+ let jsonErrors = false;
1918
+ for (let index = 0; index < args.length; index += 1) {
1919
+ const arg = args[index];
1920
+ if (arg === "--json-errors") {
1921
+ jsonErrors = true;
1922
+ continue;
1923
+ }
1924
+ const inline = arg.startsWith("--var=") ? arg.slice("--var=".length) : void 0;
1925
+ const value = inline ?? (arg === "--var" ? args[++index] : void 0);
1926
+ if (arg === "--var" || inline !== void 0) {
1927
+ const eq = value?.indexOf("=") ?? -1;
1928
+ if (value === void 0 || eq < 1) {
1929
+ stderr.write("--var expects KEY=value.\n");
1930
+ return 2;
1931
+ }
1932
+ vars[value.slice(0, eq)] = value.slice(eq + 1);
1933
+ continue;
1934
+ }
1935
+ positional.push(arg);
1936
+ }
1937
+ const file = positional[0];
1938
+ if (positional.length > 1) {
1939
+ stderr.write("mcp run accepts at most one file path.\n");
1940
+ return 2;
1941
+ }
1942
+ const code = file ? await readFile(file, "utf8") : await readStdin();
1943
+ if (!code.trim()) {
1944
+ stderr.write(
1945
+ "mcp run: empty script. Pass a file path or pipe JavaScript on stdin (see `mcp run --help`).\n"
1946
+ );
1947
+ return 2;
1948
+ }
1949
+ let timeoutMs;
1950
+ try {
1951
+ timeoutMs = positiveInteger(
1952
+ process.env.PI_RECIPES_MCP_RUN_TIMEOUT_MS,
1953
+ "PI_RECIPES_MCP_RUN_TIMEOUT_MS",
1954
+ DEFAULT_RUN_TIMEOUT_MS
1955
+ );
1956
+ } catch (error) {
1957
+ stderr.write(`${error instanceof Error ? error.message : String(error)}
1958
+ `);
1959
+ return 2;
1960
+ }
1961
+ const stopWatchdog = isMainThread ? startRunWatchdog(timeoutMs) : () => {
1962
+ };
1963
+ try {
1964
+ await runMcpJavaScript(code, { vars, timeoutMs });
1965
+ } catch (error) {
1966
+ const message = error instanceof Error ? error.name === "Error" ? error.message : `${error.name}: ${error.message}` : String(error);
1967
+ const prefixed = message.startsWith("mcp run") ? message : `mcp run: ${message}`;
1968
+ const code2 = error instanceof McpRunUsageError ? 2 : 1;
1969
+ if (jsonErrors) {
1970
+ const remoteDetails = error instanceof McpRunToolError ? error.details : void 0;
1971
+ stderr.write(
1972
+ `${JSON.stringify({
1973
+ error: {
1974
+ ...remoteDetails,
1975
+ code: remoteDetails?.code ?? (error instanceof McpRunUsageError ? "invalid_usage" : error instanceof McpRunToolError ? "tool_execution" : error instanceof McpRunTimeoutError ? "timeout" : "script_failure"),
1976
+ message: remoteDetails?.message ?? prefixed,
1977
+ retryable: remoteDetails?.retryable ?? false,
1978
+ action: remoteDetails?.action ?? (error instanceof McpRunUsageError ? "correct_script" : error instanceof McpRunTimeoutError ? "inspect_state" : "inspect_error"),
1979
+ ...error instanceof McpRunTimeoutError ? { outcome: error.outcome } : {},
1980
+ ...error instanceof McpRunToolError ? { server: error.server, tool: error.tool } : {}
1981
+ }
1982
+ })}
1983
+ `
1984
+ );
1985
+ } else {
1986
+ stderr.write(`${prefixed}
1987
+ `);
1988
+ }
1989
+ return code2;
1990
+ } finally {
1991
+ stopWatchdog();
1992
+ }
1993
+ return 0;
1994
+ }
1995
+ function createDelegatedErrorFilter(write) {
1996
+ let buffer = "";
1997
+ let sawOAuthMetadataError = false;
1998
+ const emit = (line) => {
1999
+ if (/^\s+at\s\S/.test(line)) return;
2000
+ if (/trying to load OAuth metadata/.test(line)) sawOAuthMetadataError = true;
2001
+ const safe = line.replace(
2002
+ /Tool '([^']+)' is not accessible on server '([^']+)' \(blocked by configuration\)/g,
2003
+ "Tool '$1' is not enabled on server '$2' in this recipe session"
2004
+ ).replace(
2005
+ /Failed to resolve header 'Authorization' for server '([^']+)': Environment variable\(s\) [A-Z0-9_, ]+ must be set for MCP header substitution\./g,
2006
+ "Authentication is required for MCP server '$1'. Ask the user to authenticate this MCP connection outside the agent session, then retry."
2007
+ ).replace(
2008
+ /Next: run ['`]mcporter auth [^'`]+['`] to finish authentication\.?/gi,
2009
+ "Authentication is required. Ask the user to authenticate this MCP connection outside the agent session, then retry."
2010
+ );
2011
+ write(safe);
2012
+ };
2013
+ return {
2014
+ push(chunk) {
2015
+ buffer += chunk;
2016
+ let index = buffer.indexOf("\n");
2017
+ while (index !== -1) {
2018
+ emit(buffer.slice(0, index + 1));
2019
+ buffer = buffer.slice(index + 1);
2020
+ index = buffer.indexOf("\n");
2021
+ }
2022
+ },
2023
+ flush() {
2024
+ if (buffer) emit(buffer);
2025
+ buffer = "";
2026
+ if (sawOAuthMetadataError) {
2027
+ write(
2028
+ "Hint: this server is called with a configured bearer token; the token may be invalid or expired.\n"
2029
+ );
2030
+ sawOAuthMetadataError = false;
2031
+ }
2032
+ }
2033
+ };
2034
+ }
2035
+ function duplicateCallArgumentKeys(args) {
2036
+ const seen = /* @__PURE__ */ new Set();
2037
+ const duplicates = /* @__PURE__ */ new Set();
2038
+ for (const token of args.slice(1)) {
2039
+ const rawKey = token.match(/^([A-Za-z_][A-Za-z0-9_.-]*)=/)?.[1];
2040
+ if (!rawKey) continue;
2041
+ const key = rawKey.replace(
2042
+ /-([a-zA-Z0-9])/g,
2043
+ (_match, char) => char.toUpperCase()
2044
+ );
2045
+ if (seen.has(key)) duplicates.add(key);
2046
+ seen.add(key);
2047
+ }
2048
+ return [...duplicates];
2049
+ }
2050
+ function callJsonArgumentError(args) {
2051
+ for (let index = 1; index < args.length; index += 1) {
2052
+ const arg = args[index];
2053
+ if (arg === "--json") {
2054
+ const value = args[index + 1];
2055
+ if (value === void 0 || value.startsWith("--") && value !== "-") {
2056
+ return `mcp call: ${arg} expects a JSON object or - for stdin. Example: mcp call server.tool --json '{"tags":["a","b"]}'`;
2057
+ }
2058
+ index += 1;
2059
+ continue;
2060
+ }
2061
+ if (arg === "--json=") {
2062
+ return `mcp call: ${arg.slice(0, -1)} expects a JSON object or - for stdin.`;
2063
+ }
2064
+ }
2065
+ return null;
2066
+ }
2067
+ function malformedCallExpression(args) {
2068
+ const ref = args.find((arg) => !arg.startsWith("-"));
2069
+ if (!ref || !/[()]/.test(ref)) return null;
2070
+ return `mcp call: function-call expressions are unavailable. Use mcp call <server>.<tool> key=value or --json for structured arguments.`;
2071
+ }
2072
+ async function callWithMcporter(args) {
2073
+ const shared = currentMcpCommandContext()?.runtime;
2074
+ if (shared) return callWithSharedRuntime(shared, args);
2075
+ const previousDisableAutorun = process.env.MCPORTER_DISABLE_AUTORUN;
2076
+ const previousExitCode = process.exitCode;
2077
+ process.env.MCPORTER_DISABLE_AUTORUN = "1";
2078
+ process.exitCode = void 0;
2079
+ let runtime;
2080
+ let owned = false;
2081
+ try {
2082
+ ({ runtime, owned } = await acquireRuntime());
2083
+ const { handleCall } = await import("./cli-UV4QJZZM.js");
2084
+ await handleCall(runtime, args);
2085
+ return process.exitCode ?? 0;
2086
+ } finally {
2087
+ process.exitCode = previousExitCode;
2088
+ if (previousDisableAutorun === void 0) {
2089
+ delete process.env.MCPORTER_DISABLE_AUTORUN;
2090
+ } else {
2091
+ process.env.MCPORTER_DISABLE_AUTORUN = previousDisableAutorun;
2092
+ }
2093
+ if (runtime && owned) await runtime.close().catch(() => {
2094
+ });
2095
+ }
2096
+ }
2097
+ async function callWithSharedRuntime(runtime, args) {
2098
+ const selector = args[0];
2099
+ const separator = selector?.indexOf(".") ?? -1;
2100
+ if (!selector || separator <= 0 || separator === selector.length - 1) {
2101
+ stderr.write("mcp call requires <server>.<tool>.\n");
2102
+ return 2;
2103
+ }
2104
+ const server = selector.slice(0, separator);
2105
+ const tool = selector.slice(separator + 1);
2106
+ const values = {};
2107
+ let timeoutMs;
2108
+ let output;
2109
+ for (let index = 1; index < args.length; index += 1) {
2110
+ const token = args[index];
2111
+ if (token === "--no-oauth") continue;
2112
+ if (token === "--json" || token.startsWith("--json=")) {
2113
+ const raw2 = token === "--json" ? args[++index] : token.slice("--json=".length);
2114
+ const source = raw2 === "-" ? await readStdin() : raw2;
2115
+ if (!source) {
2116
+ stderr.write("mcp call: --json expects an object or -.\n");
2117
+ return 2;
2118
+ }
2119
+ let parsed;
2120
+ try {
2121
+ parsed = JSON.parse(source);
2122
+ } catch {
2123
+ stderr.write("mcp call: --json contains invalid JSON.\n");
2124
+ return 2;
2125
+ }
2126
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2127
+ stderr.write("mcp call: --json expects a JSON object.\n");
2128
+ return 2;
2129
+ }
2130
+ Object.assign(values, parsed);
2131
+ continue;
2132
+ }
2133
+ if (token === "--timeout" || token.startsWith("--timeout=")) {
2134
+ const raw2 = token === "--timeout" ? args[++index] : token.slice("--timeout=".length);
2135
+ const parsed = Number(raw2);
2136
+ if (!Number.isFinite(parsed) || parsed <= 0) {
2137
+ stderr.write("mcp call: --timeout expects a positive number.\n");
2138
+ return 2;
2139
+ }
2140
+ timeoutMs = parsed;
2141
+ continue;
2142
+ }
2143
+ if (token === "--output" || token.startsWith("--output=")) {
2144
+ const raw2 = token === "--output" ? args[++index] : token.slice("--output=".length);
2145
+ if (!raw2 || !["text", "markdown", "json", "raw"].includes(raw2)) {
2146
+ stderr.write("mcp call: --output expects text, markdown, json, or raw.\n");
2147
+ return 2;
2148
+ }
2149
+ output = raw2;
2150
+ continue;
2151
+ }
2152
+ const equals = token.indexOf("=");
2153
+ if (equals <= 0) {
2154
+ stderr.write(`mcp call: invalid argument '${token}'.
2155
+ `);
2156
+ return 2;
2157
+ }
2158
+ const key = token.slice(0, equals).replace(/-([a-zA-Z0-9])/g, (_match, char) => char.toUpperCase());
2159
+ let raw = token.slice(equals + 1);
2160
+ if (raw.startsWith("@@")) raw = raw.slice(1);
2161
+ else if (raw.startsWith("@")) raw = await readFile(raw.slice(1), "utf8");
2162
+ try {
2163
+ values[key] = JSON.parse(raw);
2164
+ } catch {
2165
+ values[key] = raw;
2166
+ }
2167
+ }
2168
+ try {
2169
+ const call = runtime.callTool(server, tool, {
2170
+ args: values,
2171
+ timeoutMs,
2172
+ disableOAuth: true
2173
+ });
2174
+ const signal = currentMcpCommandContext()?.signal;
2175
+ let cancelled;
2176
+ const raw = signal ? await Promise.race([
2177
+ call,
2178
+ new Promise((_resolve, reject) => {
2179
+ cancelled = () => reject(new Error("MCP command cancelled."));
2180
+ if (signal.aborted) cancelled();
2181
+ else signal.addEventListener("abort", cancelled, { once: true });
2182
+ })
2183
+ ]).finally(() => {
2184
+ if (cancelled) signal.removeEventListener("abort", cancelled);
2185
+ }) : await call;
2186
+ const result = createCallResult(raw);
2187
+ let rendered;
2188
+ if (output === "raw") rendered = raw;
2189
+ else if (output === "json") rendered = result.json();
2190
+ else if (output === "markdown") rendered = result.markdown();
2191
+ else if (output === "text") rendered = result.text();
2192
+ else rendered = result.json() ?? result.structuredContent() ?? result.text() ?? raw;
2193
+ const serialized = typeof rendered === "string" ? rendered : JSON.stringify(rendered, null, 2);
2194
+ const configuredLimit = process.env.PI_RECIPES_MCP_MAX_OUTPUT_BYTES;
2195
+ if (configuredLimit !== void 0) {
2196
+ const limit = Number(configuredLimit);
2197
+ if (!Number.isSafeInteger(limit) || limit <= 0) {
2198
+ stderr.write("PI_RECIPES_MCP_MAX_OUTPUT_BYTES must be a positive integer.\n");
2199
+ return 2;
2200
+ }
2201
+ const size = Buffer.byteLength(serialized, "utf8");
2202
+ if (size > limit) {
2203
+ stderr.write(
2204
+ `MCP result is ${size} bytes, exceeding PI_RECIPES_MCP_MAX_OUTPUT_BYTES=${limit}. Use tool pagination or a narrower field projection.
2205
+ `
2206
+ );
2207
+ return 1;
2208
+ }
2209
+ }
2210
+ stdout.write(`${serialized}
2211
+ `);
2212
+ return asRecord(raw).isError === true ? 1 : 0;
2213
+ } catch (error) {
2214
+ stderr.write(`${error instanceof Error ? error.message : String(error)}
2215
+ `);
2216
+ return 1;
2217
+ }
2218
+ }
2219
+ async function main(args = process.argv.slice(2)) {
2220
+ if (args.length === 0 || args[0] === "--help" || args[0] === "-h" || args[0] === "help" && args.length === 1) {
2221
+ stdout.write(`${mcpCliHelpText()}
2222
+ `);
2223
+ return 0;
2224
+ }
2225
+ if (args[0] === "search" && isHelpArg(args[1])) {
2226
+ stdout.write(`${mcpSearchHelpText()}
2227
+ `);
2228
+ return 0;
2229
+ }
2230
+ if (args[0] === "list" && args.slice(1).some(isHelpArg)) {
2231
+ stdout.write(`${mcpListHelpText()}
2232
+ `);
2233
+ return 0;
2234
+ }
2235
+ if (args[0] === "call" && args.slice(1).some(isHelpArg)) {
2236
+ stdout.write(`${mcpCallHelpText()}
2237
+ `);
2238
+ return 0;
2239
+ }
2240
+ if (args[0] === "run" && isHelpArg(args[1])) {
2241
+ stdout.write(`${mcpRunHelpText()}
2242
+ `);
2243
+ return 0;
2244
+ }
2245
+ pinSessionMcporterConfig();
2246
+ if (args[0] === "run") return runCode(args.slice(1));
2247
+ if (args[0] === "search") return searchCatalog(args.slice(1));
2248
+ if (args[0] === "call") {
2249
+ const malformed = malformedCallExpression(args.slice(1));
2250
+ if (malformed) {
2251
+ stderr.write(`${malformed}
2252
+ `);
2253
+ return 2;
2254
+ }
2255
+ const jsonArgumentError = callJsonArgumentError(args.slice(1));
2256
+ if (jsonArgumentError) {
2257
+ stderr.write(`${jsonArgumentError}
2258
+ `);
2259
+ return 2;
2260
+ }
2261
+ }
2262
+ let validated;
2263
+ try {
2264
+ validated = validateDelegatedMcpCommand(args, await sessionCliPolicy());
2265
+ } catch (error) {
2266
+ stderr.write(`mcp: failed to load the session MCP policy: ${error instanceof Error ? error.message : String(error)}
2267
+ `);
2268
+ return 1;
2269
+ }
2270
+ if (validated.error) {
2271
+ stderr.write(`mcp: ${validated.error}
2272
+ `);
2273
+ return 2;
2274
+ }
2275
+ if (!validated.command) {
2276
+ stderr.write("mcp: invalid session command policy result.\n");
2277
+ return 1;
2278
+ }
2279
+ if (args[0] === "call") {
2280
+ const duplicates = duplicateCallArgumentKeys(
2281
+ validated.command.args.slice(1)
2282
+ );
2283
+ if (duplicates.length > 0) {
2284
+ stderr.write(
2285
+ `mcp call: argument${duplicates.length === 1 ? "" : "s"} ${duplicates.map((key) => `'${key}'`).join(", ")} ${duplicates.length === 1 ? "was" : "were"} passed more than once. Pass each argument exactly once.
2286
+ `
2287
+ );
2288
+ return 2;
2289
+ }
2290
+ }
2291
+ const delegatedArgs = validated.command.args;
2292
+ if (delegatedArgs[0] === "list") return compactList(delegatedArgs);
2293
+ if (delegatedArgs[0] === "call") {
2294
+ return callWithMcporter(delegatedArgs.slice(1));
2295
+ }
2296
+ stderr.write(`mcp: command '${delegatedArgs[0]}' is unavailable in recipe sessions.
2297
+ `);
2298
+ return 2;
2299
+ }
2300
+ async function executeMcpCommand(opts) {
2301
+ const context = {
2302
+ runtime: opts.runtime,
2303
+ stdin: opts.stdin,
2304
+ stdout: opts.stdout,
2305
+ stderr: opts.stderr,
2306
+ signal: opts.signal
2307
+ };
2308
+ return runWithMcpCommandContext(context, () => main(opts.args));
2309
+ }
2310
+
2311
+ export {
2312
+ installMcpCommandIoRouting,
2313
+ MCP_DAEMON_SOCKET_ENV,
2314
+ MCP_DAEMON_TOKEN_ENV,
2315
+ MCP_DAEMON_FINGERPRINT_ENV,
2316
+ MCP_DAEMON_PARENT_PID_ENV,
2317
+ MCP_DAEMON_MAX_FRAME_BYTES,
2318
+ serializeMcpDaemonEnvelope,
2319
+ discoverMcpCatalogs,
2320
+ executeMcpCommand
2321
+ };