Skip to content

Commit c342fa2

Browse files
cameroncookeclaude
andcommitted
fix(init): Surface MCP policy skip reasons
Print skipped-client reasons before failing policy-filtered installs so users understand why no targets remain. Include the skipped summary in the thrown error for non-interactive workflows. Move the explicit Claude override ahead of the filter loop to keep policy intent clear and avoid unnecessary work. Add coverage for the only-Claude auto-detect MCP path. Refs GH-250 Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 13eeb84 commit c342fa2

2 files changed

Lines changed: 43 additions & 9 deletions

File tree

src/cli/commands/__tests__/init.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,30 @@ describe('init command', () => {
159159
stdoutSpy.mockRestore();
160160
});
161161

162+
it('errors with skip reason when only Claude is detected for MCP auto-install', async () => {
163+
const fakeHome = join(tempDir, 'home-only-claude');
164+
mkdirSync(join(fakeHome, '.claude'), { recursive: true });
165+
mockedHomedir.mockReturnValue(fakeHome);
166+
167+
const yargs = (await import('yargs')).default;
168+
const mod = await loadInitModule();
169+
170+
const app = yargs(['init', '--skill', 'mcp']).scriptName('').fail(false);
171+
mod.registerInitCommand(app);
172+
173+
const stdoutSpy = vi.spyOn(process.stdout, 'write').mockImplementation(() => true);
174+
await expect(app.parseAsync()).rejects.toThrow(
175+
'No eligible install targets after applying skill policy. Skipped: Claude Code: MCP skill is unnecessary because Claude Code already uses server instructions.',
176+
);
177+
178+
const output = stdoutSpy.mock.calls.map((c) => String(c[0])).join('');
179+
expect(output).toContain(
180+
'Skipped Claude Code: MCP skill is unnecessary because Claude Code already uses server instructions.',
181+
);
182+
183+
stdoutSpy.mockRestore();
184+
});
185+
162186
it('allows explicit Claude MCP install with --client claude', async () => {
163187
const fakeHome = join(tempDir, 'home-explicit-claude');
164188
mkdirSync(join(fakeHome, '.claude'), { recursive: true });

src/cli/commands/init.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,14 @@ interface InstallPolicyResult {
100100
skippedClients: Array<{ client: string; reason: string }>;
101101
}
102102

103+
function formatSkippedClients(skippedClients: Array<{ client: string; reason: string }>): string {
104+
if (skippedClients.length === 0) {
105+
return '';
106+
}
107+
108+
return skippedClients.map((skipped) => `${skipped.client}: ${skipped.reason}`).join('; ');
109+
}
110+
103111
async function installSkill(
104112
skillsDir: string,
105113
clientName: string,
@@ -348,8 +356,14 @@ export function registerInitCommand(app: Argv, ctx?: { workspaceRoot: string }):
348356
const targets = resolveTargets(clientFlag, destFlag, 'install');
349357

350358
const policy = enforceInstallPolicy(targets, skillType, clientFlag, destFlag);
359+
for (const skipped of policy.skippedClients) {
360+
writeLine(`Skipped ${skipped.client}: ${skipped.reason}`);
361+
}
362+
351363
if (policy.allowedTargets.length === 0) {
352-
throw new Error('No eligible install targets after applying skill policy.');
364+
const skippedSummary = formatSkippedClients(policy.skippedClients);
365+
const reasonSuffix = skippedSummary.length > 0 ? ` Skipped: ${skippedSummary}` : '';
366+
throw new Error(`No eligible install targets after applying skill policy.${reasonSuffix}`);
353367
}
354368

355369
const results: InstallResult[] = [];
@@ -361,10 +375,6 @@ export function registerInitCommand(app: Argv, ctx?: { workspaceRoot: string }):
361375
results.push(result);
362376
}
363377

364-
for (const skipped of policy.skippedClients) {
365-
writeLine(`Skipped ${skipped.client}: ${skipped.reason}`);
366-
}
367-
368378
writeLine(`Installed ${skillDisplayName(skillType)} skill`);
369379
for (const result of results) {
370380
writeLine(` Client: ${result.client}`);
@@ -393,6 +403,10 @@ function enforceInstallPolicy(
393403
return { allowedTargets: targets, skippedClients: [] };
394404
}
395405

406+
if (clientFlag === 'claude') {
407+
return { allowedTargets: targets, skippedClients: [] };
408+
}
409+
396410
const allowedTargets: ClientInfo[] = [];
397411
const skippedClients: Array<{ client: string; reason: string }> = [];
398412

@@ -407,9 +421,5 @@ function enforceInstallPolicy(
407421
allowedTargets.push(target);
408422
}
409423

410-
if (clientFlag === 'claude') {
411-
return { allowedTargets: targets, skippedClients: [] };
412-
}
413-
414424
return { allowedTargets, skippedClients };
415425
}

0 commit comments

Comments
 (0)