@averyyy/pi-coding-agent 1.0.0-piclient.1 → 1.0.1-piclient.1

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 (196) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +32 -7
  3. package/dist/bundle/chunks/{anthropic-messages-UID2PALB.js → anthropic-messages-OH6PVFDC.js} +3 -3
  4. package/dist/bundle/chunks/{azure-openai-responses-WK37QQ3X.js → azure-openai-responses-TXH53UDQ.js} +1 -1
  5. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  6. package/dist/bundle/chunks/{chunk-Z772OFUN.js → chunk-4ILNOXGR.js} +1 -1
  7. package/dist/bundle/chunks/{chunk-LDPPAHPJ.js → chunk-6RH3AHQA.js} +2 -2
  8. package/dist/bundle/chunks/{chunk-GIUN2MS4.js → chunk-ADXZE3T6.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-Y5O5BZGX.js → chunk-AMZHOOYQ.js} +5 -5
  10. package/dist/bundle/chunks/{chunk-2LGHNI5Y.js → chunk-B5YEEMN6.js} +43 -44
  11. package/dist/bundle/chunks/chunk-BWQQ7T6C.js +2 -0
  12. package/dist/bundle/chunks/{chunk-7JGR3GZN.js → chunk-F42GKQ7R.js} +1 -1
  13. package/dist/bundle/chunks/chunk-F5ZABL6Z.js +20 -0
  14. package/dist/bundle/chunks/{chunk-CWVWZD3J.js → chunk-JTLRGL3E.js} +1 -1
  15. package/dist/bundle/chunks/{chunk-NEUJZQL3.js → chunk-KDCQ3NIW.js} +4 -4
  16. package/dist/bundle/chunks/chunk-NDRYPTF3.js +82 -0
  17. package/dist/bundle/chunks/{chunk-OSKPX3Z5.js → chunk-PXGUCLIP.js} +1 -1
  18. package/dist/bundle/chunks/{chunk-KYK7L7MP.js → chunk-TQURZFKG.js} +2 -2
  19. package/dist/bundle/chunks/{chunk-6D7LHLKG.js → chunk-WGS2XTDX.js} +1 -1
  20. package/dist/bundle/chunks/chunk-WZXBGVMV.js +2 -0
  21. package/dist/bundle/chunks/{cli-JE36AHMI.js → cli-PWPIOM3F.js} +2 -2
  22. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-NPQN263E.js → cloudflare-workers-ai-system-one-GOPEBKZD.js} +1 -1
  23. package/dist/bundle/chunks/codemode-worker.js +24 -3
  24. package/dist/bundle/chunks/easter-egg-3d-SZ6NEDZW.js +2 -0
  25. package/dist/bundle/chunks/{execute-UX73LR4F.js → execute-7OKZOXCP.js} +25 -4
  26. package/dist/bundle/chunks/{google-generative-ai-OHRSHIE7.js → google-generative-ai-XQU2LTWM.js} +1 -1
  27. package/dist/bundle/chunks/{google-vertex-KXEB3MVO.js → google-vertex-F6Q2CN55.js} +1 -1
  28. package/dist/bundle/chunks/{mistral-conversations-DKVFNQL6.js → mistral-conversations-XAJWHQFR.js} +1 -1
  29. package/dist/bundle/chunks/node-N5BSDOA5.js +16 -0
  30. package/dist/bundle/chunks/openai-chatgpt.js +1 -1
  31. package/dist/bundle/chunks/{openai-codex-responses-WVERB76N.js → openai-codex-responses-3EHOMZYM.js} +1 -1
  32. package/dist/bundle/chunks/{openai-completions-JXDDPZ23.js → openai-completions-QC2IQHSC.js} +1 -1
  33. package/dist/bundle/chunks/{openai-responses-WDT74L23.js → openai-responses-JPLD2GJS.js} +1 -1
  34. package/dist/bundle/chunks/{runtime-MPXPM7VL.js → runtime-XC47QHT2.js} +1 -1
  35. package/dist/bundle/chunks/{typesafe-system-one-A4CZTQWZ.js → typesafe-system-one-7DJTDUKY.js} +1 -1
  36. package/dist/bundle/chunks/virtual-modules-TA3HGAAA.js +2 -0
  37. package/dist/bundle/cli-runtime.js +1 -1
  38. package/dist/bundle/index.js +1 -1
  39. package/dist/bundle/rpc-entry.js +1 -1
  40. package/dist/cli/args.d.ts.map +1 -1
  41. package/dist/cli/args.js +4 -1
  42. package/dist/cli/args.js.map +1 -1
  43. package/dist/config.d.ts.map +1 -1
  44. package/dist/config.js +3 -0
  45. package/dist/config.js.map +1 -1
  46. package/dist/core/agent-session.d.ts.map +1 -1
  47. package/dist/core/agent-session.js +30 -3
  48. package/dist/core/agent-session.js.map +1 -1
  49. package/dist/core/cache-warmer.d.ts.map +1 -1
  50. package/dist/core/cache-warmer.js +11 -3
  51. package/dist/core/cache-warmer.js.map +1 -1
  52. package/dist/core/export-html/tool-renderer.d.ts +3 -3
  53. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  54. package/dist/core/export-html/tool-renderer.js +3 -3
  55. package/dist/core/export-html/tool-renderer.js.map +1 -1
  56. package/dist/core/extensions/index.d.ts +1 -1
  57. package/dist/core/extensions/index.d.ts.map +1 -1
  58. package/dist/core/extensions/index.js.map +1 -1
  59. package/dist/core/extensions/loader.d.ts.map +1 -1
  60. package/dist/core/extensions/loader.js +5 -0
  61. package/dist/core/extensions/loader.js.map +1 -1
  62. package/dist/core/extensions/runner.d.ts +3 -1
  63. package/dist/core/extensions/runner.d.ts.map +1 -1
  64. package/dist/core/extensions/runner.js +6 -0
  65. package/dist/core/extensions/runner.js.map +1 -1
  66. package/dist/core/extensions/types.d.ts +9 -0
  67. package/dist/core/extensions/types.d.ts.map +1 -1
  68. package/dist/core/extensions/types.js.map +1 -1
  69. package/dist/core/mcp-servers.d.ts +6 -0
  70. package/dist/core/mcp-servers.d.ts.map +1 -1
  71. package/dist/core/mcp-servers.js +11 -0
  72. package/dist/core/mcp-servers.js.map +1 -1
  73. package/dist/core/model-resolver.js +2 -2
  74. package/dist/core/model-resolver.js.map +1 -1
  75. package/dist/core/model-runtime.d.ts +16 -0
  76. package/dist/core/model-runtime.d.ts.map +1 -1
  77. package/dist/core/model-runtime.js +325 -31
  78. package/dist/core/model-runtime.js.map +1 -1
  79. package/dist/core/pi-server-client.d.ts +21 -1
  80. package/dist/core/pi-server-client.d.ts.map +1 -1
  81. package/dist/core/pi-server-client.js +308 -22
  82. package/dist/core/pi-server-client.js.map +1 -1
  83. package/dist/core/pi-server-protocol.d.ts +12 -1
  84. package/dist/core/pi-server-protocol.d.ts.map +1 -1
  85. package/dist/core/pi-server-protocol.js +1 -0
  86. package/dist/core/pi-server-protocol.js.map +1 -1
  87. package/dist/core/sdk.d.ts.map +1 -1
  88. package/dist/core/sdk.js +51 -18
  89. package/dist/core/sdk.js.map +1 -1
  90. package/dist/core/tools/renderers/index.d.ts +2 -2
  91. package/dist/core/tools/renderers/index.d.ts.map +1 -1
  92. package/dist/core/tools/renderers/index.js.map +1 -1
  93. package/dist/extensions/mcp/cli.d.ts.map +1 -1
  94. package/dist/extensions/mcp/cli.js +3 -0
  95. package/dist/extensions/mcp/cli.js.map +1 -1
  96. package/dist/extensions/mcp/config.d.ts +15 -3
  97. package/dist/extensions/mcp/config.d.ts.map +1 -1
  98. package/dist/extensions/mcp/config.js +46 -10
  99. package/dist/extensions/mcp/config.js.map +1 -1
  100. package/dist/extensions/mcp/index.d.ts +4 -1
  101. package/dist/extensions/mcp/index.d.ts.map +1 -1
  102. package/dist/extensions/mcp/index.js +80 -45
  103. package/dist/extensions/mcp/index.js.map +1 -1
  104. package/dist/extensions/mcp/oauth.d.ts +4 -1
  105. package/dist/extensions/mcp/oauth.d.ts.map +1 -1
  106. package/dist/extensions/mcp/oauth.js +54 -10
  107. package/dist/extensions/mcp/oauth.js.map +1 -1
  108. package/dist/extensions/mcp/runtime.d.ts.map +1 -1
  109. package/dist/extensions/mcp/runtime.js +1 -0
  110. package/dist/extensions/mcp/runtime.js.map +1 -1
  111. package/dist/extensions/mcp/tools.d.ts +3 -1
  112. package/dist/extensions/mcp/tools.d.ts.map +1 -1
  113. package/dist/extensions/mcp/tools.js +19 -13
  114. package/dist/extensions/mcp/tools.js.map +1 -1
  115. package/dist/extensions/mcp/ui.d.ts.map +1 -1
  116. package/dist/extensions/mcp/ui.js +8 -4
  117. package/dist/extensions/mcp/ui.js.map +1 -1
  118. package/dist/index.d.ts +1 -1
  119. package/dist/index.d.ts.map +1 -1
  120. package/dist/index.js.map +1 -1
  121. package/dist/main.d.ts.map +1 -1
  122. package/dist/main.js +24 -17
  123. package/dist/main.js.map +1 -1
  124. package/dist/modes/interactive/components/armin.d.ts +3 -0
  125. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  126. package/dist/modes/interactive/components/armin.js +7 -5
  127. package/dist/modes/interactive/components/armin.js.map +1 -1
  128. package/dist/modes/interactive/components/auth-url.d.ts +14 -0
  129. package/dist/modes/interactive/components/auth-url.d.ts.map +1 -0
  130. package/dist/modes/interactive/components/auth-url.js +37 -0
  131. package/dist/modes/interactive/components/auth-url.js.map +1 -0
  132. package/dist/modes/interactive/components/easter-egg-3d.d.ts +104 -0
  133. package/dist/modes/interactive/components/easter-egg-3d.d.ts.map +1 -0
  134. package/dist/modes/interactive/components/{pi-logo-animation.js → easter-egg-3d.js} +127 -74
  135. package/dist/modes/interactive/components/easter-egg-3d.js.map +1 -0
  136. package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts +6 -0
  137. package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts.map +1 -0
  138. package/dist/modes/interactive/components/easter-egg-3d.lazy.js +23 -0
  139. package/dist/modes/interactive/components/easter-egg-3d.lazy.js.map +1 -0
  140. package/dist/modes/interactive/components/index.d.ts +0 -1
  141. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  142. package/dist/modes/interactive/components/index.js +0 -1
  143. package/dist/modes/interactive/components/index.js.map +1 -1
  144. package/dist/modes/interactive/components/login-dialog.d.ts +2 -0
  145. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  146. package/dist/modes/interactive/components/login-dialog.js +11 -5
  147. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  148. package/dist/modes/interactive/components/tool-execution.d.ts +6 -18
  149. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  150. package/dist/modes/interactive/components/tool-execution.js +22 -45
  151. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  152. package/dist/modes/interactive/interactive-mode.d.ts +2 -2
  153. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  154. package/dist/modes/interactive/interactive-mode.js +15 -17
  155. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  156. package/dist/package-manager-cli.d.ts.map +1 -1
  157. package/dist/package-manager-cli.js +12 -0
  158. package/dist/package-manager-cli.js.map +1 -1
  159. package/dist/utils/image-convert.d.ts +12 -0
  160. package/dist/utils/image-convert.d.ts.map +1 -1
  161. package/dist/utils/image-convert.js +45 -6
  162. package/dist/utils/image-convert.js.map +1 -1
  163. package/docs/cli.md +2 -0
  164. package/docs/codemode.md +1 -1
  165. package/docs/extensions.md +4 -0
  166. package/docs/keybindings.md +1 -1
  167. package/docs/mcp.md +23 -1
  168. package/docs/models.md +2 -2
  169. package/docs/quickstart.md +16 -2
  170. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  171. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  172. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  173. package/examples/extensions/gondolin/package-lock.json +2 -2
  174. package/examples/extensions/gondolin/package.json +1 -1
  175. package/examples/extensions/sandbox/package-lock.json +2 -2
  176. package/examples/extensions/sandbox/package.json +1 -1
  177. package/examples/extensions/with-deps/package-lock.json +2 -2
  178. package/examples/extensions/with-deps/package.json +1 -1
  179. package/package.json +13 -14
  180. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +0 -42
  181. package/dist/bundle/chunks/chunk-OI6YFHHK.js +0 -82
  182. package/dist/bundle/chunks/chunk-SQXKOUF6.js +0 -2
  183. package/dist/bundle/chunks/node-O3RIBJRH.js +0 -16
  184. package/dist/bundle/chunks/pi-logo-animation-APS4JS6B.js +0 -2
  185. package/dist/bundle/chunks/virtual-modules-XLXFDFIX.js +0 -2
  186. package/dist/modes/interactive/components/daxnuts.d.ts +0 -23
  187. package/dist/modes/interactive/components/daxnuts.d.ts.map +0 -1
  188. package/dist/modes/interactive/components/daxnuts.js +0 -140
  189. package/dist/modes/interactive/components/daxnuts.js.map +0 -1
  190. package/dist/modes/interactive/components/pi-logo-animation.d.ts +0 -68
  191. package/dist/modes/interactive/components/pi-logo-animation.d.ts.map +0 -1
  192. package/dist/modes/interactive/components/pi-logo-animation.js.map +0 -1
  193. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts +0 -7
  194. package/dist/modes/interactive/components/pi-logo-animation.lazy.d.ts.map +0 -1
  195. package/dist/modes/interactive/components/pi-logo-animation.lazy.js +0 -12
  196. package/dist/modes/interactive/components/pi-logo-animation.lazy.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAKH,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,eAAe,EAGpB,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EACX,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,oBAAoB,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,CAAC;IACxB,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;CAC3C;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB;AAgDD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,eAAe,CASlH;AAED,+GAA+G;AAC/G,MAAM,WAAW,oBAAoB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACvB;AAED;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,oBAAoB,GAAG,IAAI,CAcnG;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAU/F;AAED,0FAA0F;AAC1F,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAUzE"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAKH,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,eAAe,EAGpB,MAAM,2BAA2B,CAAC;AAEnC,YAAY,EACX,WAAW,EACX,mBAAmB,EACnB,cAAc,EACd,eAAe,EACf,oBAAoB,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,MAAM,WAAW,cAAc;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,eAAe,CAAC;IACxB,2FAA2F;IAC3F,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,CAAC;IAC3C,4GAA4G;IAC5G,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,gGAAgG;IAChG,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AAqED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,cAAc,EAAE,OAAO,CAAA;CAAE,GAAG,eAAe,CAWlH;AAED,+GAA+G;AAC/G,MAAM,WAAW,oBAAoB;IACpC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,WAAW,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CACpC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,oBAAoB,EAC3B,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAClC,IAAI,CAmBN;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,OAAO,CAU/F;AAED,0FAA0F;AAC1F,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAUzE"}
@@ -6,6 +6,11 @@
6
6
  * existing configurations can be copied over. Project entries replace global entries with the
7
7
  * same name.
8
8
  *
9
+ * A project entry without `command`, `url`, or `type` overrides only `enabled`, `exposure`, and
10
+ * `toolExposure` of the global server with the same name, for example to turn it off in one project:
11
+ * `{ "mcpServers": { "internal-tools": { "enabled": false } } }`. The rest of the global entry is kept,
12
+ * including credentials the project could not set itself.
13
+ *
9
14
  * ```json
10
15
  * {
11
16
  * "mcpServers": {
@@ -28,6 +33,11 @@ import { dirname, join } from "node:path";
28
33
  import { CONFIG_DIR_NAME } from "../../config.js";
29
34
  import { mcpNamespace, validateMcpServerConfig, } from "../../core/mcp-servers.js";
30
35
  export { getMcpToolExposure } from "../../core/mcp-servers.js";
36
+ const OVERRIDE_KEYS = ["enabled", "exposure", "toolExposure"];
37
+ /** Whether an entry overrides a server defined elsewhere instead of defining one. */
38
+ function isOverride(value) {
39
+ return value.command === undefined && value.url === undefined && value.type === undefined;
40
+ }
31
41
  function isRecord(value) {
32
42
  return typeof value === "object" && value !== null && !Array.isArray(value);
33
43
  }
@@ -52,6 +62,24 @@ function readConfigFile(path, scope, state) {
52
62
  else if (parsed.autoEnableCodemode !== undefined)
53
63
  errors.push(`${path}: autoEnableCodemode must be a boolean`);
54
64
  for (const [name, value] of Object.entries(parsed.mcpServers ?? {})) {
65
+ if (scope === "project" && isRecord(value) && isOverride(value)) {
66
+ const base = servers.get(name);
67
+ const extra = Object.keys(value).filter((key) => !OVERRIDE_KEYS.includes(key));
68
+ if (!base) {
69
+ errors.push(`${path}: server "${name}" needs "command" or "url", or a global server to override`);
70
+ }
71
+ else if (extra.length > 0) {
72
+ errors.push(`${path}: server "${name}": an override can only set ${OVERRIDE_KEYS.join(", ")}`);
73
+ }
74
+ else {
75
+ const config = validateMcpServerConfig(name, { ...base.config, ...value });
76
+ if (typeof config === "string")
77
+ errors.push(`${path}: ${config}`);
78
+ else
79
+ servers.set(name, { ...base, config, override: path });
80
+ }
81
+ continue;
82
+ }
55
83
  const config = validateMcpServerConfig(name, value);
56
84
  if (typeof config === "string") {
57
85
  errors.push(`${path}: ${config}`);
@@ -77,31 +105,39 @@ function readConfigFile(path, scope, state) {
77
105
  export function loadMcpConfig(options) {
78
106
  const state = { servers: new Map(), errors: [] };
79
107
  readConfigFile(join(options.agentDir, "mcp.json"), "global", state);
80
- if (options.projectTrusted)
81
- readConfigFile(join(options.cwd, CONFIG_DIR_NAME, "mcp.json"), "project", state);
108
+ const projectConfig = options.projectTrusted ? join(options.cwd, CONFIG_DIR_NAME, "mcp.json") : undefined;
109
+ if (projectConfig)
110
+ readConfigFile(projectConfig, "project", state);
82
111
  return {
83
112
  servers: [...state.servers.values()],
84
113
  ...(state.autoEnableCodemode === undefined ? {} : { autoEnableCodemode: state.autoEnableCodemode }),
85
114
  errors: state.errors,
115
+ ...(projectConfig ? { projectConfig } : {}),
86
116
  };
87
117
  }
88
118
  /**
89
- * Change one server's settings in the `mcp.json` that defines it. Other content is kept; the file is
90
- * rewritten with its indentation.
119
+ * Change one server's settings in the `mcp.json` that defines or overrides it. With `override`, a
120
+ * missing entry is added as an override. Overrides keep default values, since they replace the global
121
+ * server's. Other content is kept; the file is rewritten with its indentation.
91
122
  */
92
- export function updateMcpServerConfig(path, name, patch) {
93
- editMcpServers(path, (servers) => {
94
- const server = servers?.[name];
123
+ export function updateMcpServerConfig(path, name, patch, options = {}) {
124
+ editMcpServers(path, (servers, parsed) => {
125
+ let server = servers?.[name];
126
+ if (server === undefined && options.override) {
127
+ server = {};
128
+ parsed.mcpServers = { ...servers, [name]: server };
129
+ }
95
130
  if (!isRecord(server))
96
131
  throw new Error(`${path} does not define MCP server "${name}"`);
132
+ const keepDefaults = isOverride(server);
97
133
  if (patch.enabled !== undefined) {
98
- if (patch.enabled)
134
+ if (patch.enabled && !keepDefaults)
99
135
  delete server.enabled;
100
136
  else
101
- server.enabled = false;
137
+ server.enabled = patch.enabled;
102
138
  }
103
139
  if (patch.exposure !== undefined) {
104
- if (patch.exposure === "codemode")
140
+ if (patch.exposure === "codemode" && !keepDefaults)
105
141
  delete server.exposure;
106
142
  else
107
143
  server.exposure = patch.exposure;
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/extensions/mcp/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP server configuration.\n *\n * Servers are read from `mcp.json` in the agent directory and, for trusted projects, from\n * `<project>/.pi/mcp.json`. Both use the `mcpServers` shape shared by other MCP clients, so\n * existing configurations can be copied over. Project entries replace global entries with the\n * same name.\n *\n * ```json\n * {\n * \"mcpServers\": {\n * \"filesystem\": { \"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol/server-filesystem\", \".\"] },\n * \"docs\": { \"url\": \"https://example.com/mcp\", \"headers\": { \"Authorization\": \"Bearer ${DOCS_TOKEN}\" } },\n * \"sentry\": { \"url\": \"https://mcp.sentry.dev/mcp\" }\n * }\n * }\n * ```\n *\n * HTTP servers without an `Authorization` header use OAuth when they answer 401 (sign in with `/mcp`).\n * `\"auth\": { \"provider\": \"<provider>\" }` sends the token of a `/login` provider instead. Project files\n * cannot use it, so a repository cannot pick where the credential goes.\n *\n * The top-level `autoEnableCodemode` (default true) activates the codemode tool when a server\n * with `codemode` exposure connects. A project value overrides the global one.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { CONFIG_DIR_NAME } from \"../../config.ts\";\nimport {\n\ttype McpExposure,\n\ttype McpServerConfig,\n\tmcpNamespace,\n\tvalidateMcpServerConfig,\n} from \"../../core/mcp-servers.ts\";\n\nexport type {\n\tMcpExposure,\n\tMcpHttpServerConfig,\n\tMcpOAuthConfig,\n\tMcpServerConfig,\n\tMcpStdioServerConfig,\n} from \"../../core/mcp-servers.ts\";\nexport { getMcpToolExposure } from \"../../core/mcp-servers.ts\";\n\nexport interface McpServerEntry {\n\tname: string;\n\tconfig: McpServerConfig;\n\t/** Config file that defined the entry, or the path of the extension that registered it. */\n\tsource: string;\n\t/**\n\t * The global or the project `mcp.json`, or `extension` for servers registered with\n\t * `pi.registerMcpServer()`. Changes to extension servers are not saved.\n\t */\n\tscope?: \"global\" | \"project\" | \"extension\";\n}\n\nexport interface LoadedMcpConfig {\n\tservers: McpServerEntry[];\n\t/** Activate the codemode tool when `codemode` servers connect. Default: true. */\n\tautoEnableCodemode?: boolean;\n\terrors: string[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\ninterface McpConfigState {\n\tservers: Map<string, McpServerEntry>;\n\tautoEnableCodemode?: boolean;\n\terrors: string[];\n}\n\nfunction readConfigFile(path: string, scope: \"global\" | \"project\", state: McpConfigState): void {\n\tconst { servers, errors } = state;\n\tif (!existsSync(path)) return;\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(readFileSync(path, \"utf8\"));\n\t} catch (error) {\n\t\terrors.push(`${path}: ${error instanceof Error ? error.message : String(error)}`);\n\t\treturn;\n\t}\n\tif (!isRecord(parsed) || (parsed.mcpServers !== undefined && !isRecord(parsed.mcpServers))) {\n\t\terrors.push(`${path}: expected an object with an \"mcpServers\" object`);\n\t\treturn;\n\t}\n\tif (typeof parsed.autoEnableCodemode === \"boolean\") state.autoEnableCodemode = parsed.autoEnableCodemode;\n\telse if (parsed.autoEnableCodemode !== undefined) errors.push(`${path}: autoEnableCodemode must be a boolean`);\n\tfor (const [name, value] of Object.entries(parsed.mcpServers ?? {})) {\n\t\tconst config = validateMcpServerConfig(name, value);\n\t\tif (typeof config === \"string\") {\n\t\t\terrors.push(`${path}: ${config}`);\n\t\t\tcontinue;\n\t\t}\n\t\t// Names that differ only in `-` and `_` would share a namespace.\n\t\tconst clash = [...servers.keys()].find((other) => other !== name && mcpNamespace(other) === mcpNamespace(name));\n\t\tif (clash) {\n\t\t\terrors.push(`${path}: server \"${name}\" conflicts with \"${clash}\"`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (scope === \"project\" && \"url\" in config && config.auth) {\n\t\t\terrors.push(`${path}: server \"${name}\": auth is only allowed in the global mcp.json`);\n\t\t\tcontinue;\n\t\t}\n\t\tservers.set(name, { name, config, source: path, scope });\n\t}\n}\n\n/**\n * Load global and (when trusted) project MCP configuration. Disabled servers are included with\n * `enabled: false`, so they can be enabled again.\n */\nexport function loadMcpConfig(options: { agentDir: string; cwd: string; projectTrusted: boolean }): LoadedMcpConfig {\n\tconst state: McpConfigState = { servers: new Map(), errors: [] };\n\treadConfigFile(join(options.agentDir, \"mcp.json\"), \"global\", state);\n\tif (options.projectTrusted) readConfigFile(join(options.cwd, CONFIG_DIR_NAME, \"mcp.json\"), \"project\", state);\n\treturn {\n\t\tservers: [...state.servers.values()],\n\t\t...(state.autoEnableCodemode === undefined ? {} : { autoEnableCodemode: state.autoEnableCodemode }),\n\t\terrors: state.errors,\n\t};\n}\n\n/** Settings `/mcp` changes. `enabled: true` and `exposure: \"codemode\"` are the defaults and remove the key. */\nexport interface McpServerConfigPatch {\n\tenabled?: boolean;\n\texposure?: McpExposure;\n}\n\n/**\n * Change one server's settings in the `mcp.json` that defines it. Other content is kept; the file is\n * rewritten with its indentation.\n */\nexport function updateMcpServerConfig(path: string, name: string, patch: McpServerConfigPatch): void {\n\teditMcpServers(path, (servers) => {\n\t\tconst server = servers?.[name];\n\t\tif (!isRecord(server)) throw new Error(`${path} does not define MCP server \"${name}\"`);\n\t\tif (patch.enabled !== undefined) {\n\t\t\tif (patch.enabled) delete server.enabled;\n\t\t\telse server.enabled = false;\n\t\t}\n\t\tif (patch.exposure !== undefined) {\n\t\t\tif (patch.exposure === \"codemode\") delete server.exposure;\n\t\t\telse server.exposure = patch.exposure;\n\t\t}\n\t\treturn true;\n\t});\n}\n\n/**\n * Add a server to an `mcp.json`, creating the file when missing. An existing entry with the same\n * name is replaced. Returns true when an entry was replaced.\n */\nexport function addMcpServerConfig(path: string, name: string, config: McpServerConfig): boolean {\n\tlet replaced = false;\n\teditMcpServers(path, (servers, parsed) => {\n\t\tconst target = servers ?? {};\n\t\treplaced = target[name] !== undefined;\n\t\ttarget[name] = config;\n\t\tparsed.mcpServers = target;\n\t\treturn true;\n\t});\n\treturn replaced;\n}\n\n/** Remove a server from an `mcp.json`. Returns false when the file does not define it. */\nexport function removeMcpServerConfig(path: string, name: string): boolean {\n\tif (!existsSync(path)) return false;\n\tlet removed = false;\n\teditMcpServers(path, (servers) => {\n\t\tif (!servers || servers[name] === undefined) return false;\n\t\tdelete servers[name];\n\t\tremoved = true;\n\t\treturn true;\n\t});\n\treturn removed;\n}\n\n/**\n * Read an `mcp.json` (an empty config when missing), let `edit` change its `mcpServers`, and write\n * it back with its indentation when `edit` returns true. Other content is kept.\n */\nfunction editMcpServers(\n\tpath: string,\n\tedit: (servers: Record<string, unknown> | undefined, parsed: Record<string, unknown>) => boolean,\n): void {\n\tconst text = existsSync(path) ? readFileSync(path, \"utf8\") : undefined;\n\tconst parsed: unknown = text === undefined ? {} : JSON.parse(text);\n\tif (!isRecord(parsed) || (parsed.mcpServers !== undefined && !isRecord(parsed.mcpServers))) {\n\t\tthrow new Error(`${path}: expected an object with an \"mcpServers\" object`);\n\t}\n\tconst servers = isRecord(parsed.mcpServers) ? parsed.mcpServers : undefined;\n\tif (!edit(servers, parsed)) return;\n\tconst indent = (text && /^([ \\t]+)\\S/m.exec(text)?.[1]) || \" \";\n\tmkdirSync(dirname(path), { recursive: true });\n\twriteFileSync(path, `${JSON.stringify(parsed, null, indent)}\\n`);\n}\n"]}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../src/extensions/mcp/config.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * MCP server configuration.\n *\n * Servers are read from `mcp.json` in the agent directory and, for trusted projects, from\n * `<project>/.pi/mcp.json`. Both use the `mcpServers` shape shared by other MCP clients, so\n * existing configurations can be copied over. Project entries replace global entries with the\n * same name.\n *\n * A project entry without `command`, `url`, or `type` overrides only `enabled`, `exposure`, and\n * `toolExposure` of the global server with the same name, for example to turn it off in one project:\n * `{ \"mcpServers\": { \"internal-tools\": { \"enabled\": false } } }`. The rest of the global entry is kept,\n * including credentials the project could not set itself.\n *\n * ```json\n * {\n * \"mcpServers\": {\n * \"filesystem\": { \"command\": \"npx\", \"args\": [\"-y\", \"@modelcontextprotocol/server-filesystem\", \".\"] },\n * \"docs\": { \"url\": \"https://example.com/mcp\", \"headers\": { \"Authorization\": \"Bearer ${DOCS_TOKEN}\" } },\n * \"sentry\": { \"url\": \"https://mcp.sentry.dev/mcp\" }\n * }\n * }\n * ```\n *\n * HTTP servers without an `Authorization` header use OAuth when they answer 401 (sign in with `/mcp`).\n * `\"auth\": { \"provider\": \"<provider>\" }` sends the token of a `/login` provider instead. Project files\n * cannot use it, so a repository cannot pick where the credential goes.\n *\n * The top-level `autoEnableCodemode` (default true) activates the codemode tool when a server\n * with `codemode` exposure connects. A project value overrides the global one.\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { CONFIG_DIR_NAME } from \"../../config.ts\";\nimport {\n\ttype McpExposure,\n\ttype McpServerConfig,\n\tmcpNamespace,\n\tvalidateMcpServerConfig,\n} from \"../../core/mcp-servers.ts\";\n\nexport type {\n\tMcpExposure,\n\tMcpHttpServerConfig,\n\tMcpOAuthConfig,\n\tMcpServerConfig,\n\tMcpStdioServerConfig,\n} from \"../../core/mcp-servers.ts\";\nexport { getMcpToolExposure } from \"../../core/mcp-servers.ts\";\n\nexport interface McpServerEntry {\n\tname: string;\n\tconfig: McpServerConfig;\n\t/** Config file that defined the entry, or the path of the extension that registered it. */\n\tsource: string;\n\t/**\n\t * The global or the project `mcp.json`, or `extension` for servers registered with\n\t * `pi.registerMcpServer()`. Changes to extension servers are not saved.\n\t */\n\tscope?: \"global\" | \"project\" | \"extension\";\n\t/** Project `mcp.json` with an override of this global server's `enabled`, `exposure`, or `toolExposure`. */\n\toverride?: string;\n}\n\nexport interface LoadedMcpConfig {\n\tservers: McpServerEntry[];\n\t/** Activate the codemode tool when `codemode` servers connect. Default: true. */\n\tautoEnableCodemode?: boolean;\n\terrors: string[];\n\t/** The project `mcp.json` when the project is trusted, where `/mcp` saves project overrides. */\n\tprojectConfig?: string;\n}\n\nconst OVERRIDE_KEYS = [\"enabled\", \"exposure\", \"toolExposure\"];\n\n/** Whether an entry overrides a server defined elsewhere instead of defining one. */\nfunction isOverride(value: Record<string, unknown>): boolean {\n\treturn value.command === undefined && value.url === undefined && value.type === undefined;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\ninterface McpConfigState {\n\tservers: Map<string, McpServerEntry>;\n\tautoEnableCodemode?: boolean;\n\terrors: string[];\n}\n\nfunction readConfigFile(path: string, scope: \"global\" | \"project\", state: McpConfigState): void {\n\tconst { servers, errors } = state;\n\tif (!existsSync(path)) return;\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(readFileSync(path, \"utf8\"));\n\t} catch (error) {\n\t\terrors.push(`${path}: ${error instanceof Error ? error.message : String(error)}`);\n\t\treturn;\n\t}\n\tif (!isRecord(parsed) || (parsed.mcpServers !== undefined && !isRecord(parsed.mcpServers))) {\n\t\terrors.push(`${path}: expected an object with an \"mcpServers\" object`);\n\t\treturn;\n\t}\n\tif (typeof parsed.autoEnableCodemode === \"boolean\") state.autoEnableCodemode = parsed.autoEnableCodemode;\n\telse if (parsed.autoEnableCodemode !== undefined) errors.push(`${path}: autoEnableCodemode must be a boolean`);\n\tfor (const [name, value] of Object.entries(parsed.mcpServers ?? {})) {\n\t\tif (scope === \"project\" && isRecord(value) && isOverride(value)) {\n\t\t\tconst base = servers.get(name);\n\t\t\tconst extra = Object.keys(value).filter((key) => !OVERRIDE_KEYS.includes(key));\n\t\t\tif (!base) {\n\t\t\t\terrors.push(`${path}: server \"${name}\" needs \"command\" or \"url\", or a global server to override`);\n\t\t\t} else if (extra.length > 0) {\n\t\t\t\terrors.push(`${path}: server \"${name}\": an override can only set ${OVERRIDE_KEYS.join(\", \")}`);\n\t\t\t} else {\n\t\t\t\tconst config = validateMcpServerConfig(name, { ...base.config, ...value });\n\t\t\t\tif (typeof config === \"string\") errors.push(`${path}: ${config}`);\n\t\t\t\telse servers.set(name, { ...base, config, override: path });\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tconst config = validateMcpServerConfig(name, value);\n\t\tif (typeof config === \"string\") {\n\t\t\terrors.push(`${path}: ${config}`);\n\t\t\tcontinue;\n\t\t}\n\t\t// Names that differ only in `-` and `_` would share a namespace.\n\t\tconst clash = [...servers.keys()].find((other) => other !== name && mcpNamespace(other) === mcpNamespace(name));\n\t\tif (clash) {\n\t\t\terrors.push(`${path}: server \"${name}\" conflicts with \"${clash}\"`);\n\t\t\tcontinue;\n\t\t}\n\t\tif (scope === \"project\" && \"url\" in config && config.auth) {\n\t\t\terrors.push(`${path}: server \"${name}\": auth is only allowed in the global mcp.json`);\n\t\t\tcontinue;\n\t\t}\n\t\tservers.set(name, { name, config, source: path, scope });\n\t}\n}\n\n/**\n * Load global and (when trusted) project MCP configuration. Disabled servers are included with\n * `enabled: false`, so they can be enabled again.\n */\nexport function loadMcpConfig(options: { agentDir: string; cwd: string; projectTrusted: boolean }): LoadedMcpConfig {\n\tconst state: McpConfigState = { servers: new Map(), errors: [] };\n\treadConfigFile(join(options.agentDir, \"mcp.json\"), \"global\", state);\n\tconst projectConfig = options.projectTrusted ? join(options.cwd, CONFIG_DIR_NAME, \"mcp.json\") : undefined;\n\tif (projectConfig) readConfigFile(projectConfig, \"project\", state);\n\treturn {\n\t\tservers: [...state.servers.values()],\n\t\t...(state.autoEnableCodemode === undefined ? {} : { autoEnableCodemode: state.autoEnableCodemode }),\n\t\terrors: state.errors,\n\t\t...(projectConfig ? { projectConfig } : {}),\n\t};\n}\n\n/** Settings `/mcp` changes. `enabled: true` and `exposure: \"codemode\"` are the defaults and remove the key. */\nexport interface McpServerConfigPatch {\n\tenabled?: boolean;\n\texposure?: McpExposure;\n}\n\n/**\n * Change one server's settings in the `mcp.json` that defines or overrides it. With `override`, a\n * missing entry is added as an override. Overrides keep default values, since they replace the global\n * server's. Other content is kept; the file is rewritten with its indentation.\n */\nexport function updateMcpServerConfig(\n\tpath: string,\n\tname: string,\n\tpatch: McpServerConfigPatch,\n\toptions: { override?: boolean } = {},\n): void {\n\teditMcpServers(path, (servers, parsed) => {\n\t\tlet server = servers?.[name];\n\t\tif (server === undefined && options.override) {\n\t\t\tserver = {};\n\t\t\tparsed.mcpServers = { ...servers, [name]: server };\n\t\t}\n\t\tif (!isRecord(server)) throw new Error(`${path} does not define MCP server \"${name}\"`);\n\t\tconst keepDefaults = isOverride(server);\n\t\tif (patch.enabled !== undefined) {\n\t\t\tif (patch.enabled && !keepDefaults) delete server.enabled;\n\t\t\telse server.enabled = patch.enabled;\n\t\t}\n\t\tif (patch.exposure !== undefined) {\n\t\t\tif (patch.exposure === \"codemode\" && !keepDefaults) delete server.exposure;\n\t\t\telse server.exposure = patch.exposure;\n\t\t}\n\t\treturn true;\n\t});\n}\n\n/**\n * Add a server to an `mcp.json`, creating the file when missing. An existing entry with the same\n * name is replaced. Returns true when an entry was replaced.\n */\nexport function addMcpServerConfig(path: string, name: string, config: McpServerConfig): boolean {\n\tlet replaced = false;\n\teditMcpServers(path, (servers, parsed) => {\n\t\tconst target = servers ?? {};\n\t\treplaced = target[name] !== undefined;\n\t\ttarget[name] = config;\n\t\tparsed.mcpServers = target;\n\t\treturn true;\n\t});\n\treturn replaced;\n}\n\n/** Remove a server from an `mcp.json`. Returns false when the file does not define it. */\nexport function removeMcpServerConfig(path: string, name: string): boolean {\n\tif (!existsSync(path)) return false;\n\tlet removed = false;\n\teditMcpServers(path, (servers) => {\n\t\tif (!servers || servers[name] === undefined) return false;\n\t\tdelete servers[name];\n\t\tremoved = true;\n\t\treturn true;\n\t});\n\treturn removed;\n}\n\n/**\n * Read an `mcp.json` (an empty config when missing), let `edit` change its `mcpServers`, and write\n * it back with its indentation when `edit` returns true. Other content is kept.\n */\nfunction editMcpServers(\n\tpath: string,\n\tedit: (servers: Record<string, unknown> | undefined, parsed: Record<string, unknown>) => boolean,\n): void {\n\tconst text = existsSync(path) ? readFileSync(path, \"utf8\") : undefined;\n\tconst parsed: unknown = text === undefined ? {} : JSON.parse(text);\n\tif (!isRecord(parsed) || (parsed.mcpServers !== undefined && !isRecord(parsed.mcpServers))) {\n\t\tthrow new Error(`${path}: expected an object with an \"mcpServers\" object`);\n\t}\n\tconst servers = isRecord(parsed.mcpServers) ? parsed.mcpServers : undefined;\n\tif (!edit(servers, parsed)) return;\n\tconst indent = (text && /^([ \\t]+)\\S/m.exec(text)?.[1]) || \" \";\n\tmkdirSync(dirname(path), { recursive: true });\n\twriteFileSync(path, `${JSON.stringify(parsed, null, indent)}\\n`);\n}\n"]}
@@ -41,7 +41,10 @@ export interface McpExtensionOptions {
41
41
  logPath?: string;
42
42
  /** Opens the OAuth authorization URL. Defaults to the platform browser. */
43
43
  openUrl?: (url: string) => void;
44
- /** Saves `/mcp` changes to the server's config file. Defaults to editing its `mcp.json`. */
44
+ /**
45
+ * Saves `/mcp` changes to the server's config file: its project `override` when set, else its
46
+ * `source`. Defaults to editing that `mcp.json`.
47
+ */
45
48
  updateConfig?: (entry: McpServerEntry, patch: McpServerConfigPatch) => void;
46
49
  /**
47
50
  * How long the first prompt waits for servers with `direct` tools that are still connecting at
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH,OAAO,KAAK,EAGX,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EAEN,KAAK,eAAe,EAGpB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,uBAAuB,EAAmB,MAAM,YAAY,CAAC;AAS3E,OAAO,KAAK,EAAqC,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAI3F,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,WAAW,mBAAmB;IACnC,uFAAuF;IACvF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,eAAe,CAAC;IACxD,qFAAqF;IACrF,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,4FAA4F;IAC5F,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC5E;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AA4DD,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAGjD;;;GAGG;AACH,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAe9C,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC;AAQD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,GAAG,SAAS,CA0B7F;AA2DD,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,gBAAgB,CAs4BtF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAMH,OAAO,KAAK,EAGX,gBAAgB,EAChB,gBAAgB,EAEhB,MAAM,gCAAgC,CAAC;AAMxC,OAAO,EAEN,KAAK,eAAe,EAGpB,KAAK,oBAAoB,EACzB,KAAK,cAAc,EAEnB,MAAM,aAAa,CAAC;AACrB,OAAO,KAAK,EAAE,uBAAuB,EAAmB,MAAM,YAAY,CAAC;AAS3E,OAAO,KAAK,EAAqC,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAI3F,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,WAAW,mBAAmB;IACnC,uFAAuF;IACvF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,eAAe,CAAC;IACxD,qFAAqF;IACrF,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,0DAA0D;IAC1D,WAAW,CAAC,EAAE,uBAAuB,CAAC;IACtC,8FAA8F;IAC9F,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAC5E;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACvB;AA4DD,6FAA6F;AAC7F,eAAO,MAAM,mBAAmB,gBAAgB,CAAC;AAGjD;;;GAGG;AACH,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAe9C,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAChC,KAAK,EAAE,cAAc,CAAC;IACtB,UAAU,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvC;AAQD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,SAAS,gBAAgB,EAAE,GAAG,MAAM,GAAG,SAAS,CA0B7F;AA2DD,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,gBAAgB,CA+6BtF"}
@@ -26,7 +26,6 @@
26
26
  * current session for registered servers.
27
27
  */
28
28
  import { join, resolve } from "node:path";
29
- import { hyperlink } from "@earendil-works/pi-tui";
30
29
  import { getAgentDir } from "../../config.js";
31
30
  import { mcpNamespace } from "../../core/mcp-servers.js";
32
31
  import { openBrowser } from "../../utils/open-browser.js";
@@ -35,7 +34,7 @@ import { isToolSearchTool, TOOL_SEARCH_TOOL_NAME } from "../tool-search/tool.js"
35
34
  import { getMcpToolExposure, loadMcpConfig, updateMcpServerConfig, } from "./config.js";
36
35
  import { createMcpResourceToolDefinitions, LIST_MCP_RESOURCE_TEMPLATES_TOOL, LIST_MCP_RESOURCES_TOOL, READ_MCP_RESOURCE_TOOL, } from "./resources.js";
37
36
  import { loadMcpRuntime } from "./runtime.lazy.js";
38
- import { createMcpToolDefinition, createMcpToolName } from "./tools.js";
37
+ import { createMcpToolDefinition, createMcpToolName, createMcpToolRenderers } from "./tools.js";
39
38
  import { showMcpManager } from "./ui.js";
40
39
  const DEFAULT_STARTUP_WAIT_MS = 10_000;
41
40
  const RESOURCE_TOOL_NAMES = new Set([
@@ -194,6 +193,8 @@ export function createMcpExtension(options = {}) {
194
193
  /** Servers from `mcp.json`, which take precedence over registered servers of the same name. */
195
194
  let configuredEntries = [];
196
195
  let configErrors = [];
196
+ /** The trusted project's `mcp.json`, where `/mcp` saves project overrides of global servers. */
197
+ let projectConfig;
197
198
  /** Registered servers that `mcp.json` overrides, shown in `/mcp`. */
198
199
  let overridden = [];
199
200
  /** Between session_start and session_shutdown. Registrations before that are read on session_start. */
@@ -214,7 +215,10 @@ export function createMcpExtension(options = {}) {
214
215
  let modelRegistry;
215
216
  let serverLog;
216
217
  const openUrl = options.openUrl ?? openBrowser;
217
- const updateConfig = options.updateConfig ?? ((entry, patch) => updateMcpServerConfig(entry.source, entry.name, patch));
218
+ const updateConfig = options.updateConfig ??
219
+ ((entry, patch) => updateMcpServerConfig(entry.override ?? entry.source, entry.name, patch, {
220
+ override: entry.override !== undefined,
221
+ }));
218
222
  const listeners = new Set();
219
223
  const emitChange = () => {
220
224
  for (const listener of listeners)
@@ -259,6 +263,11 @@ export function createMcpExtension(options = {}) {
259
263
  const serverTools = new Map();
260
264
  /** Last definition registered under each tool name, to re-register withdrawn tools as hidden. */
261
265
  const definitions = new Map();
266
+ // A resumed session renders calls to MCP tools before their server connected, if it ever does.
267
+ pi.registerToolRenderer((toolName, next) => {
268
+ const match = /^mcp__(.+?)__(.+)$/.exec(toolName);
269
+ return next() ?? (match ? createMcpToolRenderers(`${match[1]}/${match[2]}`) : undefined);
270
+ });
262
271
  const registerTools = (connection) => {
263
272
  const server = connection.entry.name;
264
273
  const entry = findServer(server)?.entry ?? connection.entry;
@@ -481,18 +490,20 @@ export function createMcpExtension(options = {}) {
481
490
  };
482
491
  /**
483
492
  * Save a config change; returns an error message when the file could not be updated. Changes to
484
- * registered servers only apply to the current session.
493
+ * registered servers only apply to the current session. `inProject` adds a project override.
485
494
  */
486
- const saveConfig = (server, patch) => {
487
- if (server.entry.scope !== "extension") {
495
+ const saveConfig = (server, patch, inProject = false) => {
496
+ const override = inProject ? projectConfig : server.entry.override;
497
+ const entry = override ? { ...server.entry, override } : server.entry;
498
+ if (entry.scope !== "extension") {
488
499
  try {
489
- updateConfig(server.entry, patch);
500
+ updateConfig(entry, patch);
490
501
  }
491
502
  catch (error) {
492
- return `Could not update ${server.entry.source}: ${errorMessage(error)}`;
503
+ return `Could not update ${entry.override ?? entry.source}: ${errorMessage(error)}`;
493
504
  }
494
505
  }
495
- server.entry = { ...server.entry, config: { ...server.entry.config, ...patch } };
506
+ server.entry = { ...entry, config: { ...entry.config, ...patch } };
496
507
  return undefined;
497
508
  };
498
509
  const signIn = async (server, prompt) => {
@@ -547,8 +558,8 @@ export function createMcpExtension(options = {}) {
547
558
  }
548
559
  };
549
560
  /** Returns an error message when the config could not be saved; connection errors show in the state. */
550
- const setEnabled = async (server, enabled) => {
551
- const failed = saveConfig(server, { enabled });
561
+ const setEnabled = async (server, enabled, inProject = false) => {
562
+ const failed = saveConfig(server, { enabled }, inProject);
552
563
  if (failed)
553
564
  return failed;
554
565
  if (!enabled) {
@@ -594,7 +605,7 @@ export function createMcpExtension(options = {}) {
594
605
  .map((server) => ({
595
606
  value: server.entry.name,
596
607
  label: server.entry.name,
597
- description: `${describeState(server)} · ${exposureOf(server.entry)} · ${server.entry.scope ?? server.entry.source}`,
608
+ description: `${describeState(server)} · ${exposureOf(server.entry)} · ${server.entry.override ? "global, project override" : (server.entry.scope ?? server.entry.source)}`,
598
609
  })),
599
610
  empty: `No MCP servers configured. Add them to ${resolve(getAgentDir(), "mcp.json")} or .pi/mcp.json.`,
600
611
  confirmLabel: "manage",
@@ -614,12 +625,20 @@ export function createMcpExtension(options = {}) {
614
625
  const { entry, connection } = server;
615
626
  const saved = entry.scope === "extension"
616
627
  ? "for this session"
617
- : entry.scope
618
- ? `saved to the ${entry.scope} mcp.json`
619
- : "saved to mcp.json";
628
+ : entry.override
629
+ ? "saved to the project mcp.json"
630
+ : entry.scope
631
+ ? `saved to the ${entry.scope} mcp.json`
632
+ : "saved to mcp.json";
633
+ // Global servers without an override can be turned on or off for the trusted project alone.
634
+ const inProject = entry.scope === "global" && !entry.override && projectConfig !== undefined;
635
+ const inProjectSaved = "saved to the project mcp.json";
620
636
  const items = [];
621
637
  if (!isEnabled(server)) {
622
638
  items.push({ value: "enable", label: "Enable", description: saved });
639
+ if (inProject) {
640
+ items.push({ value: "enable-project", label: "Enable in this project", description: inProjectSaved });
641
+ }
623
642
  }
624
643
  else {
625
644
  const state = connection?.state;
@@ -636,10 +655,14 @@ export function createMcpExtension(options = {}) {
636
655
  }
637
656
  items.push({ value: "exposure", label: "Exposure", description: exposureOf(entry) });
638
657
  items.push({ value: "disable", label: "Disable", description: saved });
658
+ if (inProject) {
659
+ items.push({ value: "disable-project", label: "Disable in this project", description: inProjectSaved });
660
+ }
639
661
  }
640
662
  const details = [
641
663
  describeTransport(entry),
642
664
  `${entry.scope ?? "config"}: ${entry.source}`,
665
+ ...(entry.override ? [`project override: ${entry.override}`] : []),
643
666
  `State: ${describeState(server, false)}`,
644
667
  ];
645
668
  const error = [server.message, connection?.state === "connected" ? undefined : connection?.error]
@@ -681,7 +704,7 @@ export function createMcpExtension(options = {}) {
681
704
  title: `Exposure of ${server.entry.name}`,
682
705
  details: server.entry.scope === "extension"
683
706
  ? `Applies to this session; the server is registered by ${server.entry.source}.`
684
- : `Saved to ${server.entry.source}.`,
707
+ : `Saved to ${server.entry.override ?? server.entry.source}.`,
685
708
  items: Object.keys(EXPOSURE_DESCRIPTIONS).map((exposure) => ({
686
709
  value: exposure,
687
710
  label: `${exposure === current ? "✓ " : " "}${exposure}`,
@@ -695,27 +718,30 @@ export function createMcpExtension(options = {}) {
695
718
  return undefined;
696
719
  return setExposure(server, choice);
697
720
  };
721
+ /** Sign in with the manager view's sign-in screen, which shows the URL with a copy key. */
722
+ const signInWithUi = (ui, server) => {
723
+ const title = `Sign in to ${server.entry.name}`;
724
+ let authorizationUrl = "";
725
+ ui.status(title, "Contacting the authorization server…");
726
+ return signIn(server, {
727
+ showAuthorizationUrl: (url) => {
728
+ authorizationUrl = url.href;
729
+ openUrl(url.href);
730
+ },
731
+ promptForRedirectUrl: async (signal) => {
732
+ const value = await ui.redirectUrl(title, authorizationUrl, signal);
733
+ ui.status(title, "Connecting…");
734
+ return value;
735
+ },
736
+ });
737
+ };
698
738
  const runAction = async (ui, ctx, server, action) => {
699
739
  const { name } = server.entry;
700
740
  let message;
701
741
  switch (action) {
702
- case "signin": {
703
- const title = `Sign in to ${name}`;
704
- let authorizationUrl = "";
705
- ui.status(title, "Contacting the authorization server…");
706
- message = await signIn(server, {
707
- showAuthorizationUrl: (url) => {
708
- authorizationUrl = url.href;
709
- openUrl(url.href);
710
- },
711
- promptForRedirectUrl: async (signal) => {
712
- const value = await ui.redirectUrl(title, authorizationUrl, signal);
713
- ui.status(title, "Connecting…");
714
- return value;
715
- },
716
- });
742
+ case "signin":
743
+ message = await signInWithUi(ui, server);
717
744
  break;
718
- }
719
745
  case "reconnect":
720
746
  // A failure shows as the connection's state and error.
721
747
  ui.status(`MCP server ${name}`, "Reconnecting…");
@@ -732,9 +758,13 @@ export function createMcpExtension(options = {}) {
732
758
  break;
733
759
  case "enable":
734
760
  case "disable":
735
- ui.status(`MCP server ${name}`, action === "enable" ? "Connecting…" : "Disconnecting…");
736
- message = await setEnabled(server, action === "enable");
761
+ case "enable-project":
762
+ case "disable-project": {
763
+ const enable = action.startsWith("enable");
764
+ ui.status(`MCP server ${name}`, enable ? "Connecting…" : "Disconnecting…");
765
+ message = await setEnabled(server, enable, action.endsWith("-project"));
737
766
  break;
767
+ }
738
768
  }
739
769
  server.message = message;
740
770
  ensureDiscoveryActive(ctx);
@@ -819,17 +849,21 @@ export function createMcpExtension(options = {}) {
819
849
  ctx.ui.notify(`Signing in to MCP server "${name}" requires interactive mode.`, "error");
820
850
  return;
821
851
  }
822
- const failure = await signIn(server, {
823
- showAuthorizationUrl: (url) => {
824
- // Long URLs wrap, which some terminals cannot open; a short link line stays on one line.
825
- const lines = ctx.mode === "tui"
826
- ? `${hyperlink(url.href, url.href)}\n${hyperlink(process.platform === "darwin" ? "Cmd+click to open" : "Ctrl+click to open", url.href)}`
827
- : url.href;
828
- ctx.ui.notify(`Sign in to MCP server "${name}" in your browser:\n${lines}`, "info");
829
- openUrl(url.href);
830
- },
831
- promptForRedirectUrl: (signal) => ctx.ui.input(`Waiting for sign-in to "${name}". If the browser cannot reach this machine, paste the URL it was redirected to.`, "http://127.0.0.1:.../callback?code=...", { signal }),
832
- });
852
+ let failure;
853
+ if (ctx.mode === "tui") {
854
+ await showMcpManager(ctx, async (ui) => {
855
+ failure = await signInWithUi(ui, server);
856
+ });
857
+ }
858
+ else {
859
+ failure = await signIn(server, {
860
+ showAuthorizationUrl: (url) => {
861
+ ctx.ui.notify(`Sign in to MCP server "${name}" in your browser:\n${url.href}`, "info");
862
+ openUrl(url.href);
863
+ },
864
+ promptForRedirectUrl: (signal) => ctx.ui.input(`Waiting for sign-in to "${name}". If the browser cannot reach this machine, paste the URL it was redirected to.`, "http://127.0.0.1:.../callback?code=...", { signal }),
865
+ });
866
+ }
833
867
  if (failure) {
834
868
  ctx.ui.notify(failure, failure === "Sign-in cancelled." ? "info" : "error");
835
869
  return;
@@ -840,6 +874,7 @@ export function createMcpExtension(options = {}) {
840
874
  pi.on("session_start", (_event, ctx) => {
841
875
  const loaded = (options.loadConfig ?? defaultLoadConfig)(ctx);
842
876
  configErrors = loaded.errors;
877
+ projectConfig = loaded.projectConfig;
843
878
  autoEnableCodemode = loaded.autoEnableCodemode ?? true;
844
879
  warnedUnreachable = false;
845
880
  waitedForStartup = false;