storybook-events-inspector
Provides a Storybook panel and MCP server for inspecting custom DOM events dispatched by web components in stories. Captures every event any target dispatches (elements, document, window, EventTarget subclasses), flags traps like not-composed, shared, retargeted, and undocumented events, and allows dispatching synthetic events at rendered components. The MCP server drives a headless browser directly to a story's iframe, enabling AI agents to inspect and test event behavior without the Storybook manager/channel.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@storybook-events-inspectorOpen the search-input story, capture its custom events, and flag undocumented ones."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
storybook-events-inspector
A Storybook panel for design systems whose public API is their events: a component fires one event, the host owns the workflow. That means "is this thing working?" is almost always "did it fire, and with what?" — a question the browser gives you no good way to ask, and that a story-embedded debug element only answers for stories someone remembered to add it to.
This addon answers it globally, with zero markup and zero registration, for every story:
Capture — sees every custom event any target dispatches, automatically: elements,
document,window, and bareEventTargetsubclasses (the usual shape of an event bus). Including events dispatched while the component is still rendering, fromconnectedCallbackor Lit'sfirstUpdated. No catalog required to see something; a catalog is optional annotation on top of a stream that's already complete (see How it works).Flags four traps that cost hours when hit blind: an event dispatched from inside a shadow root without
composed: true, so it never left that root and is invisible to the entire host app, not just this panel (not composed) — a name declared by more than one tag (shared) —event.targetnot matching the true dispatch origin because a composed event crossed a shadow boundary (retargeted) — and an event that fired but isn't in your catalog at all (undocumented).Dispatch — the reverse direction. Fire a synthetic event at the rendered component from the panel, to check it responds the way its docs claim, without writing a throwaway
playfunction. The dispatch itself shows up in the log too, like anything else.
Install
npm install --save-dev storybook-events-inspector// .storybook/main.ts
const config = {
addons: ['storybook-events-inspector'],
};That's it — open the Events inspector panel next to Controls/Actions/Interactions and interact with any story. No catalog, no config, nothing to register.
Related MCP server: storybook-mcp
Usage
Everything below is optional narrowing/annotation on top of capture that already works with zero configuration.
Reducing noise: filter / extra
export const Default: Story = {
parameters: {
eventsInspector: {
filter: ['item-change'], // narrow the stream to just this name
extra: ['secret-event'], // add this back even though filter is set
},
},
};filter narrows; an empty (default) filter keeps everything. extra only
does anything when filter is non-empty — it's how you say "just these,
plus this one more."
Adding meaning: catalog
// .storybook/preview.ts
import type { Preview } from '@storybook/web-components-vite';
const preview: Preview = {
parameters: {
eventsInspector: {
// Typically generated from custom-elements.json — one entry per event
// name, with every tag that documents dispatching it.
catalog: [
{ name: 'item-change', tags: ['search-input', 'sort-by'] },
{ name: 'list-load-more', tags: ['pagination'] },
],
},
},
};
export default preview;Without a catalog, every captured event is (correctly) undocumented — you
still see everything, you just don't get the shared/undocumented
distinction. Add one when you want the panel to know what's actually part of
your documented API surface.
Parameters cascade normally (project → component → story), so a story can override the project default.
Parameters (parameters.eventsInspector)
Key | Type | Default | Effect |
|
|
| Annotates captured events with |
|
|
| Narrows the capture stream to just these names. |
|
|
| Derives |
|
|
| Adds names back on top of a non-empty |
|
|
| Rows kept in the panel before older ones drop. |
|
|
| Hide the detail column. |
|
|
| Panel heading. |
How it works
Agnostic by construction, not by convention. Every custom event any
element dispatches — in Lit, Stencil, or vanilla JS — goes through exactly one
platform method: EventTarget.prototype.dispatchEvent. That's the same
reason Redux DevTools doesn't need you to register action types: it wraps
store.dispatch, the one place every action already funnels through. DOM
custom events have the same kind of chokepoint; we patch it once, and get
complete coverage with no catalog and no per-name listener registration.
Two things fall out of intercepting the call site instead of listening on
window for known names:
Native events are excluded for free. A real click is dispatched by the browser engine itself, never through a JS call to
.dispatchEvent()— so patching it naturally filters out native noise. (We also skip any dispatched type that doesn't contain a hyphen, the platform's own custom-event naming convention, to filter out synthetic native-named dispatches from things like testing libraries.)composed: falseevents become visible. Awindow-level listener, capture phase or not, never sees an event whose propagation path never leaves its shadow root. Intercepting the dispatch call itself sees it regardless — which is how thenot composedflag exists at all, and it's arguably the single highest-value one: that bug makes an event invisible to the entire host app, not just this tool. The flag is only raised when the origin is genuinely inside a shadow tree: from the light DOM, or fromdocument/window/an event bus,composedchanges nothing and flagging it would be a false alarm.
Two further consequences of owning the choke point:
Every
EventTargetcounts, not just elements. A design system routinely dispatches app-level events (theme-change,toast-show) ondocument, and an event bus is often a bareEventTargetsubclass. Those are captured too, and read in the log asdocument/window/ the bus's class name.Capture starts at module load, not at first subscribe. Events dispatched before a panel (or the MCP session) is listening are buffered and handed to the first subscriber, so a component that fires from
connectedCallbackstill shows up instead of being lost to the startup gap.
Core vs. adapter
src/core/ host-agnostic — no import from 'storybook/*' anywhere here
inspector.ts the dispatchEvent patch + subscribe/notify
dispatch.ts the reverse direction + "find the real custom element"
catalog.ts shared/undocumented + filter matching (pure functions)
describe.ts, safeDetail.ts, types.ts
browser-bundle.ts self-contained build of the above for injection into
a page with no module loader — see the MCP server below
src/preview.ts, src/manager.tsx,
src/components/ Storybook adapter: preview.ts subscribes to core and
forwards over the Storybook channel; the panel (React,
same as the built-in Actions addon) renders what arrives.
src/mcp/ MCP server adapter: session.ts drives a headless browser
straight to a story's iframe.html (bypassing the Storybook
manager/channel entirely) and injects core/browser-bundle.js
directly — see "MCP server" below.core/ doesn't know Storybook exists — the MCP server adapter is proof, not
just a promise: it reuses inspector.ts, dispatch.ts, and catalog.ts
completely unchanged, against a page that never loads the Storybook manager at
all. The same bundle could equally back a bookmarklet or a browser-extension
content script on a live, deployed page — catching real user flows, not
just what a Storybook interaction test exercises.
What this deliberately doesn't cover: framework-level reactivity (Lit
Signals, MobX observables, Vue refs, …) isn't a DOM API — there's no single
chokepoint to patch generically the way dispatchEvent is one for custom
events, and each library's internals are shaped differently. Watching one
would mean a separate, purpose-built core/-style adapter for that specific
library, added only when a real component actually needs it — not a
speculative addition now.
MCP server — for AI agents
A standalone MCP server, separate from Storybook's own @storybook/addon-mcp.
That's deliberate, not a missed integration: as of writing, Storybook's MCP
server has no extension point for a third-party addon to register its own
tools, and its "docs" toolset (component manifest lookup) doesn't cover
web-components projects yet. So this exposes this addon's own domain
directly — the same capture/dispatch loop the Storybook panel gives a human,
callable by an agent.
Setup
Requires Node 20.19+.
The server's runtime dependencies are optional peer dependencies, so installing this package for its Storybook panel alone doesn't pull down Playwright and a browser binary the panel never uses. Install them when you want the MCP server:
npm install --save-dev @modelcontextprotocol/sdk playwright zod
npx playwright install chromium(Run the server without them and it prints exactly that, rather than failing with a module-resolution stack trace.)
Then check it starts before wiring any client to it — it should print the missing-dependency message or simply wait on stdio, not crash:
npx storybook-events-inspector-mcp --storybook-url http://localhost:6006// .mcp.json (project-level MCP config, e.g. for Claude Code)
{
"mcpServers": {
"storybook-events-inspector": {
"command": "npx",
"args": [
"storybook-events-inspector-mcp",
"--storybook-url",
"http://localhost:6006",
"--catalog",
"./custom-elements-catalog.json", // optional
],
},
},
}npx here does not download anything: the package is already a local
devDependency, and npx resolves node_modules/.bin first. It's needed
because that directory isn't on PATH, so an MCP client spawning the bare
command name would fail with ENOENT.
Flags:
--storybook-url(defaulthttp://localhost:6006) — set this if your Storybook runs anywhere else; the server won't discover it.--catalog <path>(optional) — a JSON file of the same{ name, tags }[]shape as the addon's ownparameters.eventsInspector.catalog, used for the sameshared/undocumentedannotation. Relative paths resolve from the working directory the MCP client launches the server in, which is normally your project root; use an absolute path if your client differs. Omit it and everything is (correctly)undocumented— capture itself is unaffected either way. See Generating the catalog below.
Storybook has to already be running (pnpm storybook or equivalent) — the
server drives a real, separate headless browser against it, it doesn't start
Storybook itself.
Generating the catalog
shared and undocumented only mean anything with a catalog, and the
{ name, tags }[] shape is a direct projection of a Custom Elements
Manifest — so it's generated, never hand-written:
npx storybook-events-inspector-setup catalogIt finds your manifest via package.json's customElements field (the CEM
spec's own pointer), falling back to ./custom-elements.json,
./dist/custom-elements.json and ./custom-elements-manifest.json. Pass
--manifest <path> to be explicit, --out <path> to change the destination
(default events-catalog.json).
The manifest is indexed by element ("what does this tag fire?"); the catalog
inverts it to be indexed by event ("which tags fire this name?"). That
inversion is what makes shared computable — a name reachable from more than
one tag is exactly one a listener on a common ancestor can't attribute:
[
{ "name": "item-change", "tags": ["my-button", "my-toggle"] },
{ "name": "my-click", "tags": ["my-button"] }
]The output is sorted, so regenerating an unchanged manifest is a no-op in your
diff. The same file works in both places — pass it to the MCP server with
--catalog, or import it in .storybook/preview.ts as
parameters.eventsInspector.catalog.
Setup CLI
The same command scaffolds the rest of the wiring:
npx storybook-events-inspector-setup # all three steps
npx storybook-events-inspector-setup catalog # just the catalog
npx storybook-events-inspector-setup mcp # register the server in .mcp.json
npx storybook-events-inspector-setup skill # write a .claude/skills skillmcpmerges into an existing.mcp.jsonrather than replacing it, leaves any other servers alone, and won't re-register itself without--force. Takes--storybook-urland--config.skillwrites.claude/skills/storybook-events/SKILL.md— the diagnostic loop, and what each flag implies as a fix (which of the four to believe first, and why an empty log after a click often isn't a bug). Skills aren't discoverable fromnode_modules, which is why it's written into your repo rather than shipped inside the package. Takes--dir.
Nothing is overwritten without --force, and every path it touches is printed.
Run --help for the full list.
Tools
Tool | Does |
| Every story in the running Storybook, so an agent can find an id without guessing. |
| Load a story by id and start capturing. Validates the id against the real index first — a typo'd id fails clearly instead of silently loading Storybook's own "not found" page. |
| Click a real element by CSS selector and let whatever it fires get captured naturally — "open the menu, see it respond," run by an agent instead of a human. |
| The reverse direction: fire a synthetic event at the story's rendered element without a UI trigger to click. Also captured, like anything else. |
| Everything captured so far — name, the target that dispatched (a tag name, or |
| Empty the buffer without reloading the story. |
Each tool's own description (what an agent actually reads to decide when to
use it) has more detail and an example than this table — see src/mcp/server.ts.
The server also sets top-level instructions summarizing the whole loop.
Example loop
list_stories → find "my-design-system--menu"
open_story { storyId: "my-design-system--menu" }
click { selector: "my-menu-trigger" }
get_events {}
→ [{ name: "menu-open", origin: "my-menu", detail: {...}, undocumented: false, ... }]Or the reverse direction — checking a component responds to a command event without a UI trigger for it:
dispatch_event { name: "menu-close", detail: { reason: "escape" } }
get_events {}
→ the dispatch itself, captured like anything else, plus whatever the
component did in responseLocal development / testing it yourself
pnpm build
node dist/server.js --storybook-url http://localhost:6006It speaks MCP over stdio — point an MCP client at that command, or use the
SDK's own Client/StdioClientTransport to script it directly, the same way
you'd script any other MCP server.
Demo / local development
src/demo/ is a self-contained pair of Lit fixtures (not part of the
published addon) used by this repo's own Storybook to exercise every flag and
the dispatch round-trip:
pnpm install
pnpm build # compile src/manager.tsx, src/preview.ts → dist/
pnpm test # unit tests for src/core/ (vitest + jsdom)
pnpm storybook # http://localhost:6006 — see "Demo" in the sidebarDemo/Buttons— zeroeventsInspectorparameters. Both fixtures fire the shareddemo-changename (flaggedshared); shift-click the button to also firedemo-secret, which isn't in the catalog and still shows up, flaggedundocumented.Demo/Narrowed— same fixtures,filter: ['demo-change']set, sodemo-secretfrom a shift-click is captured but not shown.Demo/ScopedToCatalog—catalogOnly: true, deriving the same narrowing from the catalog's own names instead of a hand-maintainedfilterarray.Demo/ToggleOnly— a single element alone in the canvas, for trying the panel's Dispatch form against it directly.
Status
Young — the idea and the first release both landed the same week. src/core/
is covered by unit tests (pnpm test); the Storybook panel and the MCP server
are verified by hand against a real Storybook.
Known gaps, in the order they're worth closing:
safeDetailflattensError,MapandSetto{}, losing an error's message — the most useful thing a failing payload carries. (Placeholder tests are already insrc/core/safeDetail.test.ts.)peerDependenciesonstorybookis*while the code importsstorybook/internal/*; it should be pinned to the majors actually supported.Dev dependencies float on the
nexttag, so a fresh clone doesn't reproduce the committed lockfile.The MCP server and the Storybook panel ship as one package. They're siblings over the same
core/, not parent and child, and splitting them would only buy back a zero-installnpxinvocation — worth revisiting if the server ever wants its own release cadence.
Contributing
Issues and PRs welcome — see CONTRIBUTING.md for dev setup, how changes here actually get verified, and what's in/out of scope.
License
MIT © Drew Garman
Available Tools
6 toolsclear_eventsClear captured eventsA
Empty the captured-events buffer for the open story without reloading it.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full behavioral burden. It usefully discloses that the buffer is emptied and that the story is not reloaded, but does not state that the cleared contents are unrecoverable, whether subsequent get_events calls return empty, or what the call returns.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One compact sentence with the action front-loaded and the non-obvious side effect ('without reloading it') appended. No filler, no restatement of the name.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter, no-output-schema mutation the description covers the essential effect and its scope on the open story. Remaining gaps are the return value and confirmation of irreversibility, which would matter more for a destructive buffer wipe but are minor at this complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate; baseline 4 applies. The description correctly implies the operation is targetless and acts on the currently open story.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Empty') and resource ('captured-events buffer') scoped to 'the open story', and the qualifier 'without reloading it' separates it from reload/open_story semantics. An agent can distinguish it from get_events and dispatch_event on name plus description alone.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the phrase 'for the open story', which signals the prerequisite that a story must already be open, but there is no explicit when-to-use guidance or reference to alternatives like get_events for inspecting the buffer before clearing it. Adequate but leaves routing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
clickClick an elementA
Click a real element in the open story via a CSS selector (e.g. a menu trigger) and let the resulting custom event(s) get captured naturally — the same "open the menu, see it respond" loop a human runs by hand. Check what fired with get_events afterward.
| Name | Required | Description | Default |
|---|---|---|---|
| selector | Yes | A CSS selector, e.g. 'demo-toggle' or '.menu-trigger'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the burden and does useful work: it discloses the precondition (a story must be open) and the key mechanic that resulting custom events are captured naturally rather than synthesized. It omits failure behavior (e.g. non-matching selector) and any return/wait semantics, so it is not fully complete but is well above baseline.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The action and its selector mechanism are front-loaded in the first clause, and the get_events follow-up is a useful trailing pointer. The 'same loop a human runs by hand' illustration is somewhat wordy but does earn its place by conveying intent.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a low-complexity, single-parameter tool with no output schema or annotations, the description covers the precondition (open story), the mechanism (real event capture) and the follow-up (get_events). Only error/edge behavior is left undocumented, which is minor at this complexity.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the single parameter already carries its own examples ('demo-toggle', '.menu-trigger'), so the description only echoes 'CSS selector (e.g. a menu trigger)'. Baseline 3 is appropriate when the schema does the documenting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
It states a specific verb (click) and resource (a real element in the open story via a CSS selector), so the action is unambiguous. It implicitly contrasts with dispatch_event through 'real element ... captured naturally', but never names that sibling, so differentiation is inferred rather than explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear context for use: replicate the human 'open the menu, see it respond' loop and verify with get_events afterward. It stops short of stating when to prefer dispatch_event or any exclusion, so it lands at clear-context-without-alternatives rather than a full routing rule.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
dispatch_eventDispatch a synthetic eventA
The reverse direction: fire a synthetic event at the story's rendered custom element (found automatically, not necessarily the canvas's first child), to check it responds the way its docs claim — without a real UI trigger to click. The dispatch itself is also captured, same as anything else, so it shows up in the next get_events call too.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Event name, e.g. 'item-command'. | |
| detail | No | JSON detail payload for the event, if any. | |
| bubbles | No | ||
| composed | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does disclose real behavior: the target element is found automatically (and may not be the canvas's first child), and the dispatched event is itself captured so it appears in the next get_events call. It omits what happens when no listener exists, whether errors are raised, and what the call returns.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The key action is front-loaded, but the first sentence is a dense clause stack (parenthetical target-finding caveat, em-dash justification) that is harder to parse than necessary; the trailing sentence about capture is useful but could be tighter.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a four-parameter mutation-like tool with no annotations and no output schema, the description covers the interesting element-resolution and event-capture behaviors but leaves parameter meanings, return values, and failure modes unaddressed, so an agent must infer several things before calling it.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is only 50%, and the description adds no meaning for any of the four parameters (name, detail, bubbles, composed). The 'bubbles' and 'composed' parameters have no schema descriptions at all, so they are effectively undocumented in both places.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (fire/dispatch) and resource (synthetic event at the story's rendered custom element), and explicitly frames it as 'the reverse direction' relative to the event-capturing siblings. An agent can distinguish it from click and get_events without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for use: verifying that the element responds as its docs claim when there is no real UI trigger to click, which implicitly contrasts it with click. It does not state explicit exclusions or prerequisites, so it stops short of naming when-not to use it.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_eventsGet captured eventsA
Everything captured in the open story so far, newest last: name, the target that actually dispatched it, its detail, and flags (undocumented/shared/retargeted/notComposed). Pass sinceSeq (from a previous response, or from open_story) to get only what arrived after it; seq numbers increase for the life of the server, so a cursor stays valid across open_story calls.
| Name | Required | Description | Default |
|---|---|---|---|
| sinceSeq | No | Only return events with seq greater than this. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden. It discloses return contents, ordering, and cursor longevity/validity, but does not state whether the operation is read-only, what permissions are needed, or what happens if no story is open.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, front-loaded with what is returned, followed by cursor semantics. Every clause earns its place without redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter getter with no output schema and no annotations, the description is largely complete: it lists returned fields and explains cursor behavior. Minor gaps remain around the safety profile and the no-open-story case.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds useful context beyond the schema by explaining that sinceSeq can come from a previous response or open_story and that seq numbers are stable for the life of the server, making cursors durable.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific purpose: retrieving everything captured in the open story, with the returned fields (name, target, detail, flags) and ordering (newest last). It is clear but does not explicitly differentiate itself from siblings like open_story or list_stories.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explains how to use sinceSeq for incremental retrieval and notes the cursor's validity across open_story calls, implying a polling use case. However, it gives no explicit when-to-use versus alternatives, nor any exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_storiesList storiesA
List every story in the running Storybook, so you can find a story id for open_story without guessing.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries the full burden. It does imply a complete, unfiltered read ('every story') and that ids are returned, but it says nothing about result shape, ordering, or size limits for a potentially large catalog. Adequate but thin for an unannotated tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence stating purpose followed by the reason to call it. Every clause earns its place with no restatement of the tool name or title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema and no annotations, the description is the only source of return-value information, and it only gestures at it ('find a story id'). An agent does not learn whether the response is a bare id list or full story metadata, nor whether it could be large. Enough to call correctly, not enough to consume the result confidently.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so there is nothing for the description to disambiguate; the baseline for a parameterless tool applies. The description correctly does not invent or imply any inputs.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description gives a specific verb and resource ('List every story in the running Storybook') and explicitly names the sibling it feeds into ('open_story'). An agent can distinguish this from click, dispatch_event, or get_events without inspecting any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states the use case clearly ('so you can find a story id for open_story without guessing'), which is exactly the routing condition an agent needs against the sibling set. It stops short of explicit when-not guidance or naming alternative discovery paths, so it falls just below a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
open_storyOpen a storyA
Load a story by id in a real (headless) browser and start capturing every custom event it dispatches, including any dispatched while the story itself is rendering (e.g. from connectedCallback). Clears any previously captured events, and reports the sinceSeq cursor to use from here on. Call this before click, dispatch_event, or get_events.
| Name | Required | Description | Default |
|---|---|---|---|
| storyId | Yes | A story id from list_stories, e.g. 'demo--buttons'. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does well: it discloses the headless-browser execution, capture of events dispatched during rendering (e.g. connectedCallback), and the destructive act of clearing previously captured events. It does not cover failure modes (invalid storyId, load timeout) or whether it waits for render completion, so it falls short of a full 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the verb and resource, and every clause carries information (execution mode, capture timing, clearing, cursor), but the first sentence is dense with nested parentheticals that could be split for faster scanning.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description usefully reports the sinceSeq cursor returned and states the ordering contract, which is what an agent needs to call this correctly. It stops short of describing error behavior or the lifecycle of the capture session, but the essentials are covered.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one parameter and schema coverage is 100%, with the schema already documenting the storyId format and pointer to list_stories. The description's 'by id' adds no syntax or constraint beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Starts with a specific verb+resource ('Load a story by id') and immediately qualifies the execution context (real headless browser) and the side effect of starting event capture. This differentiates it cleanly from siblings like get_events or list_stories, which operate on already-loaded data.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states the prerequisite ordering: 'Call this before click, dispatch_event, or get_events.' An agent knows exactly where this tool belongs in a workflow relative to its siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
6 tool updates
v0.3.1- First observed
clear_events - First observed
click - First observed
dispatch_event - First observed
get_events - First observed
list_stories - First observed
open_story
TDQS
Scored across 6 tools
Each tool has a clearly distinct role in the event-inspection workflow: list_stories discovers stories, open_story starts a capture session, click and dispatch_event trigger events from different directions, get_events retrieves captured data, and clear_events resets the buffer. The descriptions explicitly clarify boundaries, such as open_story clearing previous events vs. clear_events emptying the buffer without reloading.
Most tools follow a verb_noun pattern (open_story, list_stories, dispatch_event, get_events, clear_events). The lone exception is click, which is a bare verb, but it remains intuitive and does not disrupt overall consistency.
Six tools is well-scoped for a focused event inspector: discovery, session setup, two interaction modes, retrieval, and reset. Each tool earns its place without redundancy or unnecessary surface area.
The set covers the full inspection lifecycle: discover stories, open one to capture events, trigger events via real clicks or synthetic dispatch, retrieve captured events with cursor support, and clear the buffer. No essential operation is missing, and agents can work around any minor polling needs by calling get_events repeatedly.
Maintenance
Related MCP Connectors
Browser-based QA for AI-built software. Test pages with real browsers via agents.
Hosted browser for AI agents: screenshots, post-JS DOM, console, WCAG. No install, no API key.
Live React design-system APIs, patterns, and code validation so AI agents build real UI, not slop.
Synthetic checks, nightly regression replay and model-drift alerts for AI agents
Related MCP Servers
AlicenseNot gradedqualityFmaintenanceEnables AI agents to interact with Storybook by exposing UI component information and development workflows through the Model Context Protocol.270MIT- AlicenseAqualityDmaintenanceIntegrates with a running Storybook instance to let AI-powered coding tools browse, inspect, and scaffold components from natural language.4MIT
- AlicenseAqualityAmaintenanceEnables AI agents to render and screenshot isolated UI components instantly across multiple browsers without a dev server or Storybook.22660 npm1MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to autonomously debug UIs by delegating high-level stories to a small agent that drives browsers or desktop apps and reports structured pass/fail findings with evidence.359 npm2MIT