c18d400bdf
Updated /mcp enable and /mcp disable so they connect or disconnect only the named server instead of reloading every MCP server in the session. Added regression coverage for both toggle directions and updated the coding-agent changelog. Fixes #3157
137 lines
4.5 KiB
TypeScript
137 lines
4.5 KiB
TypeScript
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "bun:test";
|
|
import * as fs from "node:fs/promises";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
import type { SourceMeta } from "@oh-my-pi/pi-coding-agent/capability/types";
|
|
import type { MCPServerConfig } from "@oh-my-pi/pi-coding-agent/mcp/types";
|
|
import { MCPCommandController } from "@oh-my-pi/pi-coding-agent/modes/controllers/mcp-command-controller";
|
|
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
import { getConfigRootDir, getMCPConfigPath, getProjectDir, setAgentDir, setProjectDir } from "@oh-my-pi/pi-utils";
|
|
|
|
const originalProjectDir = getProjectDir();
|
|
const originalAgentDir = process.env.PI_CODING_AGENT_DIR;
|
|
const fallbackAgentDir = path.join(getConfigRootDir(), "agent");
|
|
|
|
function restoreAgentDir(): void {
|
|
if (originalAgentDir) {
|
|
setAgentDir(originalAgentDir);
|
|
process.env.PI_CODING_AGENT_DIR = originalAgentDir;
|
|
Bun.env.PI_CODING_AGENT_DIR = originalAgentDir;
|
|
return;
|
|
}
|
|
setAgentDir(fallbackAgentDir);
|
|
delete process.env.PI_CODING_AGENT_DIR;
|
|
delete Bun.env.PI_CODING_AGENT_DIR;
|
|
}
|
|
|
|
function createController() {
|
|
const refreshMCPTools = vi.fn(async () => {});
|
|
const mcpManager = {
|
|
disconnectAll: vi.fn(async () => {}),
|
|
discoverAndConnect: vi.fn(async () => ({ errors: new Map<string, string>() })),
|
|
disconnectServer: vi.fn(async () => {}),
|
|
connectServers: vi.fn(
|
|
async (_configs: Record<string, MCPServerConfig>, _sources: Record<string, SourceMeta>) => ({
|
|
errors: new Map<string, string>(),
|
|
connectedServers: [],
|
|
tools: [],
|
|
exaApiKeys: [],
|
|
}),
|
|
),
|
|
getTools: vi.fn(() => []),
|
|
waitForConnection: vi.fn(async () => ({})),
|
|
getConnectionStatus: vi.fn(() => "connected"),
|
|
getSource: vi.fn(() => undefined),
|
|
};
|
|
const controller = new MCPCommandController({
|
|
chatContainer: { addChild: vi.fn() },
|
|
present: vi.fn(),
|
|
ui: { requestRender: vi.fn() },
|
|
editor: {},
|
|
showError: vi.fn(),
|
|
showStatus: vi.fn(),
|
|
oauthManualInput: {
|
|
hasPending: vi.fn(() => false),
|
|
pendingProviderId: undefined,
|
|
tryClaimInput: vi.fn(),
|
|
},
|
|
session: {
|
|
refreshMCPTools,
|
|
modelRegistry: { authStorage: undefined },
|
|
},
|
|
mcpManager,
|
|
} as never);
|
|
|
|
return { controller, mcpManager, refreshMCPTools };
|
|
}
|
|
|
|
async function writeProjectConfig(projectDir: string, servers: Record<string, MCPServerConfig>): Promise<void> {
|
|
await Bun.write(
|
|
getMCPConfigPath("project", projectDir),
|
|
`${JSON.stringify(
|
|
{
|
|
mcpServers: servers,
|
|
},
|
|
null,
|
|
2,
|
|
)}\n`,
|
|
);
|
|
}
|
|
|
|
describe("/mcp enable and disable", () => {
|
|
let projectDir = "";
|
|
let agentDir = "";
|
|
|
|
beforeAll(() => {
|
|
initTheme();
|
|
});
|
|
|
|
beforeEach(async () => {
|
|
projectDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-mcp-toggle-project-"));
|
|
agentDir = await fs.mkdtemp(path.join(os.tmpdir(), "omp-mcp-toggle-agent-"));
|
|
setProjectDir(projectDir);
|
|
setAgentDir(agentDir);
|
|
});
|
|
|
|
afterEach(async () => {
|
|
vi.restoreAllMocks();
|
|
setProjectDir(originalProjectDir);
|
|
restoreAgentDir();
|
|
await fs.rm(projectDir, { recursive: true, force: true });
|
|
await fs.rm(agentDir, { recursive: true, force: true });
|
|
});
|
|
|
|
test("disabling one configured server does not reload other MCP servers", async () => {
|
|
await writeProjectConfig(projectDir, {
|
|
mcp1: { type: "stdio", command: "mcp-one" },
|
|
mcp2: { type: "stdio", command: "mcp-two" },
|
|
});
|
|
const { controller, mcpManager, refreshMCPTools } = createController();
|
|
|
|
await controller.handle("/mcp disable mcp1");
|
|
|
|
expect(mcpManager.disconnectServer).toHaveBeenCalledWith("mcp1");
|
|
expect(refreshMCPTools).toHaveBeenCalledWith([]);
|
|
expect(mcpManager.disconnectAll).not.toHaveBeenCalled();
|
|
expect(mcpManager.discoverAndConnect).not.toHaveBeenCalled();
|
|
expect(mcpManager.connectServers).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test("enabling one configured server connects only that MCP server", async () => {
|
|
await writeProjectConfig(projectDir, {
|
|
mcp1: { type: "stdio", command: "mcp-one", enabled: false },
|
|
mcp2: { type: "stdio", command: "mcp-two" },
|
|
});
|
|
const { controller, mcpManager } = createController();
|
|
|
|
await controller.handle("/mcp enable mcp1");
|
|
|
|
expect(mcpManager.disconnectAll).not.toHaveBeenCalled();
|
|
expect(mcpManager.discoverAndConnect).not.toHaveBeenCalled();
|
|
expect(mcpManager.connectServers).toHaveBeenCalledTimes(1);
|
|
const [configs] = mcpManager.connectServers.mock.calls[0]!;
|
|
expect(Object.keys(configs)).toEqual(["mcp1"]);
|
|
expect(configs.mcp1).toEqual({ type: "stdio", command: "mcp-one", enabled: true });
|
|
});
|
|
});
|