Skip to content

MCP Apps support#134

Merged
JReinhold merged 18 commits into
mainfrom
mcp-apps
Jan 23, 2026
Merged

MCP Apps support#134
JReinhold merged 18 commits into
mainfrom
mcp-apps

Conversation

@JReinhold
Copy link
Copy Markdown
Contributor

@JReinhold JReinhold commented Jan 22, 2026

This PR adds support for MCP Apps, as specced in https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx

It changes the current get-story-urls tool. It still just returns URLs as usual, but it contains meta information that instructs any supporting MCP clients to render the UI resource whenever the tool is called. The tool has been renamed to preview-stories.

This is currently supported in VSCode Insiders, or can be debugged further with the MCPJam Inspector.

mcp-app-1.mp4

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Jan 22, 2026

🦋 Changeset detected

Latest commit: 55f93ac

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

💥 An error occurred when fetching the changed packages and changesets in this PR
Some errors occurred when validating the changesets config:
The package or glob expression "@storybook/mcp-eval*" is specified in the `ignore` option but it is not found in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented Jan 22, 2026

npm i https://pkg.pr.new/storybookjs/mcp/@storybook/addon-mcp@134
npm i https://pkg.pr.new/storybookjs/mcp/@storybook/mcp@134

commit: 55f93ac

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 22, 2026

Codecov Report

❌ Patch coverage is 43.57542% with 101 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@e09477c). Learn more about missing BASE report.
⚠️ Report is 19 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...ools/preview-stories/preview-stories-app-script.ts 0.00% 68 Missing ⚠️
packages/addon-mcp/src/preview.ts 0.00% 24 Missing ⚠️
packages/addon-mcp/src/tools/preview-stories.ts 91.66% 2 Missing and 2 partials ⚠️
packages/addon-mcp/src/constants.ts 0.00% 2 Missing ⚠️
packages/addon-mcp/src/preset.ts 0.00% 2 Missing ⚠️
packages/addon-mcp/src/utils/build-args-param.ts 97.05% 1 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #134   +/-   ##
=======================================
  Coverage        ?   76.16%           
=======================================
  Files           ?       26           
  Lines           ?      646           
  Branches        ?      175           
=======================================
  Hits            ?      492           
  Misses          ?      109           
  Partials        ?       45           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 22, 2026

Bundle Report

Changes will decrease total bundle size by 27.23kB (-100.0%) ⬇️. This is within the configured threshold ✅

Detailed changes
Bundle name Size Change
@storybook/addon-mcp-esm (removed) -27.23kB (-100.0%) ⬇️

@JReinhold JReinhold self-assigned this Jan 22, 2026
@JReinhold JReinhold marked this pull request as ready for review January 22, 2026 20:04
Copilot AI review requested due to automatic review settings January 22, 2026 20:04
@JReinhold JReinhold review requested due to automatic review settings January 22, 2026 20:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support for the MCP Apps specification to the Storybook MCP addon, enabling AI agents to render interactive story previews directly in supporting MCP clients. The core change renames the get-story-urls tool to preview-stories and extends it to return both raw URLs and structured data for MCP App rendering.

Changes:

  • Renamed get-story-urls tool to preview-stories with MCP Apps support via UI resources
  • Added MCP App rendering infrastructure including HTML templates and browser-side scripts for size reporting
  • Upgraded Storybook dependencies from 10.2.0-alpha.14 to 10.3.0-alpha.0
  • Updated all documentation, tests, and configuration files to reflect the renamed tool

Reviewed changes

Copilot reviewed 26 out of 27 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
pnpm-workspace.yaml Bumped Storybook catalog versions to 10.3.0-alpha.0
pnpm-lock.yaml Updated lockfile with new Storybook versions and transitive dependencies (including jsdom)
packages/addon-mcp/tsdown.config.ts Added browser build targets for preview.ts and preview-stories-app-script.ts
packages/addon-mcp/src/types.ts Removed unused StoryUrlArray type (replaced by structured PreviewStoriesOutput)
packages/addon-mcp/src/tools/preview-stories/preview-stories-app-template.html New HTML template for MCP App UI with theme support and responsive iframes
packages/addon-mcp/src/tools/preview-stories/preview-stories-app-script.ts New browser-side MCP App protocol implementation for story rendering
packages/addon-mcp/src/tools/preview-stories.ts Renamed and enhanced from get-story-urls.ts with MCP resource support and structured output
packages/addon-mcp/src/tools/preview-stories.test.ts Renamed test file with updated assertions for structuredContent
packages/addon-mcp/src/tools/get-storybook-story-instructions.ts Updated to reference new PREVIEW_STORIES_TOOL_NAME
packages/addon-mcp/src/tools/get-storybook-story-instructions.test.ts Updated test assertions for renamed tool
packages/addon-mcp/src/tools/get-story-urls.ts Deleted (replaced by preview-stories.ts)
packages/addon-mcp/src/template.html Updated tool name display from get-story-urls to preview-stories
packages/addon-mcp/src/storybook-story-instructions.md Updated tool name reference in instructions template
packages/addon-mcp/src/preview.ts New browser-side script for iframe size reporting to MCP App
packages/addon-mcp/src/preset.ts Added previewAnnotations to inject preview.ts into Storybook
packages/addon-mcp/src/mcp-handler.ts Updated tool registration and added resources capability
packages/addon-mcp/src/constants.ts New file with MCP App parameter and event constants
packages/addon-mcp/package.json Added new export paths for preview-annotation and app-script
packages/addon-mcp/README.md Updated documentation with new tool name
apps/internal-storybook/tests/mcp-endpoint.e2e.test.ts Updated E2E tests with new tool schema and structuredContent assertions
apps/internal-storybook/.storybook/main.ts Commented out debug logLevel
apps/internal-storybook/.claude/settings.local.json Updated tool permission from get-story-urls to preview-stories
.github/instructions/eval.instructions.md Fixed play function to use canvas directly
.github/instructions/addon-mcp.instructions.md Updated tool references (contains unresolved merge conflicts)
.github/copilot-instructions.md Updated tool name references in examples
.claude/settings.local.json Updated tool permission reference
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread packages/addon-mcp/src/tools/preview-stories/preview-stories-app-template.html Outdated
Comment thread packages/addon-mcp/src/tools/preview-stories/preview-stories-app-template.html Outdated
Comment thread packages/addon-mcp/src/preview.ts
Comment thread packages/addon-mcp/src/tools/preview-stories/preview-stories-app-script.ts Outdated
Comment thread apps/internal-storybook/.storybook/main.ts
Comment thread packages/addon-mcp/src/tools/preview-stories.ts Outdated
Comment thread packages/addon-mcp/src/tools/preview-stories.ts
Comment thread packages/addon-mcp/src/tools/preview-stories/preview-stories-app-script.ts Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 27 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Comment thread .changeset/eighty-dryers-yawn.md Outdated
Comment thread packages/addon-mcp/src/tools/preview-stories/preview-stories-app-template.html Outdated
Comment thread packages/addon-mcp/src/tools/preview-stories/preview-stories-app-script.ts Outdated
Copilot AI review requested due to automatic review settings January 22, 2026 20:19
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 27 changed files in this pull request and generated 10 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

packages/addon-mcp/README.md:171

  • The README documentation for the Preview Stories tool (lines 156-171) doesn't mention the new MCP Apps capability - that stories can now be rendered directly in the agent chat interface for supporting MCP clients. Consider adding a note about this feature and how it works (e.g., "In MCP clients that support MCP Apps, stories will be rendered directly in the chat interface. Otherwise, the tool returns URLs that users can visit.")
#### 2. Preview Stories (`preview-stories`)

Allows agents to retrieve direct URLs to specific stories in your Storybook. The agent can request URLs for multiple stories by providing:

- `absoluteStoryPath`: Absolute path to the story file
- `exportName`: The export name of the story
- `explicitStoryName`: Optional explicit story name

Example agent usage:

Prompt: I need to see the primary variant of the Button component

Agent calls tool, gets response:
http://localhost:6006/?path=/story/example-button--primary

Comment thread .changeset/eighty-dryers-yawn.md
Comment thread packages/addon-mcp/src/tools/preview-stories.ts
Comment thread pnpm-workspace.yaml
Comment thread packages/addon-mcp/src/preview.ts
Comment thread packages/addon-mcp/src/tools/preview-stories.ts
Comment thread packages/addon-mcp/src/tools/preview-stories.ts
Comment on lines +147 to +150
let errorMessage = `No story found for export name "${exportName}" with absolute file path "${absoluteStoryPath}"`;
if (!explicitStoryName) {
errorMessage += ` (did you forget to pass the explicit story name?)`;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would this error message be something presented to the user? or sent back to the AI/LLM to try and fix?

Is this error message good enough to achieve either or both?

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.

it's sent back to the LLM, which always triggers the LLM to re-call the tool with the correct arguments. It's not shown to the user.

Ideally it would always call the tool with correct parameters, but I haven't figured that out yet.

Comment on lines +12 to +50
interface McpUiHostStyles {
variables?: Record<string, string | undefined>;
css?: {
fonts?: string;
};
}

interface McpUiHostContext {
[key: string]: unknown;
theme?: 'light' | 'dark';
styles?: McpUiHostStyles;
displayMode?: 'inline' | 'fullscreen' | 'pip';
availableDisplayModes?: string[];
locale?: string;
timeZone?: string;
userAgent?: string;
platform?: 'web' | 'desktop' | 'mobile';
deviceCapabilities?: {
touch?: boolean;
hover?: boolean;
};
safeAreaInsets?: {
top: number;
right: number;
bottom: number;
left: number;
};
}

interface McpUiInitializeResult {
protocolVersion: string;
hostInfo: {
name: string;
version: string;
};
hostCapabilities: Record<string, unknown>;
hostContext: McpUiHostContext;
[key: string]: unknown;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Are there no packages to import these types from?

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.

Yes there is @modelcontextprotocol/ext-apps, but that's the package that depends on 400 MBs of Bun binaries, I don't want to introduce that to my workflow.

I am in contact with one of the authors that is working on a minimal package that would help us out here, not only with the types, but with utility functions for the MCP host messaging work we do.


for (const storyResult of stories) {
if ('error' in storyResult) {
console.warn('Skipping story with error:', storyResult.error);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you explain where this logs to?

Is it useful to the AI/LLM? To the user? only for debugging the MCP itself?

Copy link
Copy Markdown
Contributor Author

@JReinhold JReinhold Jan 23, 2026

Choose a reason for hiding this comment

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

This log goes into VSCode's console, which is only shown when opening VSCode's internal DevTools. The tool result already tells the LLM that the story URL is wrong, so I don't think we need to expose it from here as well.

In fact, if there are any story URLs that returns errors instead, the tool response goes into an isError: true-mode, which makes VSCode not even render the MCP app at all, so I don't even think this code is reachable in practice.

Copy link
Copy Markdown
Member

@ndelangen ndelangen left a comment

Choose a reason for hiding this comment

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

Since we cannot fully e2e test this automatically, we should write a detailed manual steps on how development & testing of this repo should happen.

Please automate as many steps as possible, such as building, running the storybooks, and whatever else might be needed to do manual QA.

Then have explicit steps that anyone could follow which has a clear "you should expect to see X" so future us know if everything still functions correctly 1 year from now. 🙏

Copilot AI review requested due to automatic review settings January 23, 2026 10:21
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 38 out of 39 changed files in this pull request and generated 10 comments.

Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)

packages/addon-mcp/src/tools/preview-stories.test.ts:502

  • The test suite for preview-stories.ts doesn't include tests for the MCP resource registration. Consider adding tests to verify that the resource is correctly registered with the proper URI, MIME type, and metadata (CSP domains, prefersBorder, etc.). This would ensure the MCP App integration is properly tested.

'@storybook/addon-mcp': patch
---

Add support for MCP App, rendering stories directly in the agent chat in MCP clients that support it
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

The tool has been renamed from get-story-urls to preview-stories, which is a breaking change for users who have configured MCP client permissions to allow/deny the old tool name. While the changeset marks this as a patch, consider documenting this breaking change in the changeset description and providing migration guidance (e.g., updating MCP client configuration files like .claude/settings.local.json).

Suggested change
Add support for MCP App, rendering stories directly in the agent chat in MCP clients that support it
Add support for MCP App, rendering stories directly in the agent chat in MCP clients that support it.
Note: the story preview tool has been renamed from `get-story-urls` to `preview-stories`. This can be a
breaking change for MCP clients that have explicit allow/deny lists based on tool names.
Migration guidance:
- Update your MCP client configuration (for example, `.claude/settings.local.json` or equivalent) to
allow the new `preview-stories` tool.
- If you previously allowed or denied `get-story-urls`, replace it with `preview-stories` in your
configuration. Some clients may allow you to list both names during a transition period.

Copilot uses AI. Check for mistakes.
Comment on lines +185 to +186
"description": "If the story has an explicit name set via the "name" propoerty, that is different from the export name, provide it here.
Otherwise don't set this.",
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

There's a typo in the word "property" - it's spelled as "propoerty" in the test snapshot. This snapshot will need to be updated after fixing the typo in types.ts.

Copilot uses AI. Check for mistakes.
Comment on lines +103 to +132
window.parent.postMessage({ jsonrpc: '2.0', id, method, params }, '*');

window.addEventListener('message', function listener(event: MessageEvent) {
if (event.data?.id !== id) {
return;
}
window.removeEventListener('message', listener);
if (event.data?.result) {
resolve(event.data.result);
} else if (event.data?.error) {
reject(new Error(String(event.data.error)));
}
});
return promise;
}

function sendHostNotification(method: McpMethod, params: unknown): void {
window.parent.postMessage({ jsonrpc: '2.0', method, params }, '*');
}

function onHostNotification<T = unknown>(
method: McpMethod,
handler: (params: T) => void,
): void {
window.addEventListener('message', function listener(event: MessageEvent) {
if (event.data?.method === method) {
handler(event.data.params as T);
}
});
}
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

The postMessage calls use a wildcard origin ('*') which could expose the application to security risks. While this follows the MCP Apps specification pattern, the message listeners should validate the event.origin against expected origins (like the Storybook origin from CSP domains). Consider adding origin validation in the message event listeners to ensure messages are only accepted from trusted sources.

Copilot uses AI. Check for mistakes.
],
framework: '@storybook/react-vite',
logLevel: 'debug',
// logLevel: 'debug',
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

The logLevel has been commented out, changing from 'debug' to the default level. While this is likely intentional to reduce noise during development, this change should either be committed separately or documented in the PR description since it's not directly related to MCP Apps support.

Copilot uses AI. Check for mistakes.
explicitStoryName: v.pipe(
v.optional(v.string()),
v.description(
`If the story has an explicit name set via the "name" propoerty, that is different from the export name, provide it here.
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

There's a typo in the word "property" - it's spelled as "propoerty" in the description.

Copilot uses AI. Check for mistakes.
"type": "string",
},
"explicitStoryName": {
"description": "If the story has an explicit name set via the "name" propoerty, that is different from the export name, provide it here.
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

There's a typo in the word "property" - it's spelled as "propoerty" in the test snapshot. This snapshot will need to be updated after fixing the typo in types.ts.

Suggested change
"description": "If the story has an explicit name set via the "name" propoerty, that is different from the export name, provide it here.
"description": "If the story has an explicit name set via the "name" property, that is different from the export name, provide it here.

Copilot uses AI. Check for mistakes.
Comment thread turbo.json
},
"storybook": {
"dependsOn": ["^build"],
"cache": false,
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

Disabling cache for the storybook task may slow down development workflows. The cache: false setting means Turbo won't cache the storybook task outputs, which could impact build performance. Consider documenting why this change was necessary (e.g., if there are issues with stale caches when developing the MCP App), or investigate if there's a more targeted solution.

Copilot uses AI. Check for mistakes.
Comment thread package.json
"publint": "turbo run publint",
"release": "turbo run build && pnpm changeset publish",
"storybook": "turbo watch storybook",
"storybook": "turbo watch storybook --filter=@storybook/mcp-internal-storybook",
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

The storybook script now filters to only run @storybook/mcp-internal-storybook, which changes the behavior from running all storybook tasks to just the internal one. This could be a breaking change for developers expecting to run multiple Storybook instances. Consider documenting this change in the commit message or adding a separate script (e.g., storybook:internal) if both behaviors are needed.

Suggested change
"storybook": "turbo watch storybook --filter=@storybook/mcp-internal-storybook",
"storybook": "turbo watch storybook",
"storybook:internal": "turbo watch storybook --filter=@storybook/mcp-internal-storybook",

Copilot uses AI. Check for mistakes.
}

// is object
if (typeof value === 'object' && value !== null) {
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

Variable 'value' is of type date, object or regular expression, but it is compared to an expression of type null.

Copilot uses AI. Check for mistakes.
'utf-8',
);

const appHtml = appTemplate.replace(
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

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

The base expression of this property access is always undefined.

Copilot uses AI. Check for mistakes.
@JReinhold JReinhold merged commit 01d149a into main Jan 23, 2026
11 checks passed
@JReinhold JReinhold deleted the mcp-apps branch January 23, 2026 10:32
@storybook-app-bot storybook-app-bot Bot mentioned this pull request Jan 23, 2026
const DEBOUNCE_MS = 100;

function sendSizeToParent() {
const height = document.body.scrollHeight;
Copy link
Copy Markdown

@liady liady Jan 24, 2026

Choose a reason for hiding this comment

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

perhaps it's more accurate to check document.documentElement.scrollHeight (and observer document.documentElement in the rest of the file) , since this is more accurately tied to the external iframe's height (in cases where there are paddings/margins, etc)

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.

I had that at one point, but it would actually be the wrong numbers. Here we specifically want to send the required height of the content, and we assume that the content stretches the body when needed. The problem with using documentElement (or html) is that it is always 100%, so that number is always the height of the iframe regardless of the content, which the parent already knows and controls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants