Skip to content

CLI: Show MCP workflow instructions in AI help#35164

Merged
kasperpeulen merged 5 commits into
nextfrom
kasper/ai-cli-mcp-instructions
Jun 16, 2026
Merged

CLI: Show MCP workflow instructions in AI help#35164
kasperpeulen merged 5 commits into
nextfrom
kasper/ai-cli-mcp-instructions

Conversation

@kasperpeulen

@kasperpeulen kasperpeulen commented Jun 15, 2026

Copy link
Copy Markdown
Member

Closes #35163

What I did

  • Parse optional initialize.result.instructions from the existing MCP handshake used by storybook ai.
  • Render non-empty workflow instructions in top-level storybook ai --help before the dynamic Storybook commands list.
  • Keep the command list sourced from tools/list, preserve the existing degraded help states (no Storybook, addon missing/starting/erroring, server unreachable, no commands), and leave feature-flag-off behavior unchanged.
  • Fetch the initialize metadata on the full request budget so a slow or still-starting dev server can't drop the instructions while tools/list succeeds — the only thing that slows the handshake is the dev server starting up, which the command waits through anyway.
  • Require the instructions via an invariant rather than degrading to a note: addon-mcp gates instructions and tools on the same toolsets, so when commands are present, missing instructions can only be a server contract bug.
  • Note: this also changes the initialize handshake budget for command execution (3s -> full request budget); the 3s originated with the telemetry handshake in Telemetry for storybook ai <command> CLI (ai-command event + clientInfo segmentation) #35131.
  • Added unit coverage for initialize instruction parsing (JSON/SSE, malformed/error/empty responses) and the help workflow-instructions rendering.
  • Manual QA on canary 0.0.0-pr-35164-sha-fe7f4278 against the storybookjs/mcp internal Storybook: feature-flagged help renders Storybook workflow instructions before Storybook commands and keeps the dynamic command list from tools/list; feature-flag-off help remains unchanged.
  • Codex thermo-nuclear code-quality review: no actionable findings.
  • Claude Opus 4.8 Max thermo-nuclear review: low-severity findings addressed in follow-up commit 9f2d2ff (explicit tool-list type and initialize metadata tests for malformed/SSE responses).

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

Caution

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

  1. Start a Storybook project with @storybook/addon-mcp installed and ready.
  2. Run STORYBOOK_FEATURE_AI_CLI=1 storybook ai --help.
  3. Confirm the output includes a # Storybook workflow instructions section (from the MCP initialize response) before the # Storybook commands list.
  4. Confirm the command list still reflects the server's tools/list output.
  5. Confirm help still degrades gracefully to an unavailable note when no Storybook is running, MCP is missing/starting/erroring, or the command server cannot be reached.
  6. Confirm storybook ai without STORYBOOK_FEATURE_AI_CLI=1 still exposes the pre-existing setup behavior only.

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Declare whether manual QA will be needed for this PR during the next release, through qa:needed or qa:skip

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This pull request has been released as version 0.0.0-pr-35164-sha-1aaf82d2. Try it out in a new sandbox by running npx storybook@0.0.0-pr-35164-sha-1aaf82d2 sandbox or in an existing project with npx storybook@0.0.0-pr-35164-sha-1aaf82d2 upgrade.

More information
Published version 0.0.0-pr-35164-sha-1aaf82d2
Triggered by @huang-julien
Repository storybookjs/storybook
Branch kasper/ai-cli-mcp-instructions
Commit 1aaf82d2
Datetime Mon Jun 15 13:47:37 UTC 2026 (1781531257)
Workflow run 27550786817

To request a new release of this pull request, mention the @storybookjs/core team.

core team members can create a new canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=35164

Summary by CodeRabbit

  • New Features
    • Help output can now include server-provided “workflow instructions” when available via the MCP initialize handshake.
    • Tool discovery now carries server metadata to enrich help generation.
  • Bug Fixes
    • Improved resilience across JSON vs SSE initialize responses, extracting and normalizing instructions when possible and falling back safely when missing/invalid.
    • Non-OK handshake responses no longer interfere with tool listing.
  • Tests
    • Expanded MCP initialize/metadata normalization coverage and updated help-generation expectations (including new workflow instructions rendering).

@kasperpeulen kasperpeulen added feature request ci:normal Run our default set of CI jobs (choose this for most PRs). qa:needed Pull Requests that will need manual QA prior to release. labels Jun 15, 2026
Comment thread code/core/src/cli/ai/mcp/run-tool.ts Outdated
Comment thread code/core/src/cli/ai/mcp/run-tool.ts Outdated
@kasperpeulen kasperpeulen force-pushed the kasper/ai-cli-mcp-instructions branch 4 times, most recently from 1533c5b to 6844c03 Compare June 15, 2026 08:38
@kasperpeulen kasperpeulen marked this pull request as ready for review June 15, 2026 09:49
@kasperpeulen kasperpeulen force-pushed the kasper/ai-cli-mcp-instructions branch from 6844c03 to b423f76 Compare June 15, 2026 09:54
@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6919f8e2-86aa-44e9-8737-31141b9052f8

📥 Commits

Reviewing files that changed from the base of the PR and between 1aaf82d and ce6819f.

📒 Files selected for processing (3)
  • code/core/src/cli/ai/mcp/client.ts
  • code/core/src/cli/ai/mcp/run-tool.test.ts
  • code/core/src/cli/ai/mcp/run-tool.ts
💤 Files with no reviewable changes (1)
  • code/core/src/cli/ai/mcp/run-tool.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • code/core/src/cli/ai/mcp/client.ts

📝 Walkthrough

Walkthrough

The MCP client gains two exported types (McpServerMetadata, McpToolList) and a new function listMcpToolsWithServerMetadata that returns tool descriptors together with instructions parsed from the initialize handshake. initializeMcpSession now returns { sessionId, serverMetadata }, threading metadata through sendJsonRpcRequest, and buildStorybookCommandsHelp renders an optional "Storybook workflow instructions" section using the extracted metadata.

Changes

MCP server metadata from initialize handshake

Layer / File(s) Summary
MCP client types and contracts
code/core/src/cli/ai/mcp/client.ts
Adds McpServerMetadata (containing optional instructions), McpToolList (combining tools with metadata), InitializeResultSchema for JSON-RPC validation, and InitializeMcpSessionResult describing the handshake return shape.
Initialize handshake with best-effort metadata extraction
code/core/src/cli/ai/mcp/client.ts
Refactors initializeMcpSession to return { sessionId, serverMetadata } instead of just session id; reads Mcp-Session-Id from response header; cancels response body on non-OK; parses initialize JSON-RPC result to extract trimmed instructions metadata while swallowing parse failures and returning empty metadata on error.
Request plumbing, response parsing, and listMcpToolsWithServerMetadata
code/core/src/cli/ai/mcp/client.ts
Updates sendJsonRpcRequest to return { result, serverMetadata } and thread session id; switches readJsonRpcResponse to JSON.parse(body) for JSON; refactors listMcpTools to delegate to listMcpToolsWithServerMetadata for backward compatibility; introduces listMcpToolsWithServerMetadata to call tools/list and return { tools, serverMetadata }.
Client test infrastructure and comprehensive metadata extraction tests
code/core/src/cli/ai/mcp/client.test.ts
Adds sseResponse fixture with custom header support; refactors initializeResponse and adds initializeSseResponse fixtures supporting optional instructions; adds toolListResult helper; updates non-ok handshake test to assert stream cancellation; comprehensive parameterized tests for listMcpToolsWithServerMetadata covering instruction extraction from JSON/SSE responses, omission on invalid/empty/whitespace values, fallback on malformed/errored initialize, and backward compatibility of listMcpTools.
buildStorybookCommandsHelp integration with server instructions
code/core/src/cli/ai/mcp/run-tool.ts
Replaces listMcpTools with listMcpToolsWithServerMetadata; conditionally prepends a "Storybook workflow instructions" section before commands when serverMetadata.instructions is present and non-empty; updates help header wording to "Storybook help from …".
Run-tool test coverage for help generation with instructions
code/core/src/cli/ai/mcp/run-tool.test.ts
Switches shared mocking from listMcpTools to listMcpToolsWithServerMetadata with { tools, serverMetadata: { instructions } }; reworks success-path tests to assert help output including workflow instructions rendering and updated header format; adds parameterized degradation tests for missing/empty/whitespace instructions; converts "MCP unreachable" and "no tools" scenario mocks to use listMcpToolsWithServerMetadata.

Sequence Diagram(s)

sequenceDiagram
  participant buildStorybookCommandsHelp
  participant listMcpToolsWithServerMetadata
  participant sendJsonRpcRequest
  participant initializeMcpSession
  participant McpServer as MCP Server

  rect rgba(70, 130, 180, 0.5)
    note over buildStorybookCommandsHelp,McpServer: Tool list + metadata fetch
    buildStorybookCommandsHelp->>listMcpToolsWithServerMetadata: fetch tools + metadata
    listMcpToolsWithServerMetadata->>sendJsonRpcRequest: tools/list
    sendJsonRpcRequest->>initializeMcpSession: POST initialize
    initializeMcpSession->>McpServer: initialize request
    McpServer-->>initializeMcpSession: response (Mcp-Session-Id header + body)
    initializeMcpSession->>initializeMcpSession: parse instructions from result
    initializeMcpSession-->>sendJsonRpcRequest: { sessionId, serverMetadata }
    sendJsonRpcRequest->>McpServer: POST tools/list (Mcp-Session-Id)
    McpServer-->>sendJsonRpcRequest: tools result
    sendJsonRpcRequest-->>listMcpToolsWithServerMetadata: { result, serverMetadata }
    listMcpToolsWithServerMetadata-->>buildStorybookCommandsHelp: { tools, serverMetadata }
  end
  buildStorybookCommandsHelp->>buildStorybookCommandsHelp: check serverMetadata.instructions
  note right of buildStorybookCommandsHelp: Render "Storybook workflow instructions" block if present and non-empty
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/core/src/cli/ai/mcp/client.ts`:
- Around line 178-180: When the response is not OK in the MCPClient (at the if
(!response.ok) check around line 178-180), the response body is not being
drained before returning early. This can cause connection reuse issues on
repeated failures. Before returning the early exit with null sessionId and empty
serverMetadata, drain the response body by calling response.body?.cancel() or
reading the response text/blob to ensure the underlying HTTP connection is
properly cleaned up.

In `@code/core/src/cli/ai/mcp/run-tool.test.ts`:
- Around line 254-263: Move the listMcpToolsWithServerMetadata mock behavior
setup from inline within test bodies into a beforeEach block. Establish the base
mock implementation with mockResolvedValue in beforeEach using a default fixture
object, then in individual tests override only the specific fixture values that
need to be different for that particular test case rather than re-declaring the
entire mock implementation each time.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0060f74d-524c-431a-b9bd-0d6c604b0c0f

📥 Commits

Reviewing files that changed from the base of the PR and between 3de0692 and b423f76.

📒 Files selected for processing (4)
  • code/core/src/cli/ai/mcp/client.test.ts
  • code/core/src/cli/ai/mcp/client.ts
  • code/core/src/cli/ai/mcp/run-tool.test.ts
  • code/core/src/cli/ai/mcp/run-tool.ts

Comment thread code/core/src/cli/ai/mcp/client.ts
Comment thread code/core/src/cli/ai/mcp/run-tool.test.ts
@kasperpeulen kasperpeulen force-pushed the kasper/ai-cli-mcp-instructions branch from b423f76 to 6a0a51c Compare June 15, 2026 10:02

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@code/core/src/cli/ai/mcp/run-tool.ts`:
- Around line 207-210: The condition at lines 207-210 in the run-tool.ts file
returns unavailable when serverMetadata.instructions is missing or empty, which
suppresses the entire command from being listed. Instead of returning
unavailable, the code should allow the command to be included in the list but
simply omit or skip the workflow section when instructions are absent. Modify
the logic to handle missing instructions gracefully by excluding only the
workflow-related portion of the response while still providing the command
help/availability. The same pattern applies at lines 212-222, where similar
handling of missing instructions should be implemented to ensure commands remain
available even when workflow instructions are not provided.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 51f8f5ff-7286-4cf3-bd2e-0fc7237c4e2a

📥 Commits

Reviewing files that changed from the base of the PR and between b423f76 and 6a0a51c.

📒 Files selected for processing (3)
  • code/core/src/cli/ai/mcp/client.ts
  • code/core/src/cli/ai/mcp/run-tool.test.ts
  • code/core/src/cli/ai/mcp/run-tool.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • code/core/src/cli/ai/mcp/run-tool.test.ts
  • code/core/src/cli/ai/mcp/client.ts

Comment thread code/core/src/cli/ai/mcp/run-tool.ts Outdated
@kasperpeulen kasperpeulen force-pushed the kasper/ai-cli-mcp-instructions branch from 6a0a51c to f91b919 Compare June 15, 2026 10:06
@kasperpeulen kasperpeulen force-pushed the kasper/ai-cli-mcp-instructions branch from f91b919 to 1aaf82d Compare June 15, 2026 10:43
@kasperpeulen

Copy link
Copy Markdown
Member Author

storybook ai --help: workflow instructions can be dropped even when the server has them

The top-level help gates the whole section on serverMetadata.instructions — no instructions ⟹ unavailable(...) and the command list is withheld too (run-tool.ts, the if (!instructions) block). Gating commands on instructions is the right product call: we want instructions to be mandatory and don't want to show commands without them.

The problem is where the instructions come from. They ride on the best-effort initialize handshake, which is a separate request from tools/list with a much tighter budget — INITIALIZE_TIMEOUT_MS = 3s vs REQUEST_TIMEOUT_MS = 10min (client.ts). initializeMcpSession swallows every failure and returns serverMetadata: {}. So listMcpToolsWithServerMetadata can legitimately return { tools: [non-empty], serverMetadata: {} }: the command list (10-min request) succeeds while the instructions (3-s handshake) get dropped. The client even asserts this as a supported outcome (client.test.ts, "keeps tools/list working when initialize returns malformed/error/no-result"). Net effect: on a cold or slow first request, help prints "did not provide workflow instructions" even though the server has perfectly good instructions.

What can actually make instructions empty while tools are present?

  1. Server genuinely has none — impossible. Instructions and tools share the same dev/test/docs toolset gating in addon-mcp (buildServerInstructions + each tool's enabled callback), so non-empty tools/list ⟹ non-empty instructions. When all toolsets are off, tools/list is empty too and the existing tools.length === 0 guard handles it first.
  2. initialize timed out at 3 s — the only illegitimate trigger, and purely an artifact of reusing the telemetry handshake's hot-path budget.
  3. initialize returned non-ok / malformed while tools/list worked — same endpoint, same transport; no legitimate reason a healthy addon-mcp rejects one and not the other. Genuine contract bug.
  4. Transient failure on the first request only — localhost, sequential requests; effectively impossible.

Only #2 is a real non-bug trigger. So this genuinely is an invariant — but only once #2 is removed.

Proposed fix (two parts):

  • Decouple the timeout. Give the help-path initialize a generous budget instead of the 3 s one. The 3 s exists because the handshake is garnish on every command's hot path — but --help has no hot path; fetching this info is its whole job. Thread an initialize-timeout through listMcpToolsWithServerMetadata so callMcpTool keeps its 3 s. After this, if tools/list succeeded, initialize succeeds too.
  • Then assert an invariant instead of the unavailable note. With Add support for custom webpack config #2 gone, empty-instructions-with-tools can only mean a genuine addon-mcp bug (served commands but botched its own initialize), which should fail loudly via the existing handleCommandFailure path rather than silently render half a help page. The invariant's comment should state why it can't happen (shared toolset gating + the now-load-bearing initialize), so a future reader doesn't have to re-derive it.

Net: instructions stay mandatory, no spurious "unavailable" on a slow server, and a real contract violation surfaces as a loud error.

The initializeMcpSession JSDoc that calls the metadata "strictly best-effort … only telemetry segmentation and initialize metadata are lost" should also be reconciled — on the help path the metadata is load-bearing, not best-effort.

The workflow instructions shown in `storybook ai --help` ride on the MCP
`initialize` handshake, which used a tight 3s best-effort budget (telemetry
garnish). A slow or still-starting dev server could drop the instructions while
the separate `tools/list` request succeeded, making help show "no instructions"
even though the server has them.

- Give the handshake the full request budget (REQUEST_TIMEOUT_MS) on every path.
  The only thing that slows it is the dev server starting up, which the command
  must wait through anyway, so the tighter budget bought nothing.
- Assert the instructions are present (invariant) instead of degrading to an
  "unavailable" note: addon-mcp gates instructions and tools on the same
  toolsets, so with commands present their absence can only be a contract bug.
- Drop the now-impossible empty-instructions test.
`sendJsonRpcRequest` and `parseInitializeServerMetadata` both parsed the
JSON-RPC envelope and checked for parse failure / error / missing result.
Extract a single `unwrapJsonRpcResult` helper: the command path throws on the
reported error, the best-effort initialize-metadata parse falls back to empty.
@kasperpeulen

Copy link
Copy Markdown
Member Author

Update — implemented (c3049663cb, ce6819f868).

Landed a simpler fix than the timeout-threading proposed above: instead of giving the help path its own initialize budget, the handshake now shares the full request budget (REQUEST_TIMEOUT_MS) on every path. The 3s budget only ever guarded against a server that's slow on initialize but fast on tools/list — which can't happen (same server, same transport), and the only thing that actually slows the handshake is the dev server still starting up, which the command must wait through anyway. So there was nothing to protect, and unifying removed the special-casing entirely.

Missing instructions are now an invariant (a healthy addon-mcp gates instructions and tools on the same toolsets, so with commands present their absence is a contract bug) rather than a degraded "unavailable" note. Also deduped the JSON-RPC envelope unwrapping into a shared unwrapJsonRpcResult helper.

Note: this also moves command execution's handshake off the 3s budget (it came from the telemetry handshake in #35131) — called out in the PR description.

@storybook-app-bot

Copy link
Copy Markdown

Package Benchmarks

Commit: ce6819f, ran on 16 June 2026 at 10:01:28 UTC

The following packages have significant changes to their size or dependencies:

storybook

Before After Difference
Dependency count 72 72 0
Self size 21.04 MB 21.02 MB 🎉 -20 KB 🎉
Dependency size 36.12 MB 36.12 MB 0 B
Bundle Size Analyzer Link Link

@storybook/cli

Before After Difference
Dependency count 203 203 0
Self size 802 KB 802 KB 🎉 -84 B 🎉
Dependency size 89.20 MB 89.18 MB 🎉 -20 KB 🎉
Bundle Size Analyzer Link Link

@storybook/codemod

Before After Difference
Dependency count 196 196 0
Self size 32 KB 32 KB 🎉 -36 B 🎉
Dependency size 87.69 MB 87.67 MB 🎉 -20 KB 🎉
Bundle Size Analyzer Link Link

create-storybook

Before After Difference
Dependency count 73 73 0
Self size 1.08 MB 1.08 MB 0 B
Dependency size 57.16 MB 57.14 MB 🎉 -20 KB 🎉
Bundle Size Analyzer node node

@kasperpeulen kasperpeulen merged commit f6382eb into next Jun 16, 2026
144 checks passed
@kasperpeulen kasperpeulen deleted the kasper/ai-cli-mcp-instructions branch June 16, 2026 10:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:normal Run our default set of CI jobs (choose this for most PRs). feature request qa:needed Pull Requests that will need manual QA prior to release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Make storybook ai --help show MCP server instructions

2 participants