Skip to content
Merged
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
8 changes: 8 additions & 0 deletions .changeset/components-manifest-flag-rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@storybook/addon-mcp': patch
'@storybook/mcp': patch
---

Rename feature flag `experimentalComponentsManifest` → `componentsManifest`

The Storybook feature flag has been renamed from `experimentalComponentsManifest` to `componentsManifest` and now defaults to `true` in Storybook core.
2 changes: 1 addition & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export { addMyTool, MY_TOOL_NAME } from './tools/my-tool.ts';
- `addon-mcp` depends on `@storybook/mcp` (workspace dependency)
- `AddonContext` extends `StorybookContext` to ensure type compatibility
- Component manifest tools are conditionally registered based on feature flags:
- Checks `features.experimentalComponentsManifest` flag
- Checks `features.componentsManifest` flag (or `features.experimentalComponentsManifest` for backwards compatibility)
- Checks for `experimental_manifests` preset
- Only registers `addListAllDocumentationTool` and `addGetDocumentationTool` when enabled
- Context includes `request` (HTTP Request object) which tools use to determine manifest location
Expand Down
1 change: 0 additions & 1 deletion .github/instructions/addon-mcp.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ The addon supports two toolsets that can be enabled/disabled:
2. **`docs`** (default: true)
- `list-all-documentation`: List all available components from manifest
- `get-documentation`: Get detailed component documentation
- Requires experimental feature flag `features.experimentalComponentsManifest`

**Configuration Methods:**

Expand Down
3 changes: 0 additions & 3 deletions eval/templates/grading/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,5 @@ const config: StorybookConfig = {
'@storybook/addon-mcp',
],
framework: '@storybook/react-vite',
features: {
experimentalComponentsManifest: true,
},
};
export default config;
23 changes: 14 additions & 9 deletions packages/addon-mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,19 @@ export default {
options: {
toolsets: {
dev: true, // Tools for story URL retrieval and UI building instructions (default: true)
docs: true, // Tools for component manifest and documentation (default: true, requires experimental feature flag below 👇)
docs: true, // Tools for component manifest and documentation (default: true)
},
},
},
],
features: {
experimentalComponentsManifest: true, // Enable manifest generation for the docs toolset, only supported in React-based setups.
},
// componentsManifest is enabled by default in recent Storybook versions, no need to set it
};
```

**Available Toolsets:**

- `dev`: Enables [Dev Tools](#dev-tools)
- `docs`: Enables [Documentation Tools](#docs-tools-experimental)
- `docs`: Enables [Documentation Tools](#docs-tools)

Disabling the Dev Tools is useful when you want to try out the same experience that your external component consumers will get, because they only get the Component Documentation Tools.

Expand Down Expand Up @@ -172,24 +170,31 @@ Agent calls tool, gets response:
http://localhost:6006/?path=/story/example-button--primary
```

### Docs Tools (Experimental)
### Docs Tools

These additional tools are available when the **experimental** component manifest feature is enabled. They provide agents with detailed documentation about your UI components.
These additional tools are available when the component manifest feature is enabled. They provide agents with detailed documentation about your UI components.

**Requirements:**

- Storybook version 10.1.0 or higher (currently only available as prereleases, `storybook@next`)
- React-based framework (`react-vite`, `nextjs-vite`, `nextjs`, `react-webpack5`)
- Feature flag `features.experimentalComponentsManifest` set to `true` in `.storybook/main.js`
- Feature flag `features.componentsManifest` enabled (defaults to `true` in recent Storybook versions)

**To enable:**

The `componentsManifest` feature is enabled by default in recent Storybook versions — no configuration needed.

If you are on an older Storybook version that doesn't default to `true`, you may need to enable it explicitly. Use the flag that matches your Storybook version:

```javascript
// .storybook/main.js
export default {
// ... other config
features: {
experimentalComponentsManifest: true,
// For Storybook 10.3.x and later:
componentsManifest: true,
Comment thread
JReinhold marked this conversation as resolved.
// For older Storybook versions (before the flag was renamed):
// experimentalComponentsManifest: true,
},
};
Comment thread
JReinhold marked this conversation as resolved.
```
Expand Down
2 changes: 1 addition & 1 deletion packages/addon-mcp/src/mcp-handler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ describe('mcpServerHandler', () => {
return { disableTelemetry: false };
}
if (key === 'features') {
return { experimentalComponentsManifest: true };
return { componentsManifest: true };
}
if (key === 'experimental_manifests') {
return vi.fn();
Expand Down
10 changes: 5 additions & 5 deletions packages/addon-mcp/src/preset.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
presets: {
apply: vi.fn((key: string) => {
if (key === 'features') {
return Promise.resolve({ experimentalComponentsManifest: false });
return Promise.resolve({ componentsManifest: false });
}
return Promise.resolve(undefined);
}),
Expand Down Expand Up @@ -172,7 +172,7 @@
presets: {
apply: vi.fn((key: string) => {
if (key === 'features') {
return Promise.resolve({ experimentalComponentsManifest: false });
return Promise.resolve({ componentsManifest: false });
}
return Promise.resolve(undefined);
}),
Expand Down Expand Up @@ -265,7 +265,7 @@
});
}
if (key === 'features') {
return Promise.resolve({ experimentalComponentsManifest: false });
return Promise.resolve({ componentsManifest: false });
}
return Promise.resolve(undefined);
}),
Expand All @@ -275,7 +275,7 @@
await (experimental_devServer as any)(mockApp, optionsWithRefs);

// The preset should have called presets.apply('refs')
expect(optionsWithRefs.presets.apply).toHaveBeenCalledWith('refs', {});

Check warning on line 278 in packages/addon-mcp/src/preset.test.ts

View workflow job for this annotation

GitHub Actions / Lint

typescript-eslint(unbound-method)

void`, or consider using an arrow function instead.
});

it('should handle refs config returning non-object gracefully', async () => {
Expand All @@ -285,7 +285,7 @@
apply: vi.fn((key: string) => {
if (key === 'refs') return Promise.resolve(null);
if (key === 'features') {
return Promise.resolve({ experimentalComponentsManifest: false });
return Promise.resolve({ componentsManifest: false });
}
return Promise.resolve(undefined);
}),
Expand All @@ -304,7 +304,7 @@
apply: vi.fn((key: string) => {
if (key === 'refs') return Promise.reject(new Error('Config error'));
if (key === 'features') {
return Promise.resolve({ experimentalComponentsManifest: false });
return Promise.resolve({ componentsManifest: false });
}
return Promise.resolve(undefined);
}),
Expand Down
22 changes: 21 additions & 1 deletion packages/addon-mcp/src/tools/is-manifest-available.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import type { Options } from 'storybook/internal/types';

function createMockOptions({
featureFlag = false,
featureFlagName = 'componentsManifest' as 'componentsManifest' | 'experimentalComponentsManifest',
hasManifests = false,
hasLegacyComponentManifestGenerator = false,
hasFeaturesObject = true,
}: {
featureFlag?: boolean;
featureFlagName?: 'componentsManifest' | 'experimentalComponentsManifest';
hasManifests?: boolean;
hasLegacyComponentManifestGenerator?: boolean;
hasFeaturesObject?: boolean;
Expand All @@ -17,7 +19,7 @@ function createMockOptions({
presets: {
apply: vi.fn(async (key: string) => {
if (key === 'features') {
return hasFeaturesObject ? { experimentalComponentsManifest: featureFlag } : {};
return hasFeaturesObject ? { [featureFlagName]: featureFlag } : {};
}
if (key === 'experimental_manifests') {
return hasManifests ? { components: { v: 1, components: {} } } : undefined;
Expand Down Expand Up @@ -67,6 +69,24 @@ describe('getManifestStatus', () => {
options: { hasManifests: true, hasFeaturesObject: false },
expected: { available: false, hasManifests: true, hasFeatureFlag: false },
},
{
description: 'legacy experimentalComponentsManifest flag is true (backwards compat)',
options: {
featureFlag: true,
featureFlagName: 'experimentalComponentsManifest' as const,
hasManifests: true,
},
expected: { available: true, hasManifests: true, hasFeatureFlag: true },
},
{
description: 'legacy experimentalComponentsManifest flag is false (backwards compat)',
options: {
featureFlag: false,
featureFlagName: 'experimentalComponentsManifest' as const,
hasManifests: true,
},
expected: { available: false, hasManifests: true, hasFeatureFlag: false },
},
])('should return correct status when $description', async ({ options, expected }) => {
const mockOptions = createMockOptions(options);
const result = await getManifestStatus(mockOptions);
Expand Down
4 changes: 3 additions & 1 deletion packages/addon-mcp/src/tools/is-manifest-available.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export const getManifestStatus = async (options: Options): Promise<ManifestStatu
]);

const hasManifests = !!manifests || !!legacyComponentManifestGenerator;
const hasFeatureFlag = !!features?.experimentalComponentsManifest;
const hasFeatureFlag = !!(
features?.componentsManifest ?? features?.experimentalComponentsManifest
);

return {
available: hasFeatureFlag && hasManifests,
Expand Down
5 changes: 3 additions & 2 deletions packages/mcp/src/utils/get-manifest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ describe('getManifest', () => {
"You must either pass the original request forward to the server context, or set a custom manifestProvider that doesn't need the request",
);
});
it('should throw ManifestGetError when fetch fails with 404 and include hint about experimentalComponentsManifest', async () => {
it('should throw ManifestGetError when fetch fails with 404 and include hint about componentsManifest', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: false,
status: 404,
Expand All @@ -123,6 +123,7 @@ describe('getManifest', () => {
await expect(getManifests(request)).rejects.toThrow(
'Failed to fetch manifest: 404 Not Found',
);
await expect(getManifests(request)).rejects.toThrow('componentsManifest');
await expect(getManifests(request)).rejects.toThrow('experimentalComponentsManifest');
});
Comment thread
JReinhold marked this conversation as resolved.

Expand All @@ -140,7 +141,7 @@ describe('getManifest', () => {
try {
await getManifests(request);
} catch (error) {
expect((error as Error).message).not.toContain('experimentalComponentsManifest');
expect((error as Error).message).not.toContain('componentsManifest');
}
});

Expand Down
2 changes: 1 addition & 1 deletion packages/mcp/src/utils/get-manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ export async function getManifests(
const reason = componentResult.reason;
const is404 = reason instanceof ManifestGetError && reason.message.includes('404');
const hint = is404
? `\nHint: The Storybook at this URL may not have the component manifest enabled. Add \`features: { experimentalComponentsManifest: true }\` to its main.ts config.`
? `\nHint: The Storybook at this URL may not have the component manifest enabled. Add \`features: { componentsManifest: true }\` (or \`features: { experimentalComponentsManifest: true }\` for older Storybook versions) to its main.ts config.`
: '';
throw new ManifestGetError(
`Failed to get component manifest: ${reason instanceof Error ? reason.message : String(reason)}${hint}`,
Expand Down
Loading