Skip to content
Draft
7 changes: 7 additions & 0 deletions .changeset/swift-tools-guide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@storybook/mcp': patch
'@storybook/addon-mcp': patch
---

Add a `get-setup-instructions` MCP tool that returns docs entries tagged with `setup-instructions`.

32 changes: 32 additions & 0 deletions eval/tasks/916-follow-setup-instructions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# 916 – Follow Setup Instructions

## Purpose

Tests whether the agent discovers and applies **project-level setup guidance** via `get-setup-instructions` before using a documented component.

## Setup

- A fake `@acme/ui` package is pre-seeded into the trial project after preparation.
- The Storybook MCP docs context exposes:
- component docs for `launch-button`
- **two** docs entries tagged `setup-instructions`
- The setup docs intentionally include specific requirements that can be graded afterward:
- import `@acme/ui/styles.css`
- wrap the app in `<AcmeProvider theme="midnight" density="comfortable">`
- render the UI inside an element with `data-acme-app="true"`

## Prompt

Asks the agent to build a small page with `LaunchButton` in `src/main.tsx` and not guess any library setup.

## Quality Signal

| Metric | Weight |
| ------------------------------------------------------ | ------ |
| MCP tools coverage (`get-setup-instructions`) | 50 % |
| Test pass rate (setup instructions were actually used) | 50 % |

## Expected MCP Tools

- `get-setup-instructions` (at least 1 call)
- `get-documentation` (at least 1 call for `launch-button`)
15 changes: 15 additions & 0 deletions eval/tasks/916-follow-setup-instructions/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"expectedMcpTools": {
"get-setup-instructions": {
"minCalls": 1
},
"get-documentation": {
"minCalls": 1,
"expectedCalls": [
{
"id": "launch-button"
}
]
}
}
}
17 changes: 17 additions & 0 deletions eval/tasks/916-follow-setup-instructions/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import type { Hooks } from '../../types.ts';
import { combine, fromMcpToolsCoverage, fromTestPassRate } from '../../lib/quality/index.ts';

const hooks: Hooks = {
postPrepareTrial: async (trialArgs) => {
await fs.cp(
path.join(trialArgs.taskPath, 'seed', '@acme', 'ui'),
path.join(trialArgs.projectPath, 'node_modules', '@acme', 'ui'),
{ recursive: true },
);
},
calculateQuality: combine([fromMcpToolsCoverage, 0.5], [fromTestPassRate, 0.5]),
};

export default hooks;
20 changes: 20 additions & 0 deletions eval/tasks/916-follow-setup-instructions/manifests/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"v": 1,
"components": {
"launch-button": {
"id": "launch-button",
"name": "LaunchButton",
"path": "src/components/LaunchButton.tsx",
"summary": "Primary Acme UI call-to-action button for launch and navigation flows.",
"import": "import { LaunchButton } from \"@acme/ui\";",
"stories": [
{
"id": "launch-button--primary",
"name": "Primary",
"summary": "Primary button for launch-oriented actions.",
"snippet": "import { LaunchButton } from \"@acme/ui\";\n\n<LaunchButton tone=\"primary\">Open dashboard</LaunchButton>"
}
]
}
}
}
28 changes: 28 additions & 0 deletions eval/tasks/916-follow-setup-instructions/manifests/docs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"v": 1,
"docs": {
"entry-setup": {
"id": "entry-setup",
"name": "Entry Setup",
"title": "Entry Setup",
"path": "docs/entry-setup.mdx",
"tags": ["setup-instructions"],
"content": "# Entry Setup\n\nThe `@acme/ui` eval package is already available in this project.\n\n## Styles\n\nImport `@acme/ui/styles.css` exactly once from the app entry file before rendering any Acme UI components."
},
"provider-setup": {
"id": "provider-setup",
"name": "Provider Setup",
"title": "Provider Setup",
"path": "docs/provider-setup.mdx",
"tags": ["setup-instructions"],
"content": "# Provider Setup\n\n## Root provider\n\nWrap the rendered app in `<AcmeProvider theme=\"midnight\" density=\"comfortable\">`.\n\n## Root container\n\nPlace your UI inside an element with `data-acme-app=\"true\"`."
},
"release-notes": {
"id": "release-notes",
"name": "Release Notes",
"title": "Release Notes",
"path": "docs/release-notes.mdx",
"content": "# Release Notes\n\nThis docs entry is intentionally not tagged as setup instructions."
}
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot it seems that this test file is being picked up by the repo global test config, but it shouldn't. FIx that, it is causing tests to fail.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5f99616. The grading test is no longer stored as a repo-visible *.test.ts file; it now gets copied into the generated trial project during preGrade, so the repo-level Vitest config won’t pick it up anymore.

Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { describe, expect, it } from 'vitest';
import { readdirSync, readFileSync, statSync } from 'node:fs';
import * as path from 'node:path';

function collectSourceFiles(dirPath: string): string[] {
return readdirSync(dirPath).flatMap((entry) => {

Check failure on line 6 in eval/tasks/916-follow-setup-instructions/pre-grade/src/setup-instructions-followed.test.ts

View workflow job for this annotation

GitHub Actions / Test

tasks/916-follow-setup-instructions/pre-grade/src/setup-instructions-followed.test.ts

Error: ENOENT: no such file or directory, scandir '/home/runner/work/mcp/mcp/src' ❯ collectSourceFiles tasks/916-follow-setup-instructions/pre-grade/src/setup-instructions-followed.test.ts:6:9 ❯ readProjectSource tasks/916-follow-setup-instructions/pre-grade/src/setup-instructions-followed.test.ts:19:9 ❯ tasks/916-follow-setup-instructions/pre-grade/src/setup-instructions-followed.test.ts:25:21 ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯ Serialized Error: { errno: -2, code: 'ENOENT', syscall: 'scandir', path: '/home/runner/work/mcp/mcp/src' }
const entryPath = path.join(dirPath, entry);
const stats = statSync(entryPath);
if (stats.isDirectory()) {
return collectSourceFiles(entryPath);
}

return /\.(ts|tsx|css)$/.test(entry) ? [entryPath] : [];
});
}

function readProjectSource(): string {
const srcDir = path.join(process.cwd(), 'src');
return collectSourceFiles(srcDir)
.map((filePath) => readFileSync(filePath, 'utf-8'))
.join('\n');
}

describe('setup instructions were applied', () => {
const sourceText = readProjectSource();

it('imports the Acme UI stylesheet', () => {
expect(sourceText).toContain('@acme/ui/styles.css');
});

it('wraps the app in AcmeProvider with the documented props', () => {
expect(sourceText).toContain('AcmeProvider');
expect(sourceText).toMatch(/theme\s*=\s*(?:\{)?["']midnight["']\}?/);
expect(sourceText).toMatch(/density\s*=\s*(?:\{)?["']comfortable["']\}?/);
});

it('renders inside the documented Acme app root container', () => {
expect(sourceText).toMatch(/data-acme-app\s*=\s*(?:\{)?["']true["']\}?/);
});

it('uses LaunchButton for the requested CTA', () => {
expect(sourceText).toContain('LaunchButton');
expect(sourceText).toContain('Mission control');
expect(sourceText).toContain('Open dashboard');
});
});
10 changes: 10 additions & 0 deletions eval/tasks/916-follow-setup-instructions/prompt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Build a small "Mission control" page directly in `src/main.tsx` using the `LaunchButton` component from the Acme UI Storybook docs.

Do not guess any required library or root setup. Use the available Storybook MCP docs to discover both component usage and any project-level setup requirements before wiring the component in.

Requirements:

1. Implement this directly in `src/main.tsx`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot to make the eval trial's Storybook slightly more useful, we should instead prompt the agent to write this to a separate component file that we have static stories for, just so we can see the component in the Storybook.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in 5f99616. Task 916 now asks the agent to implement src/components/MissionControl.tsx, updates src/main.tsx to render it, and includes a static MissionControl story so the component is visible in the eval trial’s Storybook.

2. Render the heading text `Mission control`
3. Render a `LaunchButton` with the text `Open dashboard`
4. Keep the implementation minimal and production-like
16 changes: 16 additions & 0 deletions eval/tasks/916-follow-setup-instructions/seed/@acme/ui/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { ReactNode } from 'react';

export type AcmeProviderProps = {
children?: ReactNode;
theme?: 'midnight';
density?: 'comfortable';
};

export declare function AcmeProvider(props: AcmeProviderProps): ReactNode;

export type LaunchButtonProps = {
children?: ReactNode;
tone?: 'primary' | 'secondary';
};

export declare function LaunchButton(props: LaunchButtonProps): ReactNode;
24 changes: 24 additions & 0 deletions eval/tasks/916-follow-setup-instructions/seed/@acme/ui/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';

export function AcmeProvider({ children, theme = 'midnight', density = 'comfortable' }) {
return React.createElement(
'div',
{
'data-acme-provider': 'true',
'data-acme-theme': theme,
'data-acme-density': density,
},
children,
);
}

export function LaunchButton({ children, tone = 'primary' }) {
return React.createElement(
'button',
{
type: 'button',
'data-acme-launch-button': tone,
},
children,
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "@acme/ui",
"version": "0.0.0-eval",
"type": "module",
"types": "./index.d.ts",
"exports": {
".": "./index.js",
"./styles.css": "./styles.css"
}
}
19 changes: 19 additions & 0 deletions eval/tasks/916-follow-setup-instructions/seed/@acme/ui/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
:root {
color-scheme: dark;
font-family: Inter, system-ui, sans-serif;
}

[data-acme-provider='true'] {
min-height: 100vh;
padding: 2rem;
background: #111827;
color: #f9fafb;
}

[data-acme-launch-button] {
padding: 0.75rem 1rem;
border: 0;
border-radius: 999px;
background: #8b5cf6;
color: white;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ describe('buildServerInstructions', () => {
## Documentation Workflow

1. Call **list-all-documentation** once at the start of the task to discover available component and docs IDs.
2. Call **get-documentation** with an \`id\` from that list to retrieve full component docs, props, usage examples, and stories.
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.
2. If **get-setup-instructions** is available and you need installation, providers, theming, bootstrap, or other project wiring guidance, call it before using the library.
3. Call **get-documentation** with an \`id\` from that list to retrieve full component docs, props, usage examples, and stories.
4. Call **get-documentation-for-story** when you need additional docs from a specific story variant that was not included in the initial component documentation.

Use \`withStoryIds: true\` on **list-all-documentation** when you also need story IDs for inputs to other tools.

Expand Down Expand Up @@ -78,8 +79,9 @@ describe('buildServerInstructions', () => {
## Documentation Workflow

1. Call **list-all-documentation** once at the start of the task to discover available component and docs IDs.
2. Call **get-documentation** with an \`id\` from that list to retrieve full component docs, props, usage examples, and stories.
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.
2. If **get-setup-instructions** is available and you need installation, providers, theming, bootstrap, or other project wiring guidance, call it before using the library.
3. Call **get-documentation** with an \`id\` from that list to retrieve full component docs, props, usage examples, and stories.
4. Call **get-documentation-for-story** when you need additional docs from a specific story variant that was not included in the initial component documentation.

Use \`withStoryIds: true\` on **list-all-documentation** when you also need story IDs for inputs to other tools.

Expand Down
2 changes: 2 additions & 0 deletions packages/addon-mcp/src/mcp-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
addListAllDocumentationTool,
addGetDocumentationTool,
addGetStoryDocumentationTool,
addGetSetupInstructionsTool,
type Source,
} from '@storybook/mcp';
import type { Options } from 'storybook/internal/types';
Expand Down Expand Up @@ -83,6 +84,7 @@ const initializeMCPServer = async (options: Options, multiSource?: boolean) => {
logger.info('Experimental components manifest feature detected - registering component tools');
const contextAwareEnabled = () => server.ctx.custom?.toolsets?.docs ?? true;
await addListAllDocumentationTool(server, contextAwareEnabled);
await addGetSetupInstructionsTool(server, contextAwareEnabled, { multiSource });
await addGetDocumentationTool(server, contextAwareEnabled, { multiSource });
await addGetStoryDocumentationTool(server, contextAwareEnabled, { multiSource });
}
Expand Down
19 changes: 19 additions & 0 deletions packages/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ A fetch-compatible request handler for your `/mcp` endpoint.

- Registers these MCP tools:
- [`list-all-documentation`](https://storybook.js.org/docs/next/ai/mcp/overview/#list-all-documentation)
- [`get-setup-instructions`](https://storybook.js.org/docs/next/ai/mcp/overview/#get-setup-instructions)
- [`get-documentation`](https://storybook.js.org/docs/next/ai/mcp/overview/#get-documentation)
- [`get-documentation-for-story`](https://storybook.js.org/docs/next/ai/mcp/overview/#get-documentation-for-story)
- Uses HTTP transport from [`@tmcp/transport-http`](https://github.com/paoloricciuti/tmcp/).
Expand Down Expand Up @@ -286,6 +287,7 @@ import { ValibotJsonSchemaAdapter } from '@tmcp/adapter-valibot';
import {
addGetStoryDocumentationTool,
addGetDocumentationTool,
addGetSetupInstructionsTool,
addListAllDocumentationTool,
type StorybookContext,
} from '@storybook/mcp';
Expand All @@ -302,6 +304,7 @@ const server = new McpServer(
).withContext<StorybookContext>();

await addListAllDocumentationTool(server);
await addGetSetupInstructionsTool(server);
await addGetDocumentationTool(server);
await addGetStoryDocumentationTool(server);
```
Expand Down Expand Up @@ -335,6 +338,22 @@ Registers [documentation lookup](https://storybook.js.org/docs/next/ai/mcp/overv

When `options.multiSource` is `true`, the tool schema requires `storybookId` input.

#### `addGetSetupInstructionsTool`

Type:

```ts
(
server: McpServer<any, StorybookContext>,
enabled?: () => boolean | Promise<boolean>,
options?: { multiSource?: boolean },
) => Promise<void>;
```

Registers setup guidance lookup for all docs entries tagged `setup-instructions`.

Use this when an agent needs installation steps, providers/wrappers, theming/bootstrap configuration, CSS/font/token initialization, or other project wiring before consuming component APIs. When `options.multiSource` is `true`, the tool schema requires `storybookId` input.

#### `addGetStoryDocumentationTool`

Type:
Expand Down
3 changes: 3 additions & 0 deletions packages/mcp/bin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ describe('bin.ts stdio MCP server', () => {
expect.objectContaining({
name: 'list-all-documentation',
}),
expect.objectContaining({
name: 'get-setup-instructions',
}),
expect.objectContaining({
name: 'get-documentation',
}),
Expand Down
2 changes: 2 additions & 0 deletions packages/mcp/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import pkgJson from './package.json' with { type: 'json' };
import { addListAllDocumentationTool } from './src/tools/list-all-documentation.ts';
import { addGetStoryDocumentationTool } from './src/tools/get-documentation-for-story.ts';
import { addGetDocumentationTool } from './src/tools/get-documentation.ts';
import { addGetSetupInstructionsTool } from './src/tools/get-setup-instructions.ts';
import type { StorybookContext } from './src/types.ts';
import { parseArgs } from 'node:util';
import * as fs from 'node:fs/promises';
Expand Down Expand Up @@ -48,6 +49,7 @@ const server = new McpServer(
).withContext<StorybookContext>();

await addListAllDocumentationTool(server);
await addGetSetupInstructionsTool(server);
await addGetStoryDocumentationTool(server);
await addGetDocumentationTool(server);

Expand Down
1 change: 1 addition & 0 deletions packages/mcp/fixtures/default/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"name": "Getting Started",
"title": "Getting Started Guide",
"path": "docs/getting-started.mdx",
"tags": ["setup-instructions"],
"content": "# Getting Started\n\nWelcome to the component library. This guide will help you get up and running.\n\n## Installation\n\n```bash\nnpm install my-component-library\n```\n\n## Usage\n\nImport components and use them in your application."
},
"theming": {
Expand Down
1 change: 1 addition & 0 deletions packages/mcp/fixtures/small-docs-manifest.fixture.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"name": "Getting Started",
"title": "Getting Started Guide",
"path": "docs/getting-started.mdx",
"tags": ["setup-instructions"],
"content": "# Getting Started\n\nWelcome to the component library. This guide will help you get up and running.\n\n## Installation\n\n```bash\nnpm install my-component-library\n```\n\n## Usage\n\nImport components and use them in your application."
},
"theming": {
Expand Down
Loading
Loading