mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-25 10:42:45 +00:00
fix(pi): give Pi's built-in MCP the agent's MCP servers (#5762)
Pi 0.99 ships MCP as a built-in extension. Paseo only recognized pi-mcp-adapter's /mcp command, so a Pi agent without the adapter started without Paseo's MCP servers. Pi 0.99 also rejects the adapter's --mcp-config flag, so the servers are now registered with pi.registerMcpServer from Paseo's per-session extension. Sessions that load pi-mcp-adapter keep the --mcp-config path. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> Co-authored-by: ArietidsZ <69024461+ArietidsZ@users.noreply.github.com>
This commit is contained in:
co-authored by
ArietidsZ
parent
8332f4d1ff
commit
4417d7c47a
+3
-1
@@ -103,7 +103,9 @@ Paseo's per-agent and daemon-wide system prompts are appended by its generated P
|
||||
|
||||
Pi model records expose input capabilities through `model.input`. Only send raw RPC `images` when the current model explicitly includes `"image"` in that list. Text-only Pi/OMP models reject image content and persist the rejected image in JSONL history, so image prompts for those models must be materialized to a local file and passed as a text path hint instead.
|
||||
|
||||
Pi MCP support depends on the open-source `pi-mcp-adapter` extension being loaded for the agent cwd. Probe with Pi RPC `get_commands`; the adapter registers an extension command named `mcp` (often with `sourceInfo.source` containing `pi-mcp-adapter`). When Paseo injects MCP servers into Pi, write a per-agent MCP config and pass it with `--mcp-config` instead of modifying user or project MCP files. Because that flag replaces the Pi global config layer, preserve the existing `<Pi agent dir>/mcp.json` in the generated file before overlaying injected servers. For local HTTP servers such as Paseo's own `/mcp/agents` endpoint, explicitly disable adapter OAuth (`auth: false`, `oauth: false`) in the generated config.
|
||||
Probe Pi MCP support with Pi RPC `get_commands` for the agent cwd. Pi 0.99 and later ship MCP as a built-in extension, which registers an extension command named `mcp` with `sourceInfo.path` `builtin:mcp`. Register injected servers through `pi.registerMcpServer` in Paseo's generated extension; the built-in extension rejects SSE servers and names with characters other than letters, digits, `_`, and `-`, and a server of the same name in Pi's `mcp.json` takes precedence.
|
||||
|
||||
The open-source `pi-mcp-adapter` extension replaces the built-in one and registers its own `mcp` command (often with `sourceInfo.source` containing `pi-mcp-adapter`). When it is loaded, write a per-agent MCP config and pass it with `--mcp-config` instead of modifying user or project MCP files. Because that flag replaces the Pi global config layer, preserve the existing `<Pi agent dir>/mcp.json` in the generated file before overlaying injected servers. For local HTTP servers such as Paseo's own `/mcp/agents` endpoint, explicitly disable adapter OAuth (`auth: false`, `oauth: false`) in the generated config.
|
||||
|
||||
Pi control-plane RPCs wait 60 seconds by default. Override `params.rpcTimeoutMs` when extension or MCP startup on a slow host needs more time. Timeout errors name the pending RPC phase and report both elapsed time and the configured deadline. This setting does not govern long-running Pi compaction or Pi extension UI results. See [OMP profiles and Pi-compatible forks](custom-providers.md#omp-profiles-and-pi-compatible-forks) for OMP startup and RPC deadlines.
|
||||
|
||||
|
||||
@@ -160,6 +160,7 @@ function parseEntryCapture(notification: string): unknown {
|
||||
|
||||
async function loadPaseoExtensionListeners(
|
||||
extensionPath: string,
|
||||
registerMcpServer: (name: string, config: unknown) => void = () => undefined,
|
||||
): Promise<Map<string, PaseoExtensionListener>> {
|
||||
const listeners = new Map<string, PaseoExtensionListener>();
|
||||
const extension = (await import(pathToFileURL(extensionPath).href)) as {
|
||||
@@ -167,16 +168,26 @@ async function loadPaseoExtensionListeners(
|
||||
on: (event: string, listener: PaseoExtensionListener) => void;
|
||||
events: { on: () => void };
|
||||
registerCommand: () => void;
|
||||
registerMcpServer: (name: string, config: unknown) => void;
|
||||
}) => void;
|
||||
};
|
||||
extension.default({
|
||||
on: (event, listener) => listeners.set(event, listener),
|
||||
events: { on: () => undefined },
|
||||
registerCommand: () => undefined,
|
||||
registerMcpServer,
|
||||
});
|
||||
return listeners;
|
||||
}
|
||||
|
||||
async function readRegisteredMcpServers(extensionPath: string): Promise<Record<string, unknown>> {
|
||||
const servers: Record<string, unknown> = {};
|
||||
await loadPaseoExtensionListeners(extensionPath, (name, config) => {
|
||||
servers[name] = config;
|
||||
});
|
||||
return servers;
|
||||
}
|
||||
|
||||
async function applyPaseoExtensionSystemPrompt(
|
||||
extensionPath: string,
|
||||
systemPrompt: string,
|
||||
@@ -3129,6 +3140,102 @@ describe("PiRpcAgentClient", () => {
|
||||
).rejects.toThrow(`Failed to parse Pi MCP config: ${configPath}`);
|
||||
});
|
||||
|
||||
test("registers MCP servers through Pi's built-in MCP when pi-mcp-adapter is not loaded", async () => {
|
||||
const pi = new FakePi();
|
||||
pi.queueCommands([
|
||||
{
|
||||
name: "mcp",
|
||||
source: "extension",
|
||||
sourceInfo: { path: "builtin:mcp", source: "builtin" },
|
||||
},
|
||||
]);
|
||||
const session = await createClient(pi).createSession(
|
||||
createConfig({
|
||||
mcpServers: {
|
||||
paseo: {
|
||||
type: "http",
|
||||
url: "http://127.0.0.1:6767/mcp/agents?callerAgentId=agent-1",
|
||||
headers: { Authorization: "Bearer token" },
|
||||
},
|
||||
local: { type: "stdio", command: "node", args: ["server.js"], env: { KEY: "`${x}`" } },
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(session.capabilities.supportsMcpServers).toBe(true);
|
||||
const actualLaunch = pi.recordedLaunches[1]!;
|
||||
expect(actualLaunch.argv).not.toContain("--mcp-config");
|
||||
const extensionPath = actualLaunch.extensionPaths[0]!;
|
||||
expect(await readRegisteredMcpServers(extensionPath)).toEqual({
|
||||
paseo: {
|
||||
url: "http://127.0.0.1:6767/mcp/agents?callerAgentId=agent-1",
|
||||
headers: { Authorization: "Bearer token" },
|
||||
},
|
||||
local: { command: "node", args: ["server.js"], env: { KEY: "`${x}`" } },
|
||||
});
|
||||
|
||||
await session.close();
|
||||
expect(existsSync(extensionPath)).toBe(false);
|
||||
});
|
||||
|
||||
test("registers the resumed session's MCP servers through Pi's built-in MCP", async () => {
|
||||
const pi = new FakePi();
|
||||
const client = createClient(pi);
|
||||
const original = await client.createSession(createConfig());
|
||||
const persistence = original.describePersistence()!;
|
||||
await original.close();
|
||||
|
||||
pi.queueCommands([{ name: "mcp", source: "extension", sourceInfo: { path: "builtin:mcp" } }]);
|
||||
const session = await client.resumeSession(persistence, {
|
||||
mcpServers: {
|
||||
paseo: { type: "http", url: "http://127.0.0.1:7777/mcp/agents?callerAgentId=agent-2" },
|
||||
},
|
||||
});
|
||||
onTestFinished(() => session.close());
|
||||
|
||||
expect(session.capabilities.supportsMcpServers).toBe(true);
|
||||
const actualLaunch = pi.recordedLaunches.at(-1)!;
|
||||
expect(actualLaunch.session).toBe(persistence.nativeHandle);
|
||||
expect(actualLaunch.argv).not.toContain("--mcp-config");
|
||||
expect(await readRegisteredMcpServers(actualLaunch.extensionPaths[0]!)).toEqual({
|
||||
paseo: { url: "http://127.0.0.1:7777/mcp/agents?callerAgentId=agent-2" },
|
||||
});
|
||||
});
|
||||
|
||||
test("uses pi-mcp-adapter when it replaces Pi's built-in MCP", async () => {
|
||||
const agentDir = mkdtempSync(path.join(tmpdir(), "paseo-pi-agent-"));
|
||||
onTestFinished(() => rmSync(agentDir, { recursive: true, force: true }));
|
||||
const pi = new FakePi();
|
||||
pi.queueCommands([
|
||||
{ name: "mcp", source: "extension", sourceInfo: { path: "builtin:mcp" } },
|
||||
{ name: "mcp:1", source: "extension", sourceInfo: { source: "npm:pi-mcp-adapter" } },
|
||||
]);
|
||||
const session = await createClient(pi).createSession(
|
||||
createConfig({
|
||||
mcpServers: { paseo: { type: "http", url: "http://127.0.0.1:6767/mcp/agents" } },
|
||||
}),
|
||||
{ env: { PI_CODING_AGENT_DIR: agentDir } },
|
||||
);
|
||||
onTestFinished(() => session.close());
|
||||
|
||||
const actualLaunch = pi.recordedLaunches[1]!;
|
||||
expect(actualLaunch.argv).toContain("--mcp-config");
|
||||
expect(await readRegisteredMcpServers(actualLaunch.extensionPaths[0]!)).toEqual({});
|
||||
});
|
||||
|
||||
test.each([
|
||||
{ name: "legacy", config: { type: "sse" as const, url: "https://example.com/sse" } },
|
||||
{ name: "foo.bar", config: { type: "http" as const, url: "https://example.com/mcp" } },
|
||||
])("rejects $name, which Pi's built-in MCP cannot register", async ({ name, config }) => {
|
||||
const pi = new FakePi();
|
||||
pi.queueCommands([{ name: "mcp", source: "extension", sourceInfo: { path: "builtin:mcp" } }]);
|
||||
|
||||
await expect(
|
||||
createClient(pi).createSession(createConfig({ mcpServers: { [name]: config } })),
|
||||
).rejects.toThrow(name);
|
||||
expect(pi.recordedLaunches).toHaveLength(1);
|
||||
});
|
||||
|
||||
test("does not pass MCP config when pi-mcp-adapter is not loaded", async () => {
|
||||
const pi = new FakePi();
|
||||
pi.queueCommands([]);
|
||||
|
||||
@@ -262,13 +262,17 @@ interface PiMcpServerConfig {
|
||||
env?: Record<string, string>;
|
||||
url?: string;
|
||||
headers?: Record<string, string>;
|
||||
auth?: false;
|
||||
oauth?: false;
|
||||
}
|
||||
|
||||
interface PiMcpConfigFile {
|
||||
path: string;
|
||||
cleanup: () => void;
|
||||
// Pi's built-in MCP extension takes servers from Paseo's extension; pi-mcp-adapter replaces it
|
||||
// and takes them from a --mcp-config file.
|
||||
type PiMcpInjection =
|
||||
| { kind: "builtin"; servers: Record<string, PiMcpServerConfig> }
|
||||
| { kind: "adapter"; configFile: PiTempFile };
|
||||
|
||||
interface PiPaseoExtensionOptions {
|
||||
systemPrompt?: string;
|
||||
mcpServers?: Record<string, PiMcpServerConfig>;
|
||||
}
|
||||
|
||||
interface PiTempFile {
|
||||
@@ -487,7 +491,7 @@ function buildResumeStartInput(input: {
|
||||
resumeConfig: PiResumeConfig;
|
||||
sessionFile: string;
|
||||
launchContext: AgentLaunchContext | undefined;
|
||||
mcpConfig: PiMcpConfigFile | null;
|
||||
mcpConfigFile: PiTempFile | null;
|
||||
paseoExtension: PiTempFile | null;
|
||||
}): PiStartSessionInput {
|
||||
return {
|
||||
@@ -496,7 +500,7 @@ function buildResumeStartInput(input: {
|
||||
session: input.sessionFile,
|
||||
model: input.resumeConfig.model,
|
||||
thinkingOptionId: normalizePiThinkingOption(input.resumeConfig.thinkingOptionId) ?? undefined,
|
||||
mcpConfigPath: input.mcpConfig?.path,
|
||||
mcpConfigPath: input.mcpConfigFile?.path,
|
||||
extensionPaths: input.paseoExtension ? [input.paseoExtension.path] : undefined,
|
||||
};
|
||||
}
|
||||
@@ -513,11 +517,36 @@ function toPiMcpConfig(config: McpServerConfig): PiMcpServerConfig {
|
||||
return {
|
||||
url: config.url,
|
||||
...(config.headers ? { headers: config.headers } : {}),
|
||||
auth: false,
|
||||
oauth: false,
|
||||
};
|
||||
}
|
||||
|
||||
function toPiMcpAdapterConfig(
|
||||
config: McpServerConfig,
|
||||
): PiMcpServerConfig & { auth?: false; oauth?: false } {
|
||||
const piConfig = toPiMcpConfig(config);
|
||||
return config.type === "stdio" ? piConfig : { ...piConfig, auth: false, oauth: false };
|
||||
}
|
||||
|
||||
function toPiBuiltinMcpServers(
|
||||
servers: Record<string, McpServerConfig>,
|
||||
): Record<string, PiMcpServerConfig> {
|
||||
const piServers: Record<string, PiMcpServerConfig> = {};
|
||||
for (const [name, config] of Object.entries(servers)) {
|
||||
if (!/^[A-Za-z0-9_-]+$/.test(name)) {
|
||||
throw new Error(
|
||||
`Pi's built-in MCP cannot register server "${name}": names may only contain letters, digits, "_", and "-".`,
|
||||
);
|
||||
}
|
||||
if (config.type === "sse") {
|
||||
throw new Error(
|
||||
`Pi's built-in MCP cannot register SSE server "${name}": use streamable HTTP or load pi-mcp-adapter.`,
|
||||
);
|
||||
}
|
||||
piServers[name] = toPiMcpConfig(config);
|
||||
}
|
||||
return piServers;
|
||||
}
|
||||
|
||||
function resolvePiAgentDir(env: Record<string, string> | undefined): string {
|
||||
const configured = env?.PI_CODING_AGENT_DIR?.trim() || process.env.PI_CODING_AGENT_DIR?.trim();
|
||||
if (!configured) {
|
||||
@@ -558,7 +587,7 @@ function createPiMcpConfigFile(
|
||||
options?: {
|
||||
piGlobalConfigEnv?: Record<string, string>;
|
||||
},
|
||||
): PiMcpConfigFile {
|
||||
): PiTempFile {
|
||||
const globalConfig = options?.piGlobalConfigEnv
|
||||
? readPiGlobalMcpConfig(options.piGlobalConfigEnv)
|
||||
: {};
|
||||
@@ -570,7 +599,7 @@ function createPiMcpConfigFile(
|
||||
}
|
||||
const mcpServers: Record<string, unknown> = { ...configuredServers };
|
||||
for (const [name, serverConfig] of Object.entries(servers)) {
|
||||
mcpServers[name] = toPiMcpConfig(serverConfig);
|
||||
mcpServers[name] = toPiMcpAdapterConfig(serverConfig);
|
||||
}
|
||||
|
||||
const dir = mkdtempSync(join(tmpdir(), "paseo-pi-mcp-"));
|
||||
@@ -587,7 +616,10 @@ function createPiMcpConfigFile(
|
||||
};
|
||||
}
|
||||
|
||||
function createPiPaseoExtensionFile(systemPrompt?: string): PiTempFile {
|
||||
function createPiPaseoExtensionFile({
|
||||
systemPrompt,
|
||||
mcpServers = {},
|
||||
}: PiPaseoExtensionOptions): PiTempFile {
|
||||
const dir = mkdtempSync(join(tmpdir(), "paseo-pi-extension-"));
|
||||
const filePath = join(dir, "paseo-integration.mjs");
|
||||
writeFileSync(
|
||||
@@ -647,6 +679,9 @@ function createPiPaseoExtensionFile(systemPrompt?: string): PiTempFile {
|
||||
}
|
||||
|
||||
export default function paseoIntegration(pi) {
|
||||
for (const [name, config] of Object.entries(${JSON.stringify(mcpServers)})) {
|
||||
pi.registerMcpServer(name, config);
|
||||
}
|
||||
const submittedUserMessages = [];
|
||||
${piExtensionRuntimeBridge}
|
||||
|
||||
@@ -728,7 +763,8 @@ function createPiPaseoExtensionFile(systemPrompt?: string): PiTempFile {
|
||||
});
|
||||
}
|
||||
`.trimStart(),
|
||||
"utf8",
|
||||
// MCP server headers and env can carry credentials.
|
||||
{ encoding: "utf8", mode: 0o600 },
|
||||
);
|
||||
return {
|
||||
path: filePath,
|
||||
@@ -758,6 +794,14 @@ function isPiMcpAdapterCommand(command: PiRpcSlashCommand): boolean {
|
||||
return JSON.stringify(command.sourceInfo).includes("pi-mcp-adapter");
|
||||
}
|
||||
|
||||
function isPiBuiltinMcpCommand(command: PiRpcSlashCommand): boolean {
|
||||
return (
|
||||
command.source === "extension" &&
|
||||
command.name === "mcp" &&
|
||||
command.sourceInfo?.path === "builtin:mcp"
|
||||
);
|
||||
}
|
||||
|
||||
function withPiCapabilities(supportsMcpServers: boolean): AgentCapabilityFlags {
|
||||
return {
|
||||
...PI_CAPABILITIES,
|
||||
@@ -2445,10 +2489,12 @@ export class PiRpcAgentClient implements AgentClient {
|
||||
...this.runtimeSettings?.env,
|
||||
...launchContext?.env,
|
||||
};
|
||||
const mcpConfig = await this.prepareMcpConfig(config.cwd, config.mcpServers, mcpEnv);
|
||||
const paseoExtension = createPiPaseoExtensionFile(
|
||||
composeSystemPromptParts(config.systemPrompt, config.daemonAppendSystemPrompt),
|
||||
);
|
||||
const mcp = await this.prepareMcpInjection(config.cwd, config.mcpServers, mcpEnv);
|
||||
const mcpConfigFile = mcp?.kind === "adapter" ? mcp.configFile : null;
|
||||
const paseoExtension = createPiPaseoExtensionFile({
|
||||
systemPrompt: composeSystemPromptParts(config.systemPrompt, config.daemonAppendSystemPrompt),
|
||||
mcpServers: mcp?.kind === "builtin" ? mcp.servers : undefined,
|
||||
});
|
||||
let runtimeSession: PiRuntimeSession;
|
||||
try {
|
||||
runtimeSession = await this.runtime.startSession({
|
||||
@@ -2457,11 +2503,11 @@ export class PiRpcAgentClient implements AgentClient {
|
||||
thinkingOptionId: normalizePiThinkingOption(config.thinkingOptionId) ?? undefined,
|
||||
noSession: config.internal === true,
|
||||
env: launchContext?.env,
|
||||
mcpConfigPath: mcpConfig?.path,
|
||||
mcpConfigPath: mcpConfigFile?.path,
|
||||
extensionPaths: paseoExtension ? [paseoExtension.path] : undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
mcpConfig?.cleanup();
|
||||
mcpConfigFile?.cleanup();
|
||||
paseoExtension?.cleanup();
|
||||
throw error;
|
||||
}
|
||||
@@ -2470,15 +2516,15 @@ export class PiRpcAgentClient implements AgentClient {
|
||||
runtimeSession,
|
||||
config,
|
||||
initialState: await runtimeSession.getState(),
|
||||
capabilities: capabilitiesForSession(mcpConfig !== null),
|
||||
cleanup: combineCleanup([mcpConfig?.cleanup, paseoExtension?.cleanup]),
|
||||
capabilities: capabilitiesForSession(mcp !== null),
|
||||
cleanup: combineCleanup([mcpConfigFile?.cleanup, paseoExtension?.cleanup]),
|
||||
extensionTimeoutMs: this.providerParams.extensionTimeoutMs,
|
||||
logger: this.logger,
|
||||
usagePollScheduler: this.usagePollScheduler,
|
||||
});
|
||||
} catch (error) {
|
||||
await runtimeSession.close().catch(() => undefined);
|
||||
mcpConfig?.cleanup();
|
||||
mcpConfigFile?.cleanup();
|
||||
paseoExtension?.cleanup();
|
||||
throw error;
|
||||
}
|
||||
@@ -2501,17 +2547,19 @@ export class PiRpcAgentClient implements AgentClient {
|
||||
...this.runtimeSettings?.env,
|
||||
...launchContext?.env,
|
||||
};
|
||||
const mcpConfig = await this.prepareMcpConfig(
|
||||
const mcp = await this.prepareMcpInjection(
|
||||
resumeConfig.cwd,
|
||||
resumeConfig.config.mcpServers,
|
||||
mcpEnv,
|
||||
);
|
||||
const paseoExtension = createPiPaseoExtensionFile(
|
||||
composeSystemPromptParts(
|
||||
const mcpConfigFile = mcp?.kind === "adapter" ? mcp.configFile : null;
|
||||
const paseoExtension = createPiPaseoExtensionFile({
|
||||
systemPrompt: composeSystemPromptParts(
|
||||
resumeConfig.config.systemPrompt,
|
||||
resumeConfig.config.daemonAppendSystemPrompt,
|
||||
),
|
||||
);
|
||||
mcpServers: mcp?.kind === "builtin" ? mcp.servers : undefined,
|
||||
});
|
||||
let runtimeSession: PiRuntimeSession;
|
||||
try {
|
||||
runtimeSession = await this.runtime.startSession(
|
||||
@@ -2519,12 +2567,12 @@ export class PiRpcAgentClient implements AgentClient {
|
||||
resumeConfig,
|
||||
sessionFile,
|
||||
launchContext,
|
||||
mcpConfig,
|
||||
mcpConfigFile,
|
||||
paseoExtension,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
mcpConfig?.cleanup();
|
||||
mcpConfigFile?.cleanup();
|
||||
paseoExtension?.cleanup();
|
||||
throw error;
|
||||
}
|
||||
@@ -2533,15 +2581,15 @@ export class PiRpcAgentClient implements AgentClient {
|
||||
runtimeSession,
|
||||
config: resumeConfig.config,
|
||||
initialState: await runtimeSession.getState(),
|
||||
capabilities: capabilitiesForSession(mcpConfig !== null),
|
||||
cleanup: combineCleanup([mcpConfig?.cleanup, paseoExtension?.cleanup]),
|
||||
capabilities: capabilitiesForSession(mcp !== null),
|
||||
cleanup: combineCleanup([mcpConfigFile?.cleanup, paseoExtension?.cleanup]),
|
||||
extensionTimeoutMs: this.providerParams.extensionTimeoutMs,
|
||||
logger: this.logger,
|
||||
usagePollScheduler: this.usagePollScheduler,
|
||||
});
|
||||
} catch (error) {
|
||||
await runtimeSession.close().catch(() => undefined);
|
||||
mcpConfig?.cleanup();
|
||||
mcpConfigFile?.cleanup();
|
||||
paseoExtension?.cleanup();
|
||||
throw error;
|
||||
}
|
||||
@@ -2656,33 +2704,47 @@ export class PiRpcAgentClient implements AgentClient {
|
||||
}
|
||||
}
|
||||
|
||||
private async prepareMcpConfig(
|
||||
private async prepareMcpInjection(
|
||||
cwd: string,
|
||||
servers: Record<string, McpServerConfig> | undefined,
|
||||
env: Record<string, string> | undefined,
|
||||
): Promise<PiMcpConfigFile | null> {
|
||||
): Promise<PiMcpInjection | null> {
|
||||
if (!servers || Object.keys(servers).length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (!(await this.detectMcpAdapter(cwd, env))) {
|
||||
return null;
|
||||
const mcpSupport = await this.detectMcpSupport(cwd, env);
|
||||
if (mcpSupport === "builtin") {
|
||||
return { kind: "builtin", servers: toPiBuiltinMcpServers(servers) };
|
||||
}
|
||||
return createPiMcpConfigFile(servers, { piGlobalConfigEnv: env });
|
||||
if (mcpSupport === "adapter") {
|
||||
return {
|
||||
kind: "adapter",
|
||||
configFile: createPiMcpConfigFile(servers, { piGlobalConfigEnv: env }),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async detectMcpAdapter(cwd: string, env?: Record<string, string>): Promise<boolean> {
|
||||
private async detectMcpSupport(
|
||||
cwd: string,
|
||||
env?: Record<string, string>,
|
||||
): Promise<PiMcpInjection["kind"] | null> {
|
||||
const runtimeSession = await this.runtime.startSession({ cwd, env }).catch((error) => {
|
||||
this.logger.debug({ err: error, cwd }, "Pi MCP adapter probe failed to start");
|
||||
this.logger.debug({ err: error, cwd }, "Pi MCP probe failed to start");
|
||||
return null;
|
||||
});
|
||||
if (!runtimeSession) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return (await runtimeSession.getCommands()).some(isPiMcpAdapterCommand);
|
||||
const commands = await runtimeSession.getCommands();
|
||||
if (commands.some(isPiMcpAdapterCommand)) {
|
||||
return "adapter";
|
||||
}
|
||||
return commands.some(isPiBuiltinMcpCommand) ? "builtin" : null;
|
||||
} catch (error) {
|
||||
this.logger.debug({ err: error, cwd }, "Pi MCP adapter probe failed");
|
||||
return false;
|
||||
this.logger.debug({ err: error, cwd }, "Pi MCP probe failed");
|
||||
return null;
|
||||
} finally {
|
||||
await runtimeSession.close().catch(() => undefined);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user