Skip to content
Draft
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
```
The wizard will install the `@sentry/cloudflare` SDK and show next steps. Currently only Cloudflare Workers are supported.

- feat(mcp): Add multi-select support for MCP configuration ([#1153](https://github.com/getsentry/sentry-wizard/pull/1153))

The MCP configuration prompt now allows users to select multiple editors at once using checkboxes. Users can configure Cursor, VS Code, Claude Code, JetBrains IDEs, and other IDEs in a single wizard run. If no editors are selected, the wizard will skip MCP configuration gracefully.

<details>
<summary><strong>Internal Changes</strong></summary>

Expand Down
6 changes: 3 additions & 3 deletions e2e-tests/tests/nextjs-15.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,13 +98,13 @@ describe('NextJS-15', () => {
mcpPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
'Which editor do you want to configure?',
'Which editor(s) do you want to configure?',
));

// Select Cursor as the editor (first option)
// Select Cursor as the editor (first option) - SPACE to select, ENTER to confirm
editorPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
[KEYS.ENTER],
[KEYS.SPACE, KEYS.ENTER],
'Successfully installed the Sentry Next.js SDK!',
));

Expand Down
164 changes: 89 additions & 75 deletions src/utils/clack/mcp-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,9 +448,9 @@ export async function offerProjectScopedMcpConfig(
| 'claudeCode'
| 'jetbrains'
| 'other';
const editor: EditorChoice = await abortIfCancelled(
clack.select({
message: 'Which editor do you want to configure?',
const editors: EditorChoice[] = await abortIfCancelled(
clack.multiselect({
message: 'Which editor(s) do you want to configure?',
options: [
{ value: 'cursor', label: 'Cursor (project .cursor/mcp.json)' },
{ value: 'vscode', label: 'VS Code (project .vscode/mcp.json)' },
Expand All @@ -466,82 +466,96 @@ export async function offerProjectScopedMcpConfig(
hint: "We'll show you the configuration to copy",
},
],
required: false,
}),
);

// Track which editor was selected
Sentry.setTag('mcp-editor', editor);
// If no editors were selected, return early
if (!editors || editors.length === 0) {
clack.log.info('No editors selected. You can add MCP configuration later.');
Sentry.setTag('mcp-configured', false);
return;
}

try {
switch (editor) {
case 'cursor':
await addCursorMcpConfig(orgSlug, projectSlug);
clack.log.success('Added project-scoped Sentry MCP configuration.');
clack.log.info(
chalk.dim(
'Note: You may need to reload your editor for MCP changes to take effect.',
),
);
Sentry.setTag('mcp-config-success', true);
break;
case 'vscode':
await addVsCodeMcpConfig(orgSlug, projectSlug);
clack.log.success('Added project-scoped Sentry MCP configuration.');
clack.log.info(
chalk.dim(
'Note: You may need to reload your editor for MCP changes to take effect.',
),
);
Sentry.setTag('mcp-config-success', true);
break;
case 'claudeCode':
await addClaudeCodeMcpConfig(orgSlug, projectSlug);
clack.log.success('Added project-scoped Sentry MCP configuration.');
clack.log.info(
chalk.dim(
'Note: You may need to reload your editor for MCP changes to take effect.',
),
);
Sentry.setTag('mcp-config-success', true);
break;
case 'jetbrains':
await showJetBrainsMcpConfig(orgSlug, projectSlug);
Sentry.setTag('mcp-config-success', true);
Sentry.setTag('mcp-config-manual', true);
break;
case 'other':
await showGenericMcpConfig(orgSlug, projectSlug);
Sentry.setTag('mcp-config-success', true);
Sentry.setTag('mcp-config-manual', true);
break;
}
} catch (e) {
Sentry.setTag('mcp-config-success', false);
Sentry.setTag('mcp-config-fallback', true);
clack.log.warn(
chalk.yellow(
'Failed to write MCP config automatically. Please copy/paste the snippet below into your project config file.',
),
);
// Fallback: show per-editor instructions
if (editor === 'cursor') {
await showCopyPasteInstructions({
filename: path.join('.cursor', 'mcp.json'),
codeSnippet: getCursorMcpJsonSnippet(orgSlug, projectSlug),
hint: 'create the file if it does not exist',
});
} else if (editor === 'vscode') {
await showCopyPasteInstructions({
filename: path.join('.vscode', 'mcp.json'),
codeSnippet: getVsCodeMcpJsonSnippet(orgSlug, projectSlug),
hint: 'create the file if it does not exist',
});
} else if (editor === 'claudeCode') {
await showCopyPasteInstructions({
filename: '.mcp.json',
codeSnippet: getClaudeCodeMcpJsonSnippet(orgSlug, projectSlug),
hint: 'create the file if it does not exist',
});
// Track number of editors selected
Sentry.setTag('mcp-editors-count', editors.length);

// Configure each selected editor
for (const editor of editors) {
// Track which editor is being configured
Sentry.setTag('mcp-editor', editor);

try {
switch (editor) {
case 'cursor':
await addCursorMcpConfig(orgSlug, projectSlug);
clack.log.success('Added project-scoped Sentry MCP configuration.');
clack.log.info(
chalk.dim(
'Note: You may need to reload your editor for MCP changes to take effect.',
),
);
Sentry.setTag('mcp-config-cursor-success', true);
break;
case 'vscode':
await addVsCodeMcpConfig(orgSlug, projectSlug);
clack.log.success('Added project-scoped Sentry MCP configuration.');
clack.log.info(
chalk.dim(
'Note: You may need to reload your editor for MCP changes to take effect.',
),
);
Sentry.setTag('mcp-config-vscode-success', true);
break;
case 'claudeCode':
await addClaudeCodeMcpConfig(orgSlug, projectSlug);
clack.log.success('Added project-scoped Sentry MCP configuration.');
clack.log.info(
chalk.dim(
'Note: You may need to reload your editor for MCP changes to take effect.',
),
);
Sentry.setTag('mcp-config-claudeCode-success', true);
break;
case 'jetbrains':
await showJetBrainsMcpConfig(orgSlug, projectSlug);
Sentry.setTag('mcp-config-jetbrains-success', true);
Sentry.setTag('mcp-config-manual', true);
break;
case 'other':
await showGenericMcpConfig(orgSlug, projectSlug);
Sentry.setTag('mcp-config-other-success', true);
Sentry.setTag('mcp-config-manual', true);
break;
}
} catch (e) {
Sentry.setTag(`mcp-config-${editor}-success`, false);
Sentry.setTag('mcp-config-fallback', true);
clack.log.warn(
chalk.yellow(
`Failed to write MCP config for ${editor} automatically. Please copy/paste the snippet below into your project config file.`,
),
);
// Fallback: show per-editor instructions
if (editor === 'cursor') {
await showCopyPasteInstructions({
filename: path.join('.cursor', 'mcp.json'),
codeSnippet: getCursorMcpJsonSnippet(orgSlug, projectSlug),
hint: 'create the file if it does not exist',
});
} else if (editor === 'vscode') {
await showCopyPasteInstructions({
filename: path.join('.vscode', 'mcp.json'),
codeSnippet: getVsCodeMcpJsonSnippet(orgSlug, projectSlug),
hint: 'create the file if it does not exist',
});
} else if (editor === 'claudeCode') {
await showCopyPasteInstructions({
filename: '.mcp.json',
codeSnippet: getClaudeCodeMcpJsonSnippet(orgSlug, projectSlug),
hint: 'create the file if it does not exist',
});
}
}
}
}
Loading
Loading