Skip to content

Commit 29419f1

Browse files
committed
improve composability of server instructions
1 parent 1f886da commit 29419f1

File tree

8 files changed

+268
-44
lines changed

8 files changed

+268
-44
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { buildServerInstructions } from './build-server-instructions.ts';
3+
4+
describe('buildServerInstructions', () => {
5+
it('builds a coherent instruction set when all toolsets are enabled', () => {
6+
const instructions = buildServerInstructions({
7+
devEnabled: true,
8+
testEnabled: true,
9+
docsEnabled: true,
10+
});
11+
12+
expect(instructions).toMatchInlineSnapshot(`
13+
"Follow these workflows when working with UI and/or Storybook.
14+
15+
## UI Building and Story Writing Workflow
16+
17+
- Before creating or editing components or stories, call **get-storybook-story-instructions**.
18+
- Treat that tool's output as the source of truth for framework-specific imports, story patterns, and testing conventions.
19+
- After changing any component or story, call **preview-stories**.
20+
- Always include every returned preview URL in your user-facing response so the user can verify the visual result.
21+
22+
## Validation Workflow
23+
24+
- After each component or story change, run **run-story-tests**.
25+
- Use focused runs while iterating, then run a broad pass before final handoff when scope is unclear or wide.
26+
- Fix failing tests before reporting success. Do not report completion while story tests are failing.
27+
28+
## Documentation Workflow
29+
30+
1. Call **list-all-documentation** once at the start of the task to discover available component and docs IDs.
31+
2. Call **get-documentation** with an \`id\` from that list to retrieve full component docs, props, usage examples, and stories.
32+
3. Call **get-documentation-for-story** when you need additional docs from a specific story variant that was not included in the initial component documentation.
33+
34+
Use \`withStoryIds: true\` on **list-all-documentation** when you also need story IDs for tools like \`preview-stories\` or \`get-documentation-for-story\`.
35+
36+
## Verification Rules
37+
38+
- Never assume component props, variants, or API shape. Retrieve documentation before using a component.
39+
- If a component or prop is not documented, do not invent it. Report that it was not found.
40+
- Only reference IDs returned by **list-all-documentation**. Do not guess IDs.
41+
42+
## Multi-Source Requests
43+
44+
- When multiple Storybook sources are configured, **list-all-documentation** returns entries from all sources.
45+
- Use \`storybookId\` in **get-documentation** when you need to scope a request to one source."
46+
`);
47+
});
48+
49+
it('builds a coherent instruction set for dev only', () => {
50+
const instructions = buildServerInstructions({
51+
devEnabled: true,
52+
testEnabled: false,
53+
docsEnabled: false,
54+
});
55+
56+
expect(instructions).toMatchInlineSnapshot(`
57+
"Follow these workflows when working with UI and/or Storybook.
58+
59+
## UI Building and Story Writing Workflow
60+
61+
- Before creating or editing components or stories, call **get-storybook-story-instructions**.
62+
- Treat that tool's output as the source of truth for framework-specific imports, story patterns, and testing conventions.
63+
- After changing any component or story, call **preview-stories**.
64+
- Always include every returned preview URL in your user-facing response so the user can verify the visual result."
65+
`);
66+
});
67+
68+
it('builds a coherent instruction set for docs only', () => {
69+
const instructions = buildServerInstructions({
70+
devEnabled: false,
71+
testEnabled: false,
72+
docsEnabled: true,
73+
});
74+
75+
expect(instructions).toMatchInlineSnapshot(`
76+
"Follow these workflows when working with UI and/or Storybook.
77+
78+
## Documentation Workflow
79+
80+
1. Call **list-all-documentation** once at the start of the task to discover available component and docs IDs.
81+
2. Call **get-documentation** with an \`id\` from that list to retrieve full component docs, props, usage examples, and stories.
82+
3. Call **get-documentation-for-story** when you need additional docs from a specific story variant that was not included in the initial component documentation.
83+
84+
Use \`withStoryIds: true\` on **list-all-documentation** when you also need story IDs for tools like \`preview-stories\` or \`get-documentation-for-story\`.
85+
86+
## Verification Rules
87+
88+
- Never assume component props, variants, or API shape. Retrieve documentation before using a component.
89+
- If a component or prop is not documented, do not invent it. Report that it was not found.
90+
- Only reference IDs returned by **list-all-documentation**. Do not guess IDs.
91+
92+
## Multi-Source Requests
93+
94+
- When multiple Storybook sources are configured, **list-all-documentation** returns entries from all sources.
95+
- Use \`storybookId\` in **get-documentation** when you need to scope a request to one source."
96+
`);
97+
});
98+
99+
it('builds a coherent instruction set for test only', () => {
100+
const instructions = buildServerInstructions({
101+
devEnabled: false,
102+
testEnabled: true,
103+
docsEnabled: false,
104+
});
105+
106+
expect(instructions).toMatchInlineSnapshot(`
107+
"Follow these workflows when working with UI and/or Storybook.
108+
109+
## Validation Workflow
110+
111+
- After each component or story change, run **run-story-tests**.
112+
- Use focused runs while iterating, then run a broad pass before final handoff when scope is unclear or wide.
113+
- Fix failing tests before reporting success. Do not report completion while story tests are failing."
114+
`);
115+
});
116+
117+
it('returns empty instructions when all toolsets are disabled', () => {
118+
const instructions = buildServerInstructions({
119+
devEnabled: false,
120+
testEnabled: false,
121+
docsEnabled: false,
122+
});
123+
124+
expect(instructions).toBe('');
125+
});
126+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import devInstructions from './dev-instructions.md';
2+
import testInstructions from './test-instructions.md';
3+
import { STORYBOOK_MCP_INSTRUCTIONS } from '@storybook/mcp';
4+
5+
export type BuildServerInstructionsOptions = {
6+
devEnabled: boolean;
7+
testEnabled: boolean;
8+
docsEnabled: boolean;
9+
};
10+
11+
export function buildServerInstructions(options: BuildServerInstructionsOptions): string {
12+
const sections = ['Follow these workflows when working with UI and/or Storybook.'];
13+
14+
if (options.devEnabled) {
15+
sections.push(devInstructions.trim());
16+
}
17+
18+
if (options.testEnabled) {
19+
sections.push(testInstructions.trim());
20+
}
21+
22+
if (options.docsEnabled) {
23+
sections.push(STORYBOOK_MCP_INSTRUCTIONS.trim());
24+
}
25+
26+
if (sections.length === 1) {
27+
return '';
28+
}
29+
30+
return sections.join('\n\n');
31+
}
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
## Story Writing and Development
1+
## UI Building and Story Writing Workflow
22

3-
Always call **get-storybook-story-instructions** first to get framework-specific guidance. This tool returns the correct imports, patterns, and conventions for the current project. Do not skip this step.
4-
5-
## Story Preview
6-
7-
After writing or modifying a component or story, call **preview-stories** to retrieve the live preview URLs. Always include these URLs in your response so the user can verify the visual output.
3+
- Before creating or editing components or stories, call **get-storybook-story-instructions**.
4+
- Treat that tool's output as the source of truth for framework-specific imports, story patterns, and testing conventions.
5+
- After changing any component or story, call **preview-stories**.
6+
- Always include every returned preview URL in your user-facing response so the user can verify the visual result.
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
## Story Testing
1+
## Validation Workflow
22

3-
After writing or changing stories, run tests using the **run-story-tests** tool. Fix any failures before reporting success. Do not report stories as complete if tests are failing.
3+
- After each component or story change, run **run-story-tests**.
4+
- Use focused runs while iterating, then run a broad pass before final handoff when scope is unclear or wide.
5+
- Fix failing tests before reporting success. Do not report completion while story tests are failing.

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

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -279,9 +279,74 @@ describe('mcpServerHandler', () => {
279279
});
280280
expect(parsedResponse.result.serverInfo.version).toBeDefined();
281281
expect(parsedResponse.result.instructions).toBeDefined();
282-
expect(parsedResponse.result.instructions).toContain('get-storybook-story-instructions');
283-
// list-all-documentation is only included when the docs toolset manifest is available
284-
expect(parsedResponse.result.instructions).not.toContain('list-all-documentation');
282+
expect(parsedResponse.result.instructions).toContain(
283+
'Follow these workflows when working with UI and/or Storybook.',
284+
);
285+
expect(parsedResponse.result.instructions).toContain(
286+
'## UI Building and Story Writing Workflow',
287+
);
288+
expect(parsedResponse.result.instructions).toContain('## Validation Workflow');
289+
expect(parsedResponse.result.instructions).not.toContain('## Documentation Workflow');
290+
});
291+
292+
it('should include docs-style instructions when docs toolset is selected and manifest is available', async () => {
293+
const applyMock = vi.fn(async (key: string, defaultValue?: any) => {
294+
if (key === 'core') {
295+
return { disableTelemetry: false };
296+
}
297+
if (key === 'features') {
298+
return { experimentalComponentsManifest: true };
299+
}
300+
if (key === 'experimental_manifests') {
301+
return vi.fn();
302+
}
303+
return defaultValue;
304+
});
305+
306+
const mockOptions = createMockOptions({
307+
port: 6011,
308+
presets: { apply: applyMock },
309+
});
310+
const mockReq = createMockIncomingMessage({
311+
method: 'POST',
312+
headers: {
313+
'content-type': 'application/json',
314+
host: 'localhost:6011',
315+
'X-MCP-Toolsets': 'docs',
316+
},
317+
body: createMCPInitializeRequest(),
318+
});
319+
const { response, getResponseData } = createMockServerResponse();
320+
321+
await mcpServerHandler({
322+
req: mockReq,
323+
res: response,
324+
options: mockOptions as any,
325+
addonOptions: {
326+
toolsets: {
327+
dev: true,
328+
docs: true,
329+
test: true,
330+
},
331+
},
332+
compositionAuth: new CompositionAuth(),
333+
});
334+
335+
const { body } = getResponseData();
336+
const dataLine = body.split('\n').find((line) => line.startsWith('data: '));
337+
const responseText = dataLine!.replace(/^data: /, '').trim();
338+
const parsedResponse = JSON.parse(responseText);
339+
340+
expect(parsedResponse.result.instructions).toContain(
341+
'Follow these workflows when working with UI and/or Storybook.',
342+
);
343+
expect(parsedResponse.result.instructions).toContain('## Documentation Workflow');
344+
expect(parsedResponse.result.instructions).toContain('## Verification Rules');
345+
expect(parsedResponse.result.instructions).toContain('## Multi-Source Requests');
346+
expect(parsedResponse.result.instructions).not.toContain(
347+
'## UI Building and Story Writing Workflow',
348+
);
349+
expect(parsedResponse.result.instructions).not.toContain('## Validation Workflow');
285350
});
286351

287352
it('should respect disableTelemetry setting', async () => {

packages/addon-mcp/src/mcp-handler.ts

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import {
88
addListAllDocumentationTool,
99
addGetDocumentationTool,
1010
addGetStoryDocumentationTool,
11-
STORYBOOK_MCP_INSTRUCTIONS,
1211
type Source,
1312
} from '@storybook/mcp';
1413
import type { Options } from 'storybook/internal/types';
@@ -22,20 +21,7 @@ import { addRunStoryTestsTool, getAddonVitestConstants } from './tools/run-story
2221
import { estimateTokens } from './utils/estimate-tokens.ts';
2322
import { isAddonA11yEnabled } from './utils/is-addon-a11y-enabled.ts';
2423
import type { CompositionAuth } from './auth/index.ts';
25-
import devInstructions from './instructions/dev-instructions.md';
26-
import testInstructions from './instructions/test-instructions.md';
27-
28-
function buildServerInstructions(options: {
29-
devEnabled: boolean;
30-
testEnabled: boolean;
31-
docsEnabled: boolean;
32-
}): string {
33-
const parts: string[] = [];
34-
if (options.devEnabled) parts.push(devInstructions);
35-
if (options.testEnabled) parts.push(testInstructions);
36-
if (options.docsEnabled) parts.push(STORYBOOK_MCP_INSTRUCTIONS);
37-
return parts.join('\n\n');
38-
}
24+
import { buildServerInstructions } from './instructions/build-server-instructions.ts';
3925

4026
let transport: HttpTransport<AddonContext> | undefined;
4127
let origin: string | undefined;

packages/mcp/src/index.test.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,27 @@ describe('createStorybookMcpHandler', () => {
101101

102102
const instructions = client.getInstructions();
103103
expect(instructions).toBeDefined();
104-
expect(instructions).toContain('list-all-documentation');
105-
expect(instructions).toContain('get-documentation');
106-
expect(instructions).toContain('Anti-Hallucination');
104+
expect(instructions).toMatchInlineSnapshot(`
105+
"## Documentation Workflow
106+
107+
1. Call **list-all-documentation** once at the start of the task to discover available component and docs IDs.
108+
2. Call **get-documentation** with an \`id\` from that list to retrieve full component docs, props, usage examples, and stories.
109+
3. Call **get-documentation-for-story** when you need additional docs from a specific story variant that was not included in the initial component documentation.
110+
111+
Use \`withStoryIds: true\` on **list-all-documentation** when you also need story IDs for tools like \`preview-stories\` or \`get-documentation-for-story\`.
112+
113+
## Verification Rules
114+
115+
- Never assume component props, variants, or API shape. Retrieve documentation before using a component.
116+
- If a component or prop is not documented, do not invent it. Report that it was not found.
117+
- Only reference IDs returned by **list-all-documentation**. Do not guess IDs.
118+
119+
## Multi-Source Requests
120+
121+
- When multiple Storybook sources are configured, **list-all-documentation** returns entries from all sources.
122+
- Use \`storybookId\` in **get-documentation** when you need to scope a request to one source.
123+
"
124+
`);
107125
});
108126

109127
it('should call onSessionInitialize handler when provided', async () => {

packages/mcp/src/instructions.md

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
1-
Use these tools to access Storybook component and documentation manifests.
1+
## Documentation Workflow
22

3-
## Tool Workflow
3+
1. Call **list-all-documentation** once at the start of the task to discover available component and docs IDs.
4+
2. Call **get-documentation** with an `id` from that list to retrieve full component docs, props, usage examples, and stories.
5+
3. Call **get-documentation-for-story** when you need additional docs from a specific story variant that was not included in the initial component documentation.
46

5-
Use tools in this order:
7+
Use `withStoryIds: true` on **list-all-documentation** when you also need story IDs for tools like `preview-stories` or `get-documentation-for-story`.
68

7-
1. **list-all-documentation** — Call once at the start of a task to discover available components and docs entries. Use the returned IDs for subsequent calls. Pass `withStoryIds: true` when you also need story IDs (e.g., for use with `preview-stories` or `get-documentation-for-story`) — this adds story sub-entries to the list.
9+
## Verification Rules
810

9-
2. **get-documentation** — Call with a specific `id` from the list to retrieve full component documentation including props, usage examples, and stories. Prefer this over re-calling list when you already know the ID.
11+
- Never assume component props, variants, or API shape. Retrieve documentation before using a component.
12+
- If a component or prop is not documented, do not invent it. Report that it was not found.
13+
- Only reference IDs returned by **list-all-documentation**. Do not guess IDs.
1014

11-
3. **get-documentation-for-story** — Call with a story ID when you need documentation scoped to a specific story variant rather than the whole component.
15+
## Multi-Source Requests
1216

13-
## Anti-Hallucination Rules
14-
15-
- Never assume component props, variants, or API shape. Always retrieve documentation before using a component.
16-
- If a component or prop is not in the documentation, do not invent it. Tell the user the component was not found.
17-
- Only reference IDs returned by list-all-documentation. Do not guess IDs.
18-
19-
## Multi-Source Behavior
20-
21-
When multiple Storybook sources are configured, list-all-documentation returns entries from all sources. Use the `storybookId` field in get-documentation to scope requests to a specific source when needed.
17+
- When multiple Storybook sources are configured, **list-all-documentation** returns entries from all sources.
18+
- Use `storybookId` in **get-documentation** when you need to scope a request to one source.

0 commit comments

Comments
 (0)