Skip to content

Commit b3ca930

Browse files
authored
Merge pull request #179 from storybookjs/copilot/fix-mcp-addon-vitest-detection
Clarify `/mcp` test-toolset requirements and replace auto-redirect with manual manifest link
2 parents eccfa39 + e559378 commit b3ca930

File tree

4 files changed

+62
-26
lines changed

4 files changed

+62
-26
lines changed

.changeset/clean-bears-brush.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@storybook/addon-mcp': patch
3+
---
4+
5+
Improve `/mcp` HTML response

packages/addon-mcp/src/preset.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { describe, it, expect, vi, beforeEach } from 'vitest';
22
import type { Options } from 'storybook/internal/types';
33
import { experimental_devServer } from './preset.ts';
4+
import * as runStoryTests from './tools/run-story-tests.ts';
45

56
describe('experimental_devServer', () => {
67
let mockApp: any;
@@ -67,6 +68,53 @@ describe('experimental_devServer', () => {
6768
expect(mockRes.end).toHaveBeenCalledWith(expect.stringContaining('<html'));
6869
});
6970

71+
it('should show Storybook version requirement for addon-vitest and a manual manifest link', async () => {
72+
vi.spyOn(runStoryTests, 'getAddonVitestConstants').mockResolvedValue(undefined);
73+
const manifestEnabledOptions = {
74+
presets: {
75+
apply: vi.fn((key: string) => {
76+
if (key === 'features') {
77+
return Promise.resolve({ experimentalComponentsManifest: true });
78+
}
79+
if (key === 'experimental_manifests') {
80+
return Promise.resolve({});
81+
}
82+
return Promise.resolve(undefined);
83+
}),
84+
},
85+
} as unknown as Options;
86+
87+
const handlers: Record<string, any> = {};
88+
mockApp.get = vi.fn((path: string, handler: any) => {
89+
handlers[path] = handler;
90+
});
91+
92+
await (experimental_devServer as any)(mockApp, manifestEnabledOptions);
93+
const getMcpHandler = handlers['/mcp'];
94+
expect(getMcpHandler).toBeDefined();
95+
96+
const mockReq = {
97+
headers: {
98+
accept: 'text/html',
99+
},
100+
} as any;
101+
const mockRes = {
102+
writeHead: vi.fn(),
103+
end: vi.fn(),
104+
} as any;
105+
106+
await getMcpHandler(mockReq, mockRes);
107+
108+
expect(mockRes.end).toHaveBeenCalledWith(
109+
expect.stringContaining('This toolset requires Storybook 10.3.0+ with'),
110+
);
111+
expect(mockRes.end).toHaveBeenCalledWith(
112+
expect.stringContaining(
113+
'View the <a href="/manifests/components.html">component manifest debugger</a>.',
114+
),
115+
);
116+
});
117+
70118
it('should handle POST requests as MCP protocol', async () => {
71119
await (experimental_devServer as any)(mockApp, mockOptions);
72120

packages/addon-mcp/src/preset.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ export const experimental_devServer: PresetPropertyFn<'experimental_devServer'>
120120
});
121121
}
122122

123-
// Browser request - send HTML with redirect
123+
// Browser request - send HTML
124124
res.writeHead(200, { 'Content-Type': 'text/html' });
125125

126126
let docsNotice = '';
@@ -137,7 +137,7 @@ export const experimental_devServer: PresetPropertyFn<'experimental_devServer'>
137137

138138
const testNoticeLines = [
139139
!addonVitestConstants &&
140-
`This toolset requires <code>@storybook/addon-vitest</code>. <a target="_blank" href="https://storybook.js.org/docs/writing-tests/test-addon">Learn how to set it up</a>`,
140+
`This toolset requires Storybook 10.3.0+ with <code>@storybook/addon-vitest</code>. <a target="_blank" href="https://storybook.js.org/docs/writing-tests/test-addon">Learn how to set it up</a>`,
141141
!a11yEnabled &&
142142
`Add <code>@storybook/addon-a11y</code> for accessibility testing. <a target="_blank" href="https://storybook.js.org/docs/writing-tests/accessibility-testing">Learn more</a>`,
143143
].filter(Boolean);
@@ -150,19 +150,17 @@ export const experimental_devServer: PresetPropertyFn<'experimental_devServer'>
150150
: '';
151151

152152
const html = htmlTemplate
153-
.replace(
154-
'{{REDIRECT_META}}',
155-
manifestStatus.available
156-
? // redirect the user to the component manifest page after 10 seconds
157-
'<meta http-equiv="refresh" content="10;url=/manifests/components.html" />'
158-
: // ... or hide the message about redirection
159-
'<style>#redirect-message { display: none; }</style>',
160-
)
161153
.replaceAll('{{DEV_STATUS}}', isDevEnabled ? 'enabled' : 'disabled')
162154
.replaceAll('{{DOCS_STATUS}}', isDocsEnabled ? 'enabled' : 'disabled')
163155
.replace('{{DOCS_NOTICE}}', docsNotice)
164156
.replaceAll('{{TEST_STATUS}}', isTestEnabled ? 'enabled' : 'disabled')
165157
.replace('{{TEST_NOTICE}}', testNotice)
158+
.replace(
159+
'{{MANIFEST_DEBUGGER_LINK}}',
160+
manifestStatus.available
161+
? '<p>View the <a href="/manifests/components.html">component manifest debugger</a>.</p>'
162+
: '',
163+
)
166164
.replace('{{A11Y_BADGE}}', a11yBadge);
167165
res.end(html);
168166
});

packages/addon-mcp/src/template.html

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
<!doctype html>
22
<html>
33
<head>
4-
{{REDIRECT_META}}
54
<style>
65
@font-face {
76
font-family: 'Nunito Sans';
@@ -233,21 +232,7 @@ <h3>Available Toolsets</h3>
233232
</div>
234233
</div>
235234

236-
<p id="redirect-message">
237-
Automatically redirecting to
238-
<a href="/manifests/components.html">component manifest</a>
239-
in <span id="countdown">10</span> seconds...
240-
</p>
235+
{{MANIFEST_DEBUGGER_LINK}}
241236
</div>
242-
<script>
243-
let countdown = 10;
244-
const countdownElement = document.getElementById('countdown');
245-
if (countdownElement) {
246-
setInterval(() => {
247-
countdown -= 1;
248-
countdownElement.textContent = countdown.toString();
249-
}, 1000);
250-
}
251-
</script>
252237
</body>
253238
</html>

0 commit comments

Comments
 (0)