miromiro-mcp 0.2.1 → 0.3.1
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 +20 -2
- package/package.json +1 -1
- package/src/index.mjs +48 -13
package/README.md
CHANGED
|
@@ -7,6 +7,20 @@ MCP server for the [MiroMiro](https://miromiro.app/api) design-extraction API. G
|
|
|
7
7
|
|
|
8
8
|
Works with Claude (web, desktop, Claude Code), ChatGPT, Cursor, Windsurf, Codex, and any MCP client.
|
|
9
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
|
+
|
|
10
24
|
## Quick start: remote server (nothing to install)
|
|
11
25
|
|
|
12
26
|
A hosted server lives at:
|
|
@@ -56,6 +70,8 @@ claude mcp add miromiro -s user \
|
|
|
56
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 |
|
|
57
71
|
| `extract_design_tokens` | Full token system with usage counts; `format: css` or `tailwind` for paste-ready output | 10 |
|
|
58
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 |
|
|
59
75
|
| `extract_lottie` | Lottie animation JSON files declared in the page | 3 |
|
|
60
76
|
| `extract_svgs` | Inline SVG code (sanitized, paste-ready) plus external SVG file URLs | 1 |
|
|
61
77
|
| `extract_assets` | Every image URL on a page, tagged by source, with alt text | 1 |
|
|
@@ -69,7 +85,9 @@ Remote results are trimmed by default so a big page cannot flood the chat contex
|
|
|
69
85
|
- `limit` (up to 200) - raise the item cap: "give me 80 SVGs, not 20"
|
|
70
86
|
- `full_output: true` - complete long strings, like entire inline SVG markup
|
|
71
87
|
|
|
72
|
-
The REST API and this local npm server always return everything untrimmed
|
|
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`.
|
|
73
91
|
|
|
74
92
|
## Examples
|
|
75
93
|
|
|
@@ -87,7 +105,7 @@ The agent calls `extract_fonts` and returns every family with its font file URLs
|
|
|
87
105
|
|
|
88
106
|
## Notes
|
|
89
107
|
|
|
90
|
-
- The API reads static HTML and resolves the full CSS cascade
|
|
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).
|
|
91
109
|
- Respect the sites you extract from: check their terms before reusing assets commercially.
|
|
92
110
|
|
|
93
111
|
## Links
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "miromiro-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
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
5
|
"mcpName": "app.miromiro/miromiro",
|
|
6
6
|
"type": "module",
|
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. For jsx/vue, repeated structures (pricing tiers, testimonial cards, feature lists) are collapsed into a data array rendered with .map()/v-for - one template plus the data, like a human would write it. The section's ::before/::after decorations come back as real elements, dark-mode styles as dark: variants, and its @keyframes ship in the css field. 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 - summary.styledCoverage (0-100) tells you how much styling resolved, and a client-rendered app that returns little includes a hint field saying to call again with render=true (+5 credits). Follow it.",
|
|
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
|
}
|
|
@@ -60,7 +69,7 @@ const TOOLS = [
|
|
|
60
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
|
|