storybook-addon-playwright-mcp 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +111 -0
- package/package.json +23 -0
- package/src/cli.ts +18 -0
- package/src/content.ts +84 -0
- package/src/data/actions.ts +746 -0
- package/src/data/example.ts +80 -0
- package/src/data/guides.ts +505 -0
- package/src/index.ts +15 -0
- package/src/search.ts +148 -0
- package/src/server.ts +206 -0
- package/src/story-id.ts +85 -0
- package/src/types.ts +61 -0
- package/test/actions.test.ts +42 -0
- package/test/content.test.ts +76 -0
- package/test/schema-sync.test.ts +130 -0
- package/test/search.test.ts +40 -0
- package/test/server.test.ts +109 -0
- package/test/story-id.test.ts +49 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +12 -0
package/README.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# storybook-addon-playwright-mcp
|
|
2
|
+
|
|
3
|
+
A local **stdio MCP server** that teaches AI coding assistants how to author
|
|
4
|
+
visual/screenshot regression tests for the
|
|
5
|
+
[`storybook-addon-playwright`](../README.md) addon.
|
|
6
|
+
|
|
7
|
+
It is intentionally scoped: the server description tells the assistant to
|
|
8
|
+
consult it **only** when the user asks to _add a story screenshot / visual test_
|
|
9
|
+
or _generate Playwright screenshots_ — not on every Storybook or Playwright task.
|
|
10
|
+
|
|
11
|
+
## What it knows
|
|
12
|
+
|
|
13
|
+
- The `*.stories.playwright.json` action-file **format and naming convention**
|
|
14
|
+
(same base name, same folder as the story file).
|
|
15
|
+
- How to build **action sets** and the full **action catalog** (searchable).
|
|
16
|
+
- **Selector strategy** — prefer `data-slot` / `data-testid` / `id`.
|
|
17
|
+
- **Screenshot sizing** — prefer focused `takeElementScreenshot` captures, and
|
|
18
|
+
the `options.offset` inset for trimming unwanted edges.
|
|
19
|
+
- How screenshot **images are generated** (addon panel, tRPC endpoint, or the
|
|
20
|
+
visual test suite).
|
|
21
|
+
|
|
22
|
+
## Tools
|
|
23
|
+
|
|
24
|
+
| Tool | Purpose |
|
|
25
|
+
| -------------------------------- | ------------------------------------------------------------------- |
|
|
26
|
+
| `get_screenshot_authoring_guide` | Workflow + conventions. Start here. Optional `topic` for a section. |
|
|
27
|
+
| `list_playwright_actions` | List actions grouped by category. |
|
|
28
|
+
| `search_playwright_actions` | Rank actions by relevance for a query. |
|
|
29
|
+
| `get_playwright_action` | Full detail + example for one action. |
|
|
30
|
+
| `get_example_playwright_json` | A complete example action file. |
|
|
31
|
+
|
|
32
|
+
## How it ships
|
|
33
|
+
|
|
34
|
+
This folder is **source only**; it is not published as its own npm package.
|
|
35
|
+
The addon's `tsup` build bundles [`src/cli.ts`](src/cli.ts) into
|
|
36
|
+
`dist/mcp/cli.mjs`, which the root `storybook-addon-playwright` package exposes
|
|
37
|
+
as a second bin:
|
|
38
|
+
|
|
39
|
+
```jsonc
|
|
40
|
+
// package.json (root)
|
|
41
|
+
"bin": {
|
|
42
|
+
"storybook-addon-playwright": "./dist/cli.js", // the addon's existing CLI (untouched)
|
|
43
|
+
"storybook-addon-playwright-mcp": "./dist/mcp/cli.mjs" // this MCP server
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
So installing `storybook-addon-playwright` in a project makes the
|
|
48
|
+
`storybook-addon-playwright-mcp` bin available — no separate install, published
|
|
49
|
+
by the same release pipeline.
|
|
50
|
+
|
|
51
|
+
## Usage (in a consuming project)
|
|
52
|
+
|
|
53
|
+
Once `storybook-addon-playwright` is installed, register the server with your MCP
|
|
54
|
+
client **in that project**. `npx` resolves the bin from the project's
|
|
55
|
+
`node_modules/.bin` — there is no `storybook-addon-playwright-mcp` package on
|
|
56
|
+
npm, so it must resolve locally:
|
|
57
|
+
|
|
58
|
+
```jsonc
|
|
59
|
+
{
|
|
60
|
+
"mcpServers": {
|
|
61
|
+
"storybook-playwright-screenshots": {
|
|
62
|
+
"command": "npx",
|
|
63
|
+
"args": ["storybook-addon-playwright-mcp"],
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For Claude Code (project scope, so the server starts in the project root):
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
claude mcp add -s project storybook-playwright-screenshots -- npx storybook-addon-playwright-mcp
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Troubleshooting
|
|
76
|
+
|
|
77
|
+
`connection closed: initialize response` at startup means the bin was not
|
|
78
|
+
resolved locally and `npx` fell back to the registry, where the name 404s.
|
|
79
|
+
Causes and fixes:
|
|
80
|
+
|
|
81
|
+
- **Registered in user/global scope** (or the client starts servers outside the
|
|
82
|
+
project) → re-register in project scope, or use an absolute path:
|
|
83
|
+
`node /abs/path/to/project/node_modules/storybook-addon-playwright/dist/mcp/cli.mjs`.
|
|
84
|
+
- **Working inside this repo** (a package does not link its own bins under
|
|
85
|
+
pnpm) → point at the build output: `node <repo>/dist/mcp/cli.mjs`
|
|
86
|
+
(run `pnpm build` first).
|
|
87
|
+
- **Really need it standalone** → name the owning package:
|
|
88
|
+
`npx -y -p storybook-addon-playwright storybook-addon-playwright-mcp`. Never
|
|
89
|
+
`npx -y storybook-addon-playwright-mcp` — that resolves to a nonexistent
|
|
90
|
+
package.
|
|
91
|
+
|
|
92
|
+
## Development
|
|
93
|
+
|
|
94
|
+
This folder's dependencies (`@modelcontextprotocol/sdk`, `zod`, `vitest`, `tsx`,
|
|
95
|
+
`typescript`) are all present at the repo root, so **no separate install is
|
|
96
|
+
needed** — run the checks from the repo root:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pnpm exec tsc --noEmit -p mcp/tsconfig.json # typecheck
|
|
100
|
+
pnpm exec vitest run --config mcp/vitest.config.ts # tests (incl. schema-sync)
|
|
101
|
+
pnpm exec tsx mcp/src/cli.ts # run the server over stdio
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
CI runs the typecheck + tests via the "MCP server checks" step. The
|
|
105
|
+
[`schema-sync`](test/schema-sync.test.ts) test fails the build if
|
|
106
|
+
[`src/data/actions.ts`](src/data/actions.ts) drifts from the addon's generated
|
|
107
|
+
action schema (`src/api/server/data/action-schema.json`, produced from the
|
|
108
|
+
`PlaywrightPage` interface).
|
|
109
|
+
|
|
110
|
+
The production bin is produced by the root build (`pnpm build` at the repo
|
|
111
|
+
root → `dist/mcp/cli.mjs`).
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "storybook-addon-playwright-mcp",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Source for the MCP server shipped as the `storybook-addon-playwright-mcp` bin of the storybook-addon-playwright package. Not published on its own — the addon's tsup build bundles mcp/src/cli.ts into dist/mcp/cli.mjs.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "tsx src/cli.ts",
|
|
9
|
+
"typecheck": "tsc --noEmit",
|
|
10
|
+
"test": "vitest --run --passWithNoTests",
|
|
11
|
+
"test:watch": "vitest --watch"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
15
|
+
"zod": "^4.4.3"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"@types/node": "^25.7.0",
|
|
19
|
+
"tsx": "^4.21.0",
|
|
20
|
+
"typescript": "~5.9.3",
|
|
21
|
+
"vitest": "^4.1.6"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
import { startServer } from './server.js';
|
|
4
|
+
|
|
5
|
+
function readVersion(): string {
|
|
6
|
+
// Resolves to the storybook-addon-playwright package.json in both layouts:
|
|
7
|
+
// dev : mcp/src/cli.ts -> ../../package.json (repo root)
|
|
8
|
+
// built: dist/mcp/cli.mjs -> ../../package.json (installed package root)
|
|
9
|
+
try {
|
|
10
|
+
const pkgUrl = new URL('../../package.json', import.meta.url);
|
|
11
|
+
const pkg = JSON.parse(readFileSync(pkgUrl, 'utf8')) as { version?: string };
|
|
12
|
+
return pkg.version ?? '0.0.0';
|
|
13
|
+
} catch {
|
|
14
|
+
return '0.0.0';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
await startServer(readVersion());
|
package/src/content.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ActionRegistry, Guide, PlaywrightAction } from './types.js';
|
|
2
|
+
import { actions } from './data/actions.js';
|
|
3
|
+
import { exampleExplanation, exampleFile, exampleFileName } from './data/example.js';
|
|
4
|
+
import { guides } from './data/guides.js';
|
|
5
|
+
|
|
6
|
+
const JSON_INDENT = 2;
|
|
7
|
+
|
|
8
|
+
/** Action catalog keyed by action name. */
|
|
9
|
+
export const actionRegistry: ActionRegistry = Object.fromEntries(
|
|
10
|
+
actions.map((action) => [action.name, action]),
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
/** Every guide keyed by its topic id. */
|
|
14
|
+
const guideById: Record<string, Guide> = Object.fromEntries(
|
|
15
|
+
guides.map((guide) => [guide.id, guide]),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/** Valid `topic` values for the authoring-guide tool. */
|
|
19
|
+
export const guideTopics: readonly string[] = guides.map((guide) => guide.id);
|
|
20
|
+
|
|
21
|
+
/** Look up a single action by its exact name. */
|
|
22
|
+
export function getAction(name: string): PlaywrightAction | undefined {
|
|
23
|
+
return Object.hasOwn(actionRegistry, name) ? actionRegistry[name] : undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Group action names by category for the list tool. */
|
|
27
|
+
export function listActionsByCategory(): Record<string, string[]> {
|
|
28
|
+
const grouped: Record<string, string[]> = {};
|
|
29
|
+
|
|
30
|
+
for (const action of actions) {
|
|
31
|
+
(grouped[action.category] ??= []).push(action.name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
for (const names of Object.values(grouped)) {
|
|
35
|
+
names.sort((a, b) => a.localeCompare(b));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return grouped;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const guideIndex = guides.map((guide) => `- \`${guide.id}\` — ${guide.title}`).join('\n');
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Build the markdown response for the authoring-guide tool.
|
|
45
|
+
*
|
|
46
|
+
* - no topic → overview + workflow + an index of the remaining topics;
|
|
47
|
+
* - `all` → every guide concatenated;
|
|
48
|
+
* - a topic → that single guide;
|
|
49
|
+
* - unknown → an error listing the valid topics.
|
|
50
|
+
*/
|
|
51
|
+
export function getGuideText(topic?: string): { text: string; isError?: boolean } {
|
|
52
|
+
if (!topic) {
|
|
53
|
+
const overview = guideById.overview?.body ?? '';
|
|
54
|
+
const workflow = guideById.workflow?.body ?? '';
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
text: `${overview}\n\n---\n\n${workflow}\n\n---\n\n## More topics\n\nCall this tool again with a \`topic\` for detail:\n\n${guideIndex}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (topic === 'all') {
|
|
62
|
+
return {
|
|
63
|
+
text: guides.map((guide) => guide.body).join('\n\n---\n\n'),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const guide = guideById[topic];
|
|
68
|
+
|
|
69
|
+
if (!guide) {
|
|
70
|
+
return {
|
|
71
|
+
isError: true,
|
|
72
|
+
text: `Unknown topic "${topic}". Valid topics: ${guideTopics.join(', ')}, all.`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return { text: guide.body };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Build the markdown response for the example tool. */
|
|
80
|
+
export function getExampleText(): string {
|
|
81
|
+
const json = JSON.stringify(exampleFile, null, JSON_INDENT);
|
|
82
|
+
|
|
83
|
+
return `# Example: \`${exampleFileName}\`\n\n\`\`\`json\n${json}\n\`\`\`\n\n${exampleExplanation}`;
|
|
84
|
+
}
|