Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/usage-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,7 @@ Claude Code may show project `.mcp.json` servers as pending approval until you a
```bash
teamai mcp list # servers, the file each comes from, secret status, and where they are installed
teamai mcp inject # apply now; --dry-run to preview, --force to override collisions
teamai mcp remove # remove every teamai-managed server
teamai mcp remove # remove every teamai-managed server; --dry-run to preview
```


Expand Down
2 changes: 1 addition & 1 deletion docs/usage-guide.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -1147,7 +1147,7 @@ Claude Code 可能把来自仓库的 `.mcp.json` 标为待批准,需在交互
```bash
teamai mcp list # 查看 server、各自来自哪个文件、密钥状态与安装位置
teamai mcp inject # 立即注入;--dry-run 预览,--force 覆盖同名
teamai mcp remove # 移除所有 teamai 管理的 server
teamai mcp remove # 移除所有 teamai 管理的 server;--dry-run 预览
```


Expand Down
1 change: 1 addition & 0 deletions skill-data/setup/references/manage-admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ run `teamai pull`).
```bash
teamai mcp list # team MCP servers + per-tool install status
teamai mcp inject # push team MCP servers into every AI tool's config
teamai mcp remove --dry-run # preview removal without changing tool configs or managed records
teamai mcp remove # remove teamai-managed MCP servers
```

Expand Down
17 changes: 14 additions & 3 deletions src/__tests__/e2e/mcp-uninstall.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { fileURLToPath } from 'node:url';
// ─── MCP uninstall end-to-end ─────────────────────────────────
//
// Offline fixture (no TEAMAI_TEST_TOKEN). Drives the real CLI binary:
// mcp inject → servers land in ~/.claude.json
// uninstall → teamai-managed servers are removed; hand-added ones stay
// mcp inject → servers land in ~/.claude.json
// mcp remove --dry-run → previews removal without changing config or manifest
// uninstall → teamai-managed servers are removed; hand-added ones stay
//
// Catches the order bug where removeAll ran after ~/.teamai/ (and thus
// managed-mcp.json) had already been deleted.
Expand Down Expand Up @@ -121,7 +122,17 @@ describe('MCP uninstall (e2e)', () => {
url: 'https://example.com/api/mcp',
});
expect(afterInject.mcpServers['my-own']).toEqual({ command: 'my-server' });
expect(fs.existsSync(path.join(teamaiHome, 'managed-mcp.json'))).toBe(true);
const manifestPath = path.join(teamaiHome, 'managed-mcp.json');
expect(fs.existsSync(manifestPath)).toBe(true);

const configBeforePreview = fs.readFileSync(claudeJson);
const manifestBeforePreview = fs.readFileSync(manifestPath);
const preview = await runCLI(['mcp', 'remove', '--dry-run'], env, homeDir);
expect(preview.code).toBe(0);
expect(preview.output).toContain('MCP remove (dry run):');
expect(preview.output).toMatch(/removed\s+claude\/team-mcp/);
expect(fs.readFileSync(claudeJson)).toEqual(configBeforePreview);
expect(fs.readFileSync(manifestPath)).toEqual(manifestBeforePreview);

const uninstall = await runCLI(['uninstall', '--force'], env, homeDir);
expect(uninstall.code).toBe(0);
Expand Down
22 changes: 21 additions & 1 deletion src/__tests__/mcp-cmd.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,8 +272,28 @@ describe('mcpRemove', () => {
spy.mockRestore();
}

expect(reconcileMcpForConfig).toHaveBeenCalledWith(init.teamConfig, init.localConfig, { removeAll: true });
expect(reconcileMcpForConfig).toHaveBeenCalledWith(init.teamConfig, init.localConfig, { removeAll: true, dryRun: undefined });
expect(releaseCleanMcpGitExcludes).toHaveBeenCalledWith(init.teamConfig, init.localConfig);
expect(order).toEqual(['reconcile', 'release']);
});

it('previews removal without changing MCP configs, migration state, or git exclusions', async () => {
const init = { localConfig: { repo: { localPath: '/repo' }, scope: 'project', projectRoot: '/work/app' }, teamConfig: { toolPaths: {} } };
mockedAutoDetectInit.mockResolvedValue(init);
vi.mocked(releaseCleanMcpGitExcludes).mockClear();
(reconcileMcpForConfig as Mock).mockResolvedValueOnce({ changes: [{ action: 'removed', tool: 'claude', server: 'team-mcp' }], wrote: false });
const output: string[] = [];
const spy = vi.spyOn(console, 'log').mockImplementation((line?: unknown) => { output.push(String(line)); });
try {
await mcpRemove({ dryRun: true });
} finally {
spy.mockRestore();
}

expect(mockedAutoDetectInit).toHaveBeenCalledWith(undefined, { dryRun: true });
expect(reconcileMcpForConfig).toHaveBeenCalledWith(init.teamConfig, init.localConfig, { removeAll: true, dryRun: true });
expect(releaseCleanMcpGitExcludes).not.toHaveBeenCalled();
expect(output.join('\n')).toContain('MCP remove (dry run):');
expect(output.join('\n')).toContain('removed');
});
});
15 changes: 9 additions & 6 deletions src/mcp-cmd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,18 @@ export async function mcpInject(
else if (!options.dryRun) log.info('Already up to date.');
}

export async function mcpRemove(_options: GlobalOptions): Promise<void> {
const { localConfig, teamConfig } = await autoDetectInit();
const { changes, wrote } = await reconcileMcpForConfig(teamConfig, localConfig, { removeAll: true });
export async function mcpRemove(options: GlobalOptions): Promise<void> {
const { localConfig, teamConfig } = await autoDetectInit(undefined, { dryRun: options.dryRun });
const { changes, wrote } = await reconcileMcpForConfig(teamConfig, localConfig, {
removeAll: true,
dryRun: options.dryRun,
});
// Nothing of teamai's is left for .git/info/exclude to protect (#882).
await releaseCleanMcpGitExcludes(teamConfig, localConfig);
if (!options.dryRun) await releaseCleanMcpGitExcludes(teamConfig, localConfig);

console.log('MCP remove:');
console.log(options.dryRun ? 'MCP remove (dry run):' : 'MCP remove:');
reportChanges(changes);

if (wrote) log.success('teamai-managed MCP servers removed.');
else log.info('Nothing to remove.');
else if (!options.dryRun) log.info('Nothing to remove.');
}
Loading