@earendil-works/pi-coding-agent 0.99.1 → 0.99.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/bundle/chunks/anthropic-messages-UID2PALB.js +6 -0
- package/dist/bundle/chunks/{azure-openai-responses-EN5WOBET.js → azure-openai-responses-TLHNIYZK.js} +1 -1
- package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
- package/dist/bundle/chunks/{chunk-IMTXZUO7.js → chunk-3KYEVQKX.js} +3 -3
- package/dist/bundle/chunks/{chunk-W2DOMZSC.js → chunk-3LH3ZQKY.js} +1 -1
- package/dist/bundle/chunks/chunk-3NVH2HQH.js +4 -0
- package/dist/bundle/chunks/{chunk-GUORCHFS.js → chunk-3YAHQSW6.js} +23 -20
- package/dist/bundle/chunks/{chunk-LLMKQWZS.js → chunk-45SXDISG.js} +5 -7
- package/dist/bundle/chunks/chunk-6D7LHLKG.js +2 -0
- package/dist/bundle/chunks/{chunk-AXPY26X7.js → chunk-7QSPBJKW.js} +3 -3
- package/dist/bundle/chunks/{chunk-SLDA2X2H.js → chunk-ECRK44UV.js} +1 -1
- package/dist/bundle/chunks/{chunk-PEHWOV5U.js → chunk-G3UZ5FWD.js} +12 -23
- package/dist/bundle/chunks/chunk-LD2P6SFZ.js +2 -0
- package/dist/bundle/chunks/{chunk-J4CWPC4N.js → chunk-OSKPX3Z5.js} +1 -1
- package/dist/bundle/chunks/{chunk-LRS5S6EK.js → chunk-R5J3MOBF.js} +1 -1
- package/dist/bundle/chunks/chunk-VIV3CWXG.js +2 -0
- package/dist/bundle/chunks/{cli-AZXNGIZH.js → cli-ALNBTL3U.js} +6 -4
- package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-XJQQYGGU.js → cloudflare-workers-ai-system-one-NPQN263E.js} +1 -1
- package/dist/bundle/chunks/codemode-worker.js +23 -3
- package/dist/bundle/chunks/{execute-7XXPZAWW.js → execute-RC7W4ML3.js} +26 -6
- package/dist/bundle/chunks/github-copilot.js +1 -1
- package/dist/bundle/chunks/{google-generative-ai-CQAKKJTA.js → google-generative-ai-APTPZOQL.js} +1 -1
- package/dist/bundle/chunks/{google-vertex-AE36YXJU.js → google-vertex-MXCOSOY3.js} +1 -1
- package/dist/bundle/chunks/{llama-cpp-classify-PJJYKPLD.js → llama-cpp-classify-XT44PI3C.js} +1 -1
- package/dist/bundle/chunks/{mistral-conversations-KEYTXJM7.js → mistral-conversations-DKVFNQL6.js} +1 -1
- package/dist/bundle/chunks/{openai-codex-responses-JQTCOVEG.js → openai-codex-responses-3GKGNXC4.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-XW2Q5HVC.js → openai-completions-JXDDPZ23.js} +1 -1
- package/dist/bundle/chunks/{openai-responses-6AXWFL75.js → openai-responses-S7GKCCDG.js} +1 -1
- package/dist/bundle/chunks/{openrouter-images-TPQ4V6OR.js → openrouter-images-ZVXWCSIM.js} +1 -1
- package/dist/bundle/chunks/{runtime-G5WJPRBM.js → runtime-NJPX4ID2.js} +1 -1
- package/dist/bundle/chunks/{typesafe-system-one-E7A4HNJI.js → typesafe-system-one-A4CZTQWZ.js} +1 -1
- package/dist/bundle/chunks/virtual-modules-5UGH4HRZ.js +2 -0
- package/dist/bundle/cli-runtime.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/config.d.ts +5 -8
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +16 -13
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts +7 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +14 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +13 -1
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/types.d.ts +6 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/mcp-servers.d.ts +28 -7
- package/dist/core/mcp-servers.d.ts.map +1 -1
- package/dist/core/mcp-servers.js +45 -11
- package/dist/core/mcp-servers.js.map +1 -1
- package/dist/core/model-runtime.d.ts +6 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +22 -18
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/remote-catalog-provider.d.ts.map +1 -1
- package/dist/core/remote-catalog-provider.js +4 -9
- package/dist/core/remote-catalog-provider.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/tools/renderers/bash.d.ts.map +1 -1
- package/dist/core/tools/renderers/bash.js +11 -31
- package/dist/core/tools/renderers/bash.js.map +1 -1
- package/dist/core/virtual-models.d.ts +2 -0
- package/dist/core/virtual-models.d.ts.map +1 -1
- package/dist/core/virtual-models.js +19 -12
- package/dist/core/virtual-models.js.map +1 -1
- package/dist/extensions/codemode/execute.d.ts.map +1 -1
- package/dist/extensions/codemode/execute.js +43 -4
- package/dist/extensions/codemode/execute.js.map +1 -1
- package/dist/extensions/codemode/renderer.d.ts.map +1 -1
- package/dist/extensions/codemode/renderer.js +43 -25
- package/dist/extensions/codemode/renderer.js.map +1 -1
- package/dist/extensions/codemode/tool.d.ts +5 -4
- package/dist/extensions/codemode/tool.d.ts.map +1 -1
- package/dist/extensions/codemode/tool.js +22 -31
- package/dist/extensions/codemode/tool.js.map +1 -1
- package/dist/extensions/codemode/worker.d.ts +1 -1
- package/dist/extensions/codemode/worker.js +1 -1
- package/dist/extensions/codemode/worker.js.map +1 -1
- package/dist/extensions/mcp/cli.d.ts.map +1 -1
- package/dist/extensions/mcp/cli.js +18 -3
- package/dist/extensions/mcp/cli.js.map +1 -1
- package/dist/extensions/mcp/config.d.ts +4 -2
- package/dist/extensions/mcp/config.d.ts.map +1 -1
- package/dist/extensions/mcp/config.js +14 -2
- package/dist/extensions/mcp/config.js.map +1 -1
- package/dist/extensions/mcp/index.d.ts +32 -8
- package/dist/extensions/mcp/index.d.ts.map +1 -1
- package/dist/extensions/mcp/index.js +205 -52
- package/dist/extensions/mcp/index.js.map +1 -1
- package/dist/extensions/mcp/oauth.d.ts +2 -0
- package/dist/extensions/mcp/oauth.d.ts.map +1 -1
- package/dist/extensions/mcp/oauth.js +1 -1
- package/dist/extensions/mcp/oauth.js.map +1 -1
- package/dist/extensions/mcp/runtime.d.ts +2 -0
- package/dist/extensions/mcp/runtime.d.ts.map +1 -1
- package/dist/extensions/mcp/runtime.js +14 -8
- package/dist/extensions/mcp/runtime.js.map +1 -1
- package/dist/extensions/mcp/tools.d.ts +5 -4
- package/dist/extensions/mcp/tools.d.ts.map +1 -1
- package/dist/extensions/mcp/tools.js +33 -15
- package/dist/extensions/mcp/tools.js.map +1 -1
- package/dist/extensions/mcp/ui.d.ts.map +1 -1
- package/dist/extensions/mcp/ui.js +4 -2
- package/dist/extensions/mcp/ui.js.map +1 -1
- package/dist/extensions/tool-search/tool.d.ts +5 -4
- package/dist/extensions/tool-search/tool.d.ts.map +1 -1
- package/dist/extensions/tool-search/tool.js +7 -28
- package/dist/extensions/tool-search/tool.js.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.d.ts +27 -3
- package/dist/modes/interactive/components/visual-truncate.d.ts.map +1 -1
- package/dist/modes/interactive/components/visual-truncate.js +39 -6
- package/dist/modes/interactive/components/visual-truncate.js.map +1 -1
- package/docs/cli.md +3 -3
- package/docs/extensions.md +3 -3
- package/docs/mcp.md +125 -82
- package/docs/providers.md +2 -0
- package/docs/sdk.md +1 -1
- package/docs/settings.md +3 -1
- package/examples/extensions/built-in-tool-renderer.ts +16 -40
- package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/gondolin/package-lock.json +2 -2
- package/examples/extensions/gondolin/package.json +1 -1
- package/examples/extensions/minimal-mode.ts +24 -96
- package/examples/extensions/sandbox/package-lock.json +2 -2
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package-lock.json +2 -2
- package/examples/extensions/with-deps/package.json +1 -1
- package/examples/sdk/14-codemode-mcp.ts +2 -2
- package/npm-shrinkwrap.json +26 -26
- package/package.json +10 -10
- package/dist/bundle/chunks/anthropic-messages-A6EXUBG6.js +0 -6
- package/dist/bundle/chunks/chunk-GOPCREV2.js +0 -4
- package/dist/bundle/chunks/chunk-HSPCVFST.js +0 -2
- package/dist/bundle/chunks/chunk-XNGRGP62.js +0 -2
- package/dist/bundle/chunks/virtual-modules-CSWRO37N.js +0 -2
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Connects the servers from `mcp.json` and the servers extensions register with
|
|
5
5
|
* `pi.registerMcpServer()` when a session starts, and servers registered later right away. A server
|
|
6
|
-
* in `mcp.json` takes precedence over a registered server of the same name.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the
|
|
6
|
+
* in `mcp.json` takes precedence over a registered server of the same name. Connections run in the
|
|
7
|
+
* background: the first prompt waits only for servers with `direct` tools, and codemode scripts,
|
|
8
|
+
* `tool_search`, and the resource tools wait for the servers they need when they run. Tools are
|
|
9
|
+
* registered as `mcp__<server>__<tool>`. By default (`"exposure": "codemode"`) the tools are only
|
|
10
|
+
* callable from codemode scripts, which keeps MCP tools out of the model's tool declarations and
|
|
11
|
+
* the codemode description: scripts find the tools with `searchTools()` and the server instructions
|
|
12
|
+
* with `describeNamespace()`. The codemode tool is activated for that unless `autoEnableCodemode` is
|
|
13
|
+
* false. `"deferred"` declares the tools to the model once the `tool_search` tool loads them, and
|
|
14
|
+
* activates `tool_search` instead of codemode.
|
|
12
15
|
* `"exposure": "direct"` declares them to the model right away, and `"hidden"` makes them
|
|
13
16
|
* unreachable. `toolExposure` overrides the exposure of single tools. Servers with resources are
|
|
14
17
|
* reached through Codex's `list_mcp_resources`, `list_mcp_resource_templates`, and
|
|
@@ -41,11 +44,32 @@ export interface McpExtensionOptions {
|
|
|
41
44
|
/** Saves `/mcp` changes to the server's config file. Defaults to editing its `mcp.json`. */
|
|
42
45
|
updateConfig?: (entry: McpServerEntry, patch: McpServerConfigPatch) => void;
|
|
43
46
|
/**
|
|
44
|
-
* How long the first prompt waits for servers that are still connecting at
|
|
45
|
-
* milliseconds. Their tools become available when they connect.
|
|
47
|
+
* How long the first prompt waits for servers with `direct` tools that are still connecting at
|
|
48
|
+
* startup, in milliseconds. Their tools become available when they connect. Other servers are
|
|
49
|
+
* waited for when a script or search needs them. Default: 10000.
|
|
46
50
|
*/
|
|
47
51
|
startupWaitMs?: number;
|
|
48
52
|
}
|
|
53
|
+
/** Name of the system prompt section that lists the servers whose tools are not declared. */
|
|
54
|
+
export declare const MCP_SERVERS_SECTION = "mcp_servers";
|
|
55
|
+
/**
|
|
56
|
+
* Characters of the whole section. Descriptions shrink to fit; when the server lines alone do not
|
|
57
|
+
* fit, the last servers are left out and counted in a closing line.
|
|
58
|
+
*/
|
|
59
|
+
export declare const MAX_SERVERS_SECTION_CHARS = 4096;
|
|
60
|
+
/** What the `mcp_servers` section needs of a server. */
|
|
61
|
+
export interface McpServerListing {
|
|
62
|
+
entry: McpServerEntry;
|
|
63
|
+
connection?: {
|
|
64
|
+
instructions?: string;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The `mcp_servers` section: every enabled server with codemode or deferred tools, with how its
|
|
69
|
+
* tools are reached and a one-line summary. The model learns of the servers from it, since neither
|
|
70
|
+
* codemode nor tool_search lists them. Undefined when there are no such servers.
|
|
71
|
+
*/
|
|
72
|
+
export declare function renderServersSection(servers: readonly McpServerListing[]): string | undefined;
|
|
49
73
|
export declare function createMcpExtension(options?: McpExtensionOptions): ExtensionFactory;
|
|
50
74
|
declare const _default: ExtensionFactory;
|
|
51
75
|
export default _default;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/mcp/index.ts"],"names":[],"mappings":"AAAA
|
|
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;AAU9C,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,CA2B7F;AA2DD,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,gBAAgB,CAi4BtF"}
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Connects the servers from `mcp.json` and the servers extensions register with
|
|
5
5
|
* `pi.registerMcpServer()` when a session starts, and servers registered later right away. A server
|
|
6
|
-
* in `mcp.json` takes precedence over a registered server of the same name.
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* the
|
|
6
|
+
* in `mcp.json` takes precedence over a registered server of the same name. Connections run in the
|
|
7
|
+
* background: the first prompt waits only for servers with `direct` tools, and codemode scripts,
|
|
8
|
+
* `tool_search`, and the resource tools wait for the servers they need when they run. Tools are
|
|
9
|
+
* registered as `mcp__<server>__<tool>`. By default (`"exposure": "codemode"`) the tools are only
|
|
10
|
+
* callable from codemode scripts, which keeps MCP tools out of the model's tool declarations and
|
|
11
|
+
* the codemode description: scripts find the tools with `searchTools()` and the server instructions
|
|
12
|
+
* with `describeNamespace()`. The codemode tool is activated for that unless `autoEnableCodemode` is
|
|
13
|
+
* false. `"deferred"` declares the tools to the model once the `tool_search` tool loads them, and
|
|
14
|
+
* activates `tool_search` instead of codemode.
|
|
12
15
|
* `"exposure": "direct"` declares them to the model right away, and `"hidden"` makes them
|
|
13
16
|
* unreachable. `toolExposure` overrides the exposure of single tools. Servers with resources are
|
|
14
17
|
* reached through Codex's `list_mcp_resources`, `list_mcp_resource_templates`, and
|
|
@@ -24,18 +27,23 @@
|
|
|
24
27
|
*/
|
|
25
28
|
import { join, resolve } from "node:path";
|
|
26
29
|
import { getAgentDir } from "../../config.js";
|
|
30
|
+
import { mcpNamespace } from "../../core/mcp-servers.js";
|
|
27
31
|
import { openBrowser } from "../../utils/open-browser.js";
|
|
28
32
|
import { CODEMODE_TOOL_NAME, isCodemodeTool } from "../codemode/tool.js";
|
|
29
33
|
import { isToolSearchTool, TOOL_SEARCH_TOOL_NAME } from "../tool-search/tool.js";
|
|
30
34
|
import { getMcpToolExposure, loadMcpConfig, updateMcpServerConfig, } from "./config.js";
|
|
31
|
-
import { createMcpResourceToolDefinitions } from "./resources.js";
|
|
35
|
+
import { createMcpResourceToolDefinitions, LIST_MCP_RESOURCE_TEMPLATES_TOOL, LIST_MCP_RESOURCES_TOOL, READ_MCP_RESOURCE_TOOL, } from "./resources.js";
|
|
32
36
|
import { loadMcpRuntime } from "./runtime.lazy.js";
|
|
33
37
|
import { createMcpToolDefinition, createMcpToolName } from "./tools.js";
|
|
34
38
|
import { showMcpManager } from "./ui.js";
|
|
35
39
|
const DEFAULT_STARTUP_WAIT_MS = 10_000;
|
|
40
|
+
const RESOURCE_TOOL_NAMES = new Set([
|
|
41
|
+
LIST_MCP_RESOURCES_TOOL,
|
|
42
|
+
LIST_MCP_RESOURCE_TEMPLATES_TOOL,
|
|
43
|
+
READ_MCP_RESOURCE_TOOL,
|
|
44
|
+
]);
|
|
36
45
|
const EXPOSURE_DESCRIPTIONS = {
|
|
37
|
-
codemode: "called from codemode scripts,
|
|
38
|
-
"codemode-deferred": "called from codemode scripts, not listed; scripts find them with searchTools()",
|
|
46
|
+
codemode: "called from codemode scripts, which find them with searchTools()",
|
|
39
47
|
deferred: "not declared until tool_search loads them, then called directly; no codemode needed",
|
|
40
48
|
direct: "declared to the model like built-in tools",
|
|
41
49
|
};
|
|
@@ -51,6 +59,80 @@ function isEnabled(server) {
|
|
|
51
59
|
function exposureOf(entry) {
|
|
52
60
|
return entry.config.exposure ?? "codemode";
|
|
53
61
|
}
|
|
62
|
+
/** Exposures the server's tools can have, known from its config before it connects. */
|
|
63
|
+
function configuredExposures(entry) {
|
|
64
|
+
return new Set([exposureOf(entry), ...Object.values(entry.config.toolExposure ?? {})]);
|
|
65
|
+
}
|
|
66
|
+
/** Whether some of the server's tools are declared to the model, so the first prompt waits for them. */
|
|
67
|
+
function hasDirectTools(entry) {
|
|
68
|
+
return configuredExposures(entry).has("direct");
|
|
69
|
+
}
|
|
70
|
+
/** Whether some of the server's tools are reached through codemode or tool_search. */
|
|
71
|
+
function hasIndirectTools(entry) {
|
|
72
|
+
const exposures = configuredExposures(entry);
|
|
73
|
+
return exposures.has("codemode") || exposures.has("deferred");
|
|
74
|
+
}
|
|
75
|
+
/** Name of the system prompt section that lists the servers whose tools are not declared. */
|
|
76
|
+
export const MCP_SERVERS_SECTION = "mcp_servers";
|
|
77
|
+
/** Characters of a server description in the section, as Codex allows for deferred namespaces. */
|
|
78
|
+
const MAX_SERVER_DESCRIPTION_CHARS = 250;
|
|
79
|
+
/**
|
|
80
|
+
* Characters of the whole section. Descriptions shrink to fit; when the server lines alone do not
|
|
81
|
+
* fit, the last servers are left out and counted in a closing line.
|
|
82
|
+
*/
|
|
83
|
+
export const MAX_SERVERS_SECTION_CHARS = 4096;
|
|
84
|
+
const SERVERS_SECTION_INTRO = "MCP servers whose tools are not declared to you. Call the tools of `codemode` servers from codemode scripts: find them with `searchTools(query, { namespace })` and read a server's instructions and tool names with `describeNamespace(name)`. Load the tools of `tool_search` servers with `tool_search`.";
|
|
85
|
+
function truncate(text, max) {
|
|
86
|
+
if (text.length <= max)
|
|
87
|
+
return text;
|
|
88
|
+
return max <= 1 ? "" : `${text.slice(0, max - 1).trimEnd()}…`;
|
|
89
|
+
}
|
|
90
|
+
/** First line of the configured description, or of the server instructions once connected. */
|
|
91
|
+
function serverSummary(server) {
|
|
92
|
+
const text = server.entry.config.description?.trim() || server.connection?.instructions || "";
|
|
93
|
+
return (text.split("\n", 1)[0] ?? "").trim();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* The `mcp_servers` section: every enabled server with codemode or deferred tools, with how its
|
|
97
|
+
* tools are reached and a one-line summary. The model learns of the servers from it, since neither
|
|
98
|
+
* codemode nor tool_search lists them. Undefined when there are no such servers.
|
|
99
|
+
*/
|
|
100
|
+
export function renderServersSection(servers) {
|
|
101
|
+
const listed = servers
|
|
102
|
+
.filter((server) => isEnabled(server) && hasIndirectTools(server.entry))
|
|
103
|
+
.sort((a, b) => a.entry.name.localeCompare(b.entry.name));
|
|
104
|
+
if (listed.length === 0)
|
|
105
|
+
return undefined;
|
|
106
|
+
const heads = listed.map((server) => {
|
|
107
|
+
const exposures = configuredExposures(server.entry);
|
|
108
|
+
const reach = exposures.has("codemode") ? "codemode" : "tool_search";
|
|
109
|
+
return `- ${mcpNamespace(server.entry.name)} (${reach})`;
|
|
110
|
+
});
|
|
111
|
+
const omitted = (count) => count > 0 ? [`- … ${count} more server${count === 1 ? "" : "s"}; find their tools with searchTools()`] : [];
|
|
112
|
+
// Characters of the intro, the first `kept` server lines without descriptions, and the omission line.
|
|
113
|
+
const size = (kept) => [SERVERS_SECTION_INTRO, ...heads.slice(0, kept), ...omitted(listed.length - kept)].join("\n").length;
|
|
114
|
+
let kept = listed.length;
|
|
115
|
+
while (kept > 0 && size(kept) > MAX_SERVERS_SECTION_CHARS)
|
|
116
|
+
kept--;
|
|
117
|
+
// Each description also takes a ": " separator.
|
|
118
|
+
const perServer = kept === 0
|
|
119
|
+
? 0
|
|
120
|
+
: Math.min(MAX_SERVER_DESCRIPTION_CHARS, Math.floor((MAX_SERVERS_SECTION_CHARS - size(kept)) / kept) - 2);
|
|
121
|
+
const lines = listed.slice(0, kept).map((server, index) => {
|
|
122
|
+
const summary = perServer > 0 ? truncate(serverSummary(server), perServer) : "";
|
|
123
|
+
return summary ? `${heads[index]}: ${summary}` : heads[index];
|
|
124
|
+
});
|
|
125
|
+
return [SERVERS_SECTION_INTRO, ...lines, ...omitted(listed.length - kept)].join("\n");
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Whether a codemode script needs the server: it names the server's namespace, or searches,
|
|
129
|
+
* enumerates, or describes tools or namespaces, which may name the server in other forms.
|
|
130
|
+
*/
|
|
131
|
+
function scriptNeedsServer(code, server) {
|
|
132
|
+
if (/\b(searchTools|describeNamespace|describeTool|ALL_TOOLS)\b/.test(code))
|
|
133
|
+
return true;
|
|
134
|
+
return code.includes(mcpNamespace(server));
|
|
135
|
+
}
|
|
54
136
|
/** Short state for lists and the startup report. `withError` appends the first line of a failure. */
|
|
55
137
|
function describeState(server, withError = true) {
|
|
56
138
|
if (!isEnabled(server))
|
|
@@ -121,6 +203,8 @@ export function createMcpExtension(options = {}) {
|
|
|
121
203
|
/** Working directory of the session, for stdio servers. */
|
|
122
204
|
let sessionCwd = process.cwd();
|
|
123
205
|
let credentials = options.credentials;
|
|
206
|
+
/** The session's model registry, which resolves `auth.provider` tokens. */
|
|
207
|
+
let modelRegistry;
|
|
124
208
|
let serverLog;
|
|
125
209
|
const openUrl = options.openUrl ?? openBrowser;
|
|
126
210
|
const updateConfig = options.updateConfig ?? ((entry, patch) => updateMcpServerConfig(entry.source, entry.name, patch));
|
|
@@ -142,9 +226,9 @@ export function createMcpExtension(options = {}) {
|
|
|
142
226
|
const registered = [];
|
|
143
227
|
const overriddenNames = [];
|
|
144
228
|
for (const { name, config, extensionPath } of pi.getMcpServers()) {
|
|
145
|
-
const configured = configuredEntries.find((entry) => entry.name === name);
|
|
229
|
+
const configured = configuredEntries.find((entry) => mcpNamespace(entry.name) === mcpNamespace(name));
|
|
146
230
|
if (configured) {
|
|
147
|
-
overriddenNames.push(`"${name}" registered by ${extensionPath} is overridden by ${configured.source}`);
|
|
231
|
+
overriddenNames.push(`"${name}" registered by ${extensionPath} is overridden by "${configured.name}" in ${configured.source}`);
|
|
148
232
|
continue;
|
|
149
233
|
}
|
|
150
234
|
registered.push({
|
|
@@ -171,17 +255,23 @@ export function createMcpExtension(options = {}) {
|
|
|
171
255
|
const registerTools = (connection) => {
|
|
172
256
|
const server = connection.entry.name;
|
|
173
257
|
const entry = findServer(server)?.entry ?? connection.entry;
|
|
174
|
-
const
|
|
258
|
+
const description = entry.config.description?.trim();
|
|
175
259
|
const namespace = {
|
|
176
|
-
name:
|
|
177
|
-
description
|
|
260
|
+
name: mcpNamespace(server),
|
|
261
|
+
...(description ? { description } : {}),
|
|
262
|
+
...(connection.instructions ? { instructions: connection.instructions } : {}),
|
|
178
263
|
};
|
|
179
264
|
const previous = serverTools.get(server) ?? new Set();
|
|
180
265
|
const current = new Set();
|
|
266
|
+
// Like Codex, all tools whose names sanitize to the same name get the hash suffix, so which
|
|
267
|
+
// one would keep the plain name does not depend on the order of the list.
|
|
268
|
+
const plain = [...new Set(connection.tools.map((tool) => tool.name))].map((tool) => createMcpToolName(server, tool));
|
|
181
269
|
const assignName = (tool, owner) => {
|
|
182
270
|
const name = createMcpToolName(server, tool, (candidate) => {
|
|
183
271
|
const existing = toolOwners.get(candidate);
|
|
184
|
-
return (existing !== undefined && existing !== owner) ||
|
|
272
|
+
return ((existing !== undefined && existing !== owner) ||
|
|
273
|
+
current.has(candidate) ||
|
|
274
|
+
plain.indexOf(candidate) !== plain.lastIndexOf(candidate));
|
|
185
275
|
});
|
|
186
276
|
toolOwners.set(name, owner);
|
|
187
277
|
current.add(name);
|
|
@@ -232,7 +322,7 @@ export function createMcpExtension(options = {}) {
|
|
|
232
322
|
*/
|
|
233
323
|
const syncResourceTools = () => {
|
|
234
324
|
const exposures = new Set(serversWithResources().map((server) => exposureOf(server.entry)));
|
|
235
|
-
const exposure = ["direct", "codemode", "
|
|
325
|
+
const exposure = ["direct", "codemode", "deferred"].find((candidate) => exposures.has(candidate));
|
|
236
326
|
const next = exposure ?? "hidden";
|
|
237
327
|
if (next === resourceToolsExposure || (resourceToolsExposure === undefined && next === "hidden"))
|
|
238
328
|
return;
|
|
@@ -249,21 +339,19 @@ export function createMcpExtension(options = {}) {
|
|
|
249
339
|
/**
|
|
250
340
|
* Tools that are not declared to the model are reached through the codemode tool (scripts call
|
|
251
341
|
* them) or the tool_search tool (it declares them). Either reaches every such tool. Activate the
|
|
252
|
-
* one the tools' exposure asks for: codemode for `codemode`
|
|
253
|
-
*
|
|
342
|
+
* one the tools' exposure asks for: codemode for `codemode` unless `autoEnableCodemode` is false,
|
|
343
|
+
* tool_search for `deferred`.
|
|
254
344
|
*/
|
|
255
345
|
const ensureDiscoveryActive = (ctx) => {
|
|
346
|
+
// From the config, so the tool is active before the servers connect. Resource tools share
|
|
347
|
+
// their server's exposure.
|
|
256
348
|
const exposures = new Set();
|
|
257
|
-
for (const
|
|
258
|
-
if (
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (connection.hasResources)
|
|
262
|
-
exposures.add(exposureOf(entry));
|
|
263
|
-
for (const tool of connection.tools)
|
|
264
|
-
exposures.add(getMcpToolExposure(entry.config, tool.name));
|
|
349
|
+
for (const server of servers) {
|
|
350
|
+
if (isEnabled(server))
|
|
351
|
+
for (const exposure of configuredExposures(server.entry))
|
|
352
|
+
exposures.add(exposure);
|
|
265
353
|
}
|
|
266
|
-
const needsCodemode = exposures.has("codemode")
|
|
354
|
+
const needsCodemode = exposures.has("codemode");
|
|
267
355
|
const needsToolSearch = exposures.has("deferred");
|
|
268
356
|
if (!needsCodemode && !needsToolSearch)
|
|
269
357
|
return;
|
|
@@ -326,6 +414,7 @@ export function createMcpExtension(options = {}) {
|
|
|
326
414
|
cwd: sessionCwd,
|
|
327
415
|
createTransport: options.createTransport ?? runtime.createDefaultTransport,
|
|
328
416
|
credentials: getCredentials(runtime),
|
|
417
|
+
providerToken: async (provider) => modelRegistry?.getApiKeyForProvider(provider),
|
|
329
418
|
log: getServerLog(runtime),
|
|
330
419
|
onTools: registerTools,
|
|
331
420
|
onChange: onConnectionChange,
|
|
@@ -334,6 +423,40 @@ export function createMcpExtension(options = {}) {
|
|
|
334
423
|
emitChange();
|
|
335
424
|
return connection;
|
|
336
425
|
};
|
|
426
|
+
/**
|
|
427
|
+
* Connect the server in the background. `server.ready` settles when it connected or failed;
|
|
428
|
+
* failures show in its state. It starts after `after`; `isCurrent` stops it when the session ended
|
|
429
|
+
* meanwhile. The returned promise rejects when the MCP runtime cannot be loaded.
|
|
430
|
+
*/
|
|
431
|
+
const startConnection = (server, isCurrent, after) => {
|
|
432
|
+
const ready = (async () => {
|
|
433
|
+
await after;
|
|
434
|
+
if (!isCurrent())
|
|
435
|
+
return;
|
|
436
|
+
const connection = await createConnection(server);
|
|
437
|
+
if (!isCurrent())
|
|
438
|
+
return;
|
|
439
|
+
await connection.getClient().catch(() => undefined);
|
|
440
|
+
})();
|
|
441
|
+
server.ready = ready.catch(() => undefined);
|
|
442
|
+
return ready;
|
|
443
|
+
};
|
|
444
|
+
/** Wait for servers still connecting, until they settle or `signal` aborts. */
|
|
445
|
+
const waitForServers = async (waiting, signal) => {
|
|
446
|
+
const ready = waiting.flatMap((server) => (server.ready ? [server.ready] : []));
|
|
447
|
+
if (ready.length === 0 || signal?.aborted)
|
|
448
|
+
return;
|
|
449
|
+
let onAbort;
|
|
450
|
+
await Promise.race([
|
|
451
|
+
Promise.all(ready),
|
|
452
|
+
new Promise((resolve) => {
|
|
453
|
+
onAbort = () => resolve();
|
|
454
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
455
|
+
}),
|
|
456
|
+
]);
|
|
457
|
+
if (onAbort)
|
|
458
|
+
signal?.removeEventListener("abort", onAbort);
|
|
459
|
+
};
|
|
337
460
|
/**
|
|
338
461
|
* One message for everything that needs the user after startup, or only for `only`, servers
|
|
339
462
|
* that connected later.
|
|
@@ -429,8 +552,7 @@ export function createMcpExtension(options = {}) {
|
|
|
429
552
|
await connection?.close();
|
|
430
553
|
return undefined;
|
|
431
554
|
}
|
|
432
|
-
|
|
433
|
-
await connection.getClient().catch(() => undefined);
|
|
555
|
+
await startConnection(server, () => true);
|
|
434
556
|
return undefined;
|
|
435
557
|
};
|
|
436
558
|
const setExposure = (server, exposure) => {
|
|
@@ -711,6 +833,7 @@ export function createMcpExtension(options = {}) {
|
|
|
711
833
|
warnedUnreachable = false;
|
|
712
834
|
waitedForStartup = false;
|
|
713
835
|
sessionCwd = ctx.cwd;
|
|
836
|
+
modelRegistry = ctx.modelRegistry;
|
|
714
837
|
const current = ++generation;
|
|
715
838
|
sessionActive = true;
|
|
716
839
|
configuredEntries = loaded.servers;
|
|
@@ -718,6 +841,9 @@ export function createMcpExtension(options = {}) {
|
|
|
718
841
|
overridden = registered.overridden;
|
|
719
842
|
servers = [...loaded.servers.map((entry) => ({ entry })), ...registered.servers];
|
|
720
843
|
emitChange();
|
|
844
|
+
// Codemode or tool_search is activated from the config: the first prompt does not wait for
|
|
845
|
+
// servers whose tools are not declared to the model, and scripts or searches wait for them.
|
|
846
|
+
ensureDiscoveryActive(ctx);
|
|
721
847
|
const enabled = servers.filter(isEnabled);
|
|
722
848
|
if (enabled.length === 0) {
|
|
723
849
|
reportProblems(ctx);
|
|
@@ -725,19 +851,12 @@ export function createMcpExtension(options = {}) {
|
|
|
725
851
|
}
|
|
726
852
|
// The MCP client loads only now, so sessions without servers never pay for it. Waiting one
|
|
727
853
|
// event loop turn lets the first render happen before loading and connecting.
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
if (current !== generation)
|
|
735
|
-
return;
|
|
736
|
-
await Promise.allSettled(started.map((connection) => connection.getClient()));
|
|
737
|
-
if (current !== generation)
|
|
738
|
-
return;
|
|
739
|
-
ensureDiscoveryActive(ctx);
|
|
740
|
-
reportProblems(ctx);
|
|
854
|
+
const runtime = new Promise((resolve) => setImmediate(resolve)).then(() => loadMcpRuntime());
|
|
855
|
+
const isCurrent = () => current === generation;
|
|
856
|
+
pending = Promise.all(enabled.map((server) => startConnection(server, isCurrent, runtime)))
|
|
857
|
+
.then(() => {
|
|
858
|
+
if (isCurrent())
|
|
859
|
+
reportProblems(ctx);
|
|
741
860
|
})
|
|
742
861
|
.catch((error) => {
|
|
743
862
|
// The session may have been disposed meanwhile, which makes ctx stale.
|
|
@@ -747,16 +866,19 @@ export function createMcpExtension(options = {}) {
|
|
|
747
866
|
catch { }
|
|
748
867
|
});
|
|
749
868
|
});
|
|
750
|
-
// The first prompt waits for
|
|
751
|
-
// indefinitely: a slow or hanging server must not hold up the
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
if (
|
|
869
|
+
// The first prompt waits for servers whose tools are declared to the model, so they are declared
|
|
870
|
+
// in its first request, but not indefinitely: a slow or hanging server must not hold up the
|
|
871
|
+
// prompt. Other servers are waited for when a script or search needs them (below).
|
|
872
|
+
const waitForDirectServers = async (ctx) => {
|
|
873
|
+
if (waitedForStartup)
|
|
755
874
|
return;
|
|
756
875
|
waitedForStartup = true;
|
|
876
|
+
const ready = servers.flatMap((server) => isEnabled(server) && hasDirectTools(server.entry) && server.ready ? [server.ready] : []);
|
|
877
|
+
if (ready.length === 0)
|
|
878
|
+
return;
|
|
757
879
|
let timer;
|
|
758
880
|
const finished = await Promise.race([
|
|
759
|
-
|
|
881
|
+
Promise.all(ready).then(() => true),
|
|
760
882
|
new Promise((resolve) => {
|
|
761
883
|
timer = setTimeout(() => resolve(false), startupWaitMs);
|
|
762
884
|
}),
|
|
@@ -765,6 +887,38 @@ export function createMcpExtension(options = {}) {
|
|
|
765
887
|
if (!finished) {
|
|
766
888
|
ctx.ui.notify("MCP servers are still connecting; their tools become available once connected.", "info");
|
|
767
889
|
}
|
|
890
|
+
};
|
|
891
|
+
// Every prompt lists the servers in the `mcp_servers` section as they are when it starts. Pi
|
|
892
|
+
// appends the section to the conversation when it changed, for example after a server connected.
|
|
893
|
+
pi.on("before_agent_start", async (event, ctx) => {
|
|
894
|
+
await waitForDirectServers(ctx);
|
|
895
|
+
const { sections } = event.systemPromptOptions;
|
|
896
|
+
const section = renderServersSection(servers);
|
|
897
|
+
if (section)
|
|
898
|
+
sections[MCP_SERVERS_SECTION] = section;
|
|
899
|
+
else
|
|
900
|
+
delete sections[MCP_SERVERS_SECTION];
|
|
901
|
+
});
|
|
902
|
+
// A codemode script waits for the servers it names, or for every server when it searches or
|
|
903
|
+
// enumerates tools, so their tools are registered before the script runs. tool_search and the
|
|
904
|
+
// resource tools reach every server, so they wait for all of them.
|
|
905
|
+
pi.on("tool_call", async (event, ctx) => {
|
|
906
|
+
const tool = pi.getAllTools().find((candidate) => candidate.name === event.toolName);
|
|
907
|
+
if (!tool)
|
|
908
|
+
return;
|
|
909
|
+
const pendingServers = servers.filter((server) => isEnabled(server) && server.connection?.state !== "connected" && server.ready);
|
|
910
|
+
if (pendingServers.length === 0)
|
|
911
|
+
return;
|
|
912
|
+
let waiting = [];
|
|
913
|
+
if (isCodemodeTool(tool)) {
|
|
914
|
+
const { code } = event.input;
|
|
915
|
+
const source = typeof code === "string" ? code : "";
|
|
916
|
+
waiting = pendingServers.filter((server) => scriptNeedsServer(source, server.entry.name));
|
|
917
|
+
}
|
|
918
|
+
else if (isToolSearchTool(tool) || RESOURCE_TOOL_NAMES.has(tool.name)) {
|
|
919
|
+
waiting = pendingServers;
|
|
920
|
+
}
|
|
921
|
+
await waitForServers(waiting, ctx.signal);
|
|
768
922
|
});
|
|
769
923
|
// Pick up sign-ins done outside the session, such as `pi mcp login` run by the agent.
|
|
770
924
|
pi.on("turn_start", async (_event, ctx) => {
|
|
@@ -788,17 +942,17 @@ export function createMcpExtension(options = {}) {
|
|
|
788
942
|
const added = registered.servers.filter((server) => !findServer(server.entry.name));
|
|
789
943
|
servers.push(...added);
|
|
790
944
|
emitChange();
|
|
945
|
+
ensureDiscoveryActive(ctx);
|
|
791
946
|
await Promise.all(removed.map((server) => server.connection?.close()));
|
|
792
947
|
const connecting = added.filter(isEnabled);
|
|
793
948
|
if (current !== generation || connecting.length === 0)
|
|
794
949
|
return;
|
|
795
950
|
try {
|
|
796
|
-
|
|
951
|
+
await Promise.all(connecting.map((server) => startConnection(server, () => current === generation)));
|
|
797
952
|
if (current !== generation) {
|
|
798
|
-
await Promise.all(
|
|
953
|
+
await Promise.all(connecting.map((server) => server.connection?.close()));
|
|
799
954
|
return;
|
|
800
955
|
}
|
|
801
|
-
await Promise.allSettled(started.map((connection) => connection.getClient()));
|
|
802
956
|
}
|
|
803
957
|
catch (error) {
|
|
804
958
|
ctx.ui.notify(`MCP failed to load: ${errorMessage(error)}`, "error");
|
|
@@ -806,7 +960,6 @@ export function createMcpExtension(options = {}) {
|
|
|
806
960
|
}
|
|
807
961
|
if (current !== generation)
|
|
808
962
|
return;
|
|
809
|
-
ensureDiscoveryActive(ctx);
|
|
810
963
|
reportProblems(ctx, connecting);
|
|
811
964
|
});
|
|
812
965
|
pi.on("session_shutdown", async () => {
|