import { describe, it, expect } from 'vitest'; import { MCPCTL_SERVER_NAME, mergeMcpctlServers, projectOfEntry, isLegacyMcpctlEntry, activeProjectIn, } from '../../src/config/claude-mcp.js'; const bridge = (project: string): Record => ({ command: 'mcpctl', args: ['mcp', '-p', project], }); describe('projectOfEntry', () => { it('reads the project out of the bridge args', () => { expect(projectOfEntry(bridge('docmost'))).toBe('docmost'); expect(projectOfEntry({ command: 'mcpctl', args: ['mcp', '--project', 'sre'] })).toBe('sre'); }); it('ignores anything that is not our stdio bridge', () => { expect(projectOfEntry({ command: 'echo', args: ['mcp', '-p', 'x'] })).toBeNull(); expect(projectOfEntry({ command: 'mcpctl', args: ['console', '--stdin-mcp'] })).toBeNull(); expect(projectOfEntry({ type: 'remote', url: 'https://x/projects/y/mcp' })).toBeNull(); expect(projectOfEntry({ command: 'mcpctl', args: ['mcp', '-p'] })).toBeNull(); expect(projectOfEntry(null)).toBeNull(); expect(projectOfEntry('nope')).toBeNull(); }); }); describe('isLegacyMcpctlEntry', () => { it('recognises an entry named after the very project it bridges to', () => { expect(isLegacyMcpctlEntry('docmost', bridge('docmost'))).toBe(true); }); it('never claims the canonical entry', () => { expect(isLegacyMcpctlEntry(MCPCTL_SERVER_NAME, bridge('docmost'))).toBe(false); }); it('leaves a hand-configured server alone', () => { // Same name, different command — someone else's server. expect(isLegacyMcpctlEntry('docmost', { command: 'docker', args: ['run', 'docmost'] })).toBe(false); // Our command, but the name does not match the project: not something this // CLI ever wrote, so it is the user's to keep. expect(isLegacyMcpctlEntry('my-shortcut', bridge('docmost'))).toBe(false); }); }); describe('activeProjectIn', () => { it('prefers the canonical entry', () => { expect(activeProjectIn({ mcpServers: { [MCPCTL_SERVER_NAME]: bridge('sre') } })).toBe('sre'); }); it('falls back to a legacy entry so pre-migration installs still report', () => { expect(activeProjectIn({ mcpServers: { docmost: bridge('docmost') } })).toBe('docmost'); }); it('is null when nothing of ours is mounted', () => { expect(activeProjectIn({ mcpServers: { other: { command: 'echo' } } })).toBeNull(); expect(activeProjectIn(null)).toBeNull(); expect(activeProjectIn({ mcpServers: {} })).toBeNull(); }); }); describe('mergeMcpctlServers', () => { it('writes one constant entry regardless of project', () => { const { config } = mergeMcpctlServers(null, { project: 'my-fancy-project' }); expect(Object.keys(config.mcpServers)).toEqual([MCPCTL_SERVER_NAME]); expect(config.mcpServers[MCPCTL_SERVER_NAME]).toEqual(bridge('my-fancy-project')); }); it('re-points rather than stacking on a second project', () => { const first = mergeMcpctlServers(null, { project: 'a' }).config; const { config } = mergeMcpctlServers(first, { project: 'b' }); expect(Object.keys(config.mcpServers)).toEqual([MCPCTL_SERVER_NAME]); expect(config.mcpServers[MCPCTL_SERVER_NAME]).toEqual(bridge('b')); }); it('retires legacy per-project entries and reports them', () => { const existing = { mcpServers: { homeautomation: bridge('homeautomation'), sre: bridge('sre') } }; const { config, retired } = mergeMcpctlServers(existing, { project: 'docmost' }); expect(Object.keys(config.mcpServers)).toEqual([MCPCTL_SERVER_NAME]); expect(retired.sort()).toEqual(['homeautomation', 'sre']); }); it('preserves servers the user configured, and other top-level keys', () => { const existing = { mcpServers: { 'my-own': { command: 'echo', args: [] } }, someOtherKey: { keep: true }, }; const { config, retired } = mergeMcpctlServers(existing, { project: 'p' }); expect(config.mcpServers['my-own']).toEqual({ command: 'echo', args: [] }); expect(config['someOtherKey']).toEqual({ keep: true }); expect(retired).toEqual([]); }); it('--inspect alone does not unmount the project you are working in', () => { const existing = mergeMcpctlServers(null, { project: 'p' }).config; const { config, retired } = mergeMcpctlServers(existing, { inspect: true }); expect(config.mcpServers[MCPCTL_SERVER_NAME]).toEqual(bridge('p')); expect(config.mcpServers['mcpctl-inspect']).toEqual({ command: 'mcpctl', args: ['console', '--stdin-mcp'] }); expect(retired).toEqual([]); }); it('does not mutate the config it was handed', () => { const existing = { mcpServers: { homeautomation: bridge('homeautomation') } }; mergeMcpctlServers(existing, { project: 'docmost' }); expect(Object.keys(existing.mcpServers)).toEqual(['homeautomation']); }); });