miromiro-mcp 0.2.0 → 0.3.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 +76 -17
- package/package.json +10 -2
- package/src/index.mjs +49 -14
package/README.md
CHANGED
|
@@ -1,11 +1,44 @@
|
|
|
1
1
|
# miromiro-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/miromiro-mcp)
|
|
4
|
+
[](./LICENSE)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
MCP server for the [MiroMiro](https://miromiro.app/api) design-extraction API. Give your AI agent the real design of any live website - brand colors, fonts, design tokens, SVGs, images, Lottie animations, and paste-ready component code - so it builds from actual values instead of guessing from a screenshot.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
Works with Claude (web, desktop, Claude Code), ChatGPT, Cursor, Windsurf, Codex, and any MCP client.
|
|
9
|
+
|
|
10
|
+
## Claude Code plugin
|
|
11
|
+
|
|
12
|
+
Install as a plugin to get the tools plus three skills that teach Claude when to reach for
|
|
13
|
+
each one (`clone-section`, `match-brand`, `extract-assets`):
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
claude plugin marketplace add anthropics/claude-plugins-community
|
|
17
|
+
claude plugin install miromiro@claude-community
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The plugin uses the remote server, so there is nothing to install and no key to paste - on
|
|
21
|
+
first use it opens a one-click MiroMiro sign-in that links a free account (100 credits/month,
|
|
22
|
+
no card).
|
|
23
|
+
|
|
24
|
+
## Quick start: remote server (nothing to install)
|
|
25
|
+
|
|
26
|
+
A hosted server lives at:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
https://miromiro.app/mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Paste that URL into any client that accepts a remote MCP server. On first use it opens a one-click MiroMiro sign-in that links a free account (100 credits/month, no card).
|
|
33
|
+
|
|
34
|
+
- **Claude (web or desktop):** Settings → Connectors → Add custom connector → paste the URL. Or use the [one-click install](https://miromiro.app/api/mcp).
|
|
35
|
+
- **ChatGPT:** Settings → Security and login → enable Developer mode, then add the URL as a connector (authentication: OAuth).
|
|
36
|
+
- **Cursor (remote):** add `{ "mcpServers": { "miromiro": { "url": "https://miromiro.app/mcp" } } }` to `~/.cursor/mcp.json`.
|
|
37
|
+
- **Raw API key instead of OAuth:** append `?access_key=mm_live_...` to the URL, or send it as an `Authorization: Bearer` header.
|
|
38
|
+
|
|
39
|
+
## Local server (npm)
|
|
40
|
+
|
|
41
|
+
Prefer a local stdio process with the key in an env var? Get a free key at [miromiro.app/api/dashboard](https://miromiro.app/api/dashboard), then:
|
|
9
42
|
|
|
10
43
|
**Cursor** (`~/.cursor/mcp.json`) / **Windsurf** (Settings → MCP):
|
|
11
44
|
|
|
@@ -33,24 +66,50 @@ claude mcp add miromiro -s user \
|
|
|
33
66
|
|
|
34
67
|
| Tool | What it returns | Credits |
|
|
35
68
|
|---|---|---|
|
|
36
|
-
| `extract_code` | A section of a live page as a paste-ready component (Tailwind, HTML+CSS, JSX or Vue)
|
|
37
|
-
| `extract_brand` | Named color roles (primary/accent/background/text), palette, fonts, logo | 15 |
|
|
38
|
-
| `extract_design_tokens` | Full token system; `format: css` or `tailwind` for paste-ready output | 10 |
|
|
39
|
-
| `extract_fonts` |
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `
|
|
69
|
+
| `extract_code` | A section of a live page as a paste-ready component (Tailwind, HTML+CSS, JSX or Vue) plus the tokens it uses - start here to rebuild real UI | 25 |
|
|
70
|
+
| `extract_brand` | Named color roles (primary/accent/background/text), palette, fonts split by role, logo, social profiles, and a 0-1 confidence score | 15 |
|
|
71
|
+
| `extract_design_tokens` | Full token system with usage counts; `format: css` or `tailwind` for paste-ready output | 10 |
|
|
72
|
+
| `extract_fonts` | Every font file and family the site loads, with weights, formats, and file URLs | 5 |
|
|
73
|
+
| `extract_videos` | Video files, `og:video`, and player embeds (YouTube, Vimeo, Wistia, Loom), tagged by source | 1 |
|
|
74
|
+
| `extract_audio` | Audio files, `og:audio`, and podcast enclosures, tagged by source | 1 |
|
|
75
|
+
| `extract_lottie` | Lottie animation JSON files declared in the page | 3 |
|
|
76
|
+
| `extract_svgs` | Inline SVG code (sanitized, paste-ready) plus external SVG file URLs | 1 |
|
|
77
|
+
| `extract_assets` | Every image URL on a page, tagged by source, with alt text | 1 |
|
|
78
|
+
|
|
79
|
+
All tools are read-only and retry-safe. Identical calls within 24 hours are served from cache for free, and failed calls are never billed.
|
|
80
|
+
|
|
81
|
+
### Controlling output size (remote server)
|
|
82
|
+
|
|
83
|
+
Remote results are trimmed by default so a big page cannot flood the chat context, and every trimmed response says exactly what was dropped. Two optional arguments loosen it per call:
|
|
84
|
+
|
|
85
|
+
- `limit` (up to 200) - raise the item cap: "give me 80 SVGs, not 20"
|
|
86
|
+
- `full_output: true` - complete long strings, like entire inline SVG markup
|
|
43
87
|
|
|
44
|
-
|
|
88
|
+
The REST API and this local npm server always return everything untrimmed, so `limit` and
|
|
89
|
+
`full_output` apply to the remote server only. Every tool on both servers accepts
|
|
90
|
+
`render: true`.
|
|
91
|
+
|
|
92
|
+
## Examples
|
|
45
93
|
|
|
46
94
|
> "Rebuild stripe.com's pricing section in React"
|
|
47
95
|
|
|
48
|
-
The agent calls `extract_code` with the URL
|
|
96
|
+
The agent calls `extract_code` with the URL, gets the section's real markup, colors, fonts, and spacing, and adapts actual values instead of guessing.
|
|
97
|
+
|
|
98
|
+
> "Build a dashboard in the style of linear.app"
|
|
99
|
+
|
|
100
|
+
The agent calls `extract_brand` and `extract_design_tokens`, gets Linear's real colors, fonts, and spacing scale, and builds to match.
|
|
101
|
+
|
|
102
|
+
> "What fonts does apple.com use?"
|
|
103
|
+
|
|
104
|
+
The agent calls `extract_fonts` and returns every family with its font file URLs and weights.
|
|
105
|
+
|
|
106
|
+
## Notes
|
|
49
107
|
|
|
50
|
-
|
|
108
|
+
- The API reads static HTML and resolves the full CSS cascade by default, so a fully client-rendered app may return little for images, video, audio and Lottie (tokens, fonts, brand, and code come from CSS and are unaffected). Pass `render: true` on any remote-server tool to run the page in a real browser first so its JavaScript executes (+5 credits).
|
|
109
|
+
- Respect the sites you extract from: check their terms before reusing assets commercially.
|
|
51
110
|
|
|
52
|
-
|
|
111
|
+
## Links
|
|
53
112
|
|
|
54
|
-
|
|
113
|
+
[API docs](https://miromiro.app/api/docs) · [Playground (no account)](https://miromiro.app/api/demo) · [Pricing](https://miromiro.app/api/pricing) · [Changelog](https://miromiro.app/api/changelog) · [Connect page](https://miromiro.app/api/mcp)
|
|
55
114
|
|
|
56
|
-
|
|
115
|
+
MIT © [MiroMiro](https://miromiro.app)
|
package/package.json
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "miromiro-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "MCP server for the MiroMiro design-extraction API — give your coding agent the real code, design tokens, assets, SVGs, fonts, and Lottie of any website.",
|
|
5
|
+
"mcpName": "app.miromiro/miromiro",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"author": "MiroMiro (https://miromiro.app)",
|
|
7
8
|
"homepage": "https://miromiro.app/api",
|
|
8
|
-
"
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/SoraiaBarroso/miromiro-mcp.git"
|
|
12
|
+
},
|
|
13
|
+
"bugs": { "url": "https://github.com/SoraiaBarroso/miromiro-mcp/issues" },
|
|
9
14
|
"bin": {
|
|
10
15
|
"miromiro-mcp": "src/index.mjs"
|
|
11
16
|
},
|
|
@@ -15,8 +20,11 @@
|
|
|
15
20
|
"keywords": [
|
|
16
21
|
"mcp",
|
|
17
22
|
"model-context-protocol",
|
|
23
|
+
"mcp-server",
|
|
18
24
|
"design-tokens",
|
|
19
25
|
"design-to-code",
|
|
26
|
+
"brand-colors",
|
|
27
|
+
"web-scraping",
|
|
20
28
|
"miromiro",
|
|
21
29
|
"cursor",
|
|
22
30
|
"claude-code",
|
package/src/index.mjs
CHANGED
|
@@ -34,20 +34,29 @@ const TOOL_ANNOTATIONS = {
|
|
|
34
34
|
openWorldHint: true
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// Shared input arg: every tool accepts render=true to execute the page's
|
|
38
|
+
// JavaScript in a real (rented) browser before extraction - the fix for
|
|
39
|
+
// client-rendered React/Next/SPA sites that return little statically.
|
|
40
|
+
const RENDER_ARG = {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
description: 'Run the page in a real browser first so its JavaScript executes - use for client-rendered (React/Next/SPA) sites, or when a normal call came back thin. Slower, and adds 5 credits to the call.'
|
|
43
|
+
}
|
|
44
|
+
|
|
37
45
|
const TOOLS = [
|
|
38
46
|
{
|
|
39
47
|
name: 'extract_code',
|
|
40
48
|
title: 'Extract section as code',
|
|
41
49
|
endpoint: 'code',
|
|
42
50
|
description:
|
|
43
|
-
"Turn a section of a LIVE website into clean, paste-ready component code. Give it a URL and (optionally) a CSS selector; it resolves the page's real CSS cascade and returns a self-contained component in Tailwind, plain HTML+CSS, React (JSX) or Vue — plus the design tokens that section actually uses. The response also includes `sections`: every extractable section on the page with a human-readable label and a ready selector — when the user wants a specific part (pricing, footer, testimonials), find it there and call again with that selector. USE THIS whenever the user asks to rebuild, clone, port, or copy a section of a real website: it returns the actual code with true colors, fonts, spacing and copy, so you adapt real values instead of guessing from a screenshot. Note: it reads static HTML/CSS
|
|
51
|
+
"Turn a section of a LIVE website into clean, paste-ready component code. Give it a URL and (optionally) a CSS selector; it resolves the page's real CSS cascade and returns a self-contained component in Tailwind, plain HTML+CSS, React (JSX) or Vue — plus the design tokens that section actually uses. The response also includes `sections`: every extractable section on the page with a human-readable label and a ready selector — when the user wants a specific part (pricing, footer, testimonials), find it there and call again with that selector. USE THIS whenever the user asks to rebuild, clone, port, or copy a section of a real website: it returns the actual code with true colors, fonts, spacing and copy, so you adapt real values instead of guessing from a screenshot. Note: it reads static HTML/CSS by default - for a fully client-rendered app that returns little, call again with render=true to execute the page's JavaScript in a real browser first (+5 credits).",
|
|
44
52
|
inputSchema: {
|
|
45
53
|
type: 'object',
|
|
46
54
|
properties: {
|
|
47
55
|
url: { type: 'string', description: 'The website URL, e.g. stripe.com' },
|
|
48
56
|
selector: { type: 'string', description: 'CSS selector for the section to extract, e.g. "#hero" or ".pricing-card". Omit to auto-detect the hero.' },
|
|
49
57
|
framework: { type: 'string', enum: ['tailwind', 'html', 'jsx', 'vue'], description: 'The code to generate (default tailwind).' },
|
|
50
|
-
name: { type: 'string', description: 'Component name for the jsx/vue targets (default ExtractedSection).' }
|
|
58
|
+
name: { type: 'string', description: 'Component name for the jsx/vue targets (default ExtractedSection).' },
|
|
59
|
+
render: RENDER_ARG
|
|
51
60
|
},
|
|
52
61
|
required: ['url']
|
|
53
62
|
}
|
|
@@ -57,10 +66,10 @@ const TOOLS = [
|
|
|
57
66
|
title: 'Extract brand identity',
|
|
58
67
|
endpoint: 'brand',
|
|
59
68
|
description:
|
|
60
|
-
"Get a website's curated brand identity:
|
|
69
|
+
"Get a website's curated brand identity: color roles (primary, accent, background, text) read from the site's own CSS variables and logo — each as hex plus a human-readable name like 'Royal Blue' — dark/light theme detection, the brand palette, fonts split by role (primary/mono), logo, social image, the brand's social profiles (X, GitHub, LinkedIn, …), and a 0-1 confidence score for how much the site declared vs how much was inferred. Use this when you only need a site's STYLE (colors/fonts) rather than its code — to build new UI 'in the style of' a reference site. If you need the actual markup of a section, use extract_code instead.",
|
|
61
70
|
inputSchema: {
|
|
62
71
|
type: 'object',
|
|
63
|
-
properties: { url: { type: 'string', description: 'The website URL, e.g. stripe.com' } },
|
|
72
|
+
properties: { url: { type: 'string', description: 'The website URL, e.g. stripe.com' }, render: RENDER_ARG },
|
|
64
73
|
required: ['url']
|
|
65
74
|
}
|
|
66
75
|
},
|
|
@@ -74,7 +83,8 @@ const TOOLS = [
|
|
|
74
83
|
type: 'object',
|
|
75
84
|
properties: {
|
|
76
85
|
url: { type: 'string', description: 'The website URL' },
|
|
77
|
-
format: { type: 'string', enum: ['json', 'css', 'tailwind'], description: 'Output shape (default json)' }
|
|
86
|
+
format: { type: 'string', enum: ['json', 'css', 'tailwind'], description: 'Output shape (default json)' },
|
|
87
|
+
render: RENDER_ARG
|
|
78
88
|
},
|
|
79
89
|
required: ['url']
|
|
80
90
|
}
|
|
@@ -84,10 +94,10 @@ const TOOLS = [
|
|
|
84
94
|
title: 'Extract images',
|
|
85
95
|
endpoint: 'images',
|
|
86
96
|
description:
|
|
87
|
-
"List every image on a page (img, srcset, picture, CSS background, favicon, og:image) as resolved URLs tagged by source. Use to recover imagery from a site or pull reference assets.",
|
|
97
|
+
"List every image on a page (img, srcset, picture, CSS background, favicon, og:image) as resolved URLs tagged by source. Use to recover imagery from a site or pull reference assets. Best-effort from static HTML: if images are mounted by JavaScript, call again with render=true (+5 credits).",
|
|
88
98
|
inputSchema: {
|
|
89
99
|
type: 'object',
|
|
90
|
-
properties: { url: { type: 'string', description: 'The website URL' } },
|
|
100
|
+
properties: { url: { type: 'string', description: 'The website URL' }, render: RENDER_ARG },
|
|
91
101
|
required: ['url']
|
|
92
102
|
}
|
|
93
103
|
},
|
|
@@ -96,10 +106,10 @@ const TOOLS = [
|
|
|
96
106
|
title: 'Extract SVGs',
|
|
97
107
|
endpoint: 'svg',
|
|
98
108
|
description:
|
|
99
|
-
"Extract inline SVGs (as paste-ready code) and SVG file references from a page. Use to grab icons and vector logos.",
|
|
109
|
+
"Extract inline SVGs (as paste-ready code) and SVG file references from a page. Use to grab icons and vector logos. Best-effort from static HTML: if icons are mounted by JavaScript, call again with render=true (+5 credits).",
|
|
100
110
|
inputSchema: {
|
|
101
111
|
type: 'object',
|
|
102
|
-
properties: { url: { type: 'string', description: 'The website URL' } },
|
|
112
|
+
properties: { url: { type: 'string', description: 'The website URL' }, render: RENDER_ARG },
|
|
103
113
|
required: ['url']
|
|
104
114
|
}
|
|
105
115
|
},
|
|
@@ -113,7 +123,8 @@ const TOOLS = [
|
|
|
113
123
|
type: 'object',
|
|
114
124
|
properties: {
|
|
115
125
|
url: { type: 'string', description: 'The website URL' },
|
|
116
|
-
fields: { type: 'string', description: "Optional comma-separated fields to return (e.g. 'families')" }
|
|
126
|
+
fields: { type: 'string', description: "Optional comma-separated fields to return (e.g. 'families')" },
|
|
127
|
+
render: RENDER_ARG
|
|
117
128
|
},
|
|
118
129
|
required: ['url']
|
|
119
130
|
}
|
|
@@ -123,10 +134,34 @@ const TOOLS = [
|
|
|
123
134
|
title: 'Extract Lottie animations',
|
|
124
135
|
endpoint: 'lottie',
|
|
125
136
|
description:
|
|
126
|
-
"Find Lottie/animation JSON files declared on a page. Best-effort from static HTML
|
|
137
|
+
"Find Lottie/animation JSON files declared on a page. Best-effort from static HTML - most Lotties are runtime-injected, so pass render=true to execute the page JS first and catch those too (+5 credits). Use to recover animations.",
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: { url: { type: 'string', description: 'The website URL' }, render: RENDER_ARG },
|
|
141
|
+
required: ['url']
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: 'extract_videos',
|
|
146
|
+
title: 'Extract videos',
|
|
147
|
+
endpoint: 'videos',
|
|
148
|
+
description:
|
|
149
|
+
'List every video a page declares: <video>/<source> files, og:video, player embeds (YouTube, Vimeo, Wistia, Loom) and direct video-file links - each tagged by source, with a poster image when declared. Note `source: "embed"` is a player page, not a downloadable file. Best-effort from static HTML: most sites mount their player with JavaScript, so if the result is thin or empty, call again with render=true to execute the page JS first (+5 credits).',
|
|
150
|
+
inputSchema: {
|
|
151
|
+
type: 'object',
|
|
152
|
+
properties: { url: { type: 'string', description: 'The website URL' }, render: RENDER_ARG },
|
|
153
|
+
required: ['url']
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'extract_audio',
|
|
158
|
+
title: 'Extract audio',
|
|
159
|
+
endpoint: 'audio',
|
|
160
|
+
description:
|
|
161
|
+
'List every audio file a page declares: <audio>/<source> files, og:audio, and direct audio-file links such as podcast enclosures and download buttons - each tagged by source. Best-effort from static HTML: if a JavaScript player mounts the audio at runtime, call again with render=true (+5 credits).',
|
|
127
162
|
inputSchema: {
|
|
128
163
|
type: 'object',
|
|
129
|
-
properties: { url: { type: 'string', description: 'The website URL' } },
|
|
164
|
+
properties: { url: { type: 'string', description: 'The website URL' }, render: RENDER_ARG },
|
|
130
165
|
required: ['url']
|
|
131
166
|
}
|
|
132
167
|
}
|
|
@@ -134,7 +169,7 @@ const TOOLS = [
|
|
|
134
169
|
|
|
135
170
|
// Query params a tool may forward to the API, beyond `url`. Anything else in the
|
|
136
171
|
// tool arguments is ignored, so a hallucinated param can't reach the endpoint.
|
|
137
|
-
const FORWARDED_PARAMS = ['format', 'selector', 'framework', 'name', 'fields']
|
|
172
|
+
const FORWARDED_PARAMS = ['format', 'selector', 'framework', 'name', 'fields', 'render']
|
|
138
173
|
|
|
139
174
|
async function callApi(endpoint, args) {
|
|
140
175
|
const q = new URLSearchParams({ url: args.url })
|
|
@@ -157,7 +192,7 @@ async function callApi(endpoint, args) {
|
|
|
157
192
|
}
|
|
158
193
|
|
|
159
194
|
const server = new Server(
|
|
160
|
-
{ name: 'miromiro', version: '0.
|
|
195
|
+
{ name: 'miromiro', version: '0.3.0' },
|
|
161
196
|
{ capabilities: { tools: {} } }
|
|
162
197
|
)
|
|
163
198
|
|