miromiro-mcp 0.7.4 → 0.8.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.
Files changed (3) hide show
  1. package/README.md +13 -8
  2. package/package.json +1 -1
  3. package/src/index.mjs +12 -11
package/README.md CHANGED
@@ -32,8 +32,13 @@ https://miromiro.app/mcp
32
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 (300 credits/month, no card).
33
33
 
34
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).
35
+ - **ChatGPT:** Settings → Security and login → enable Developer mode, then Settings → Plugins → New Plugin with the URL (authentication: OAuth).
36
36
  - **Cursor (remote):** add `{ "mcpServers": { "miromiro": { "url": "https://miromiro.app/mcp" } } }` to `~/.cursor/mcp.json`.
37
+ - **Claude Code:** `claude mcp add --transport http miromiro https://miromiro.app/mcp`, then `/mcp` in a session to sign in.
38
+ - **Codex:** add `[mcp_servers.miromiro]` with `url = "https://miromiro.app/mcp"` to `~/.codex/config.toml`, then run `codex mcp login miromiro`.
39
+ - **Grok Bot:** ask the bot "Add a custom MCP server called miromiro at https://miromiro.app/mcp", then press Authorize.
40
+ - **Muse:** Settings → Connectors → Add custom connector → paste the URL.
41
+ - Step-by-step pages for each agent: https://miromiro.app/api/agents/chatgpt (swap the last part for `claude`, `claude-code`, `codex`, `cursor`, `grok-bot`, `muse` or `openai-dots`).
37
42
  - **Raw API key instead of OAuth:** append `?access_key=mm_live_...` to the URL, or send it as an `Authorization: Bearer` header.
38
43
 
39
44
  ## Local server (npm)
@@ -66,15 +71,15 @@ claude mcp add miromiro -s user \
66
71
 
67
72
  | Tool | What it returns | Credits |
68
73
  |---|---|---|
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 |
74
+ | `extract_code` | A section of a live page as a paste-ready component (Tailwind, HTML+CSS, JSX or Vue) or an importable Elementor template / Shopify section, plus the tokens it uses - start here to rebuild real UI | 25 |
70
75
  | `extract_brand` | Named color roles (primary/accent/background/text), palette, fonts split by role, logo, social profiles, a 0-1 confidence score, and `signals` explaining where the colors came from (declared tokens vs inferred) | 15 |
71
76
  | `extract_design_tokens` | Full token system with usage counts; `format: css` or `tailwind` for paste-ready output, `dtcg` for a W3C Design Tokens file (Style Dictionary / Figma variables) | 10 |
72
77
  | `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 |
78
+ | `extract_videos` | Video files, `og:video`, and player embeds (YouTube, Vimeo, Wistia, Loom), tagged by source; a direct video-file URL is returned as the single result, and a YouTube/Vimeo/TikTok/Dailymotion/Loom video URL returns its player embed without a fetch | 1 |
79
+ | `extract_audio` | Audio files, `og:audio`, and podcast enclosures, tagged by source; a direct audio-file URL is returned as the single result | 1 |
75
80
  | `extract_lottie` | Lottie animation JSON files declared in the page | 3 |
76
81
  | `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 |
82
+ | `extract_assets` | Every image URL on a page, tagged by source, with alt text; a direct image-file URL is returned as the single result | 1 |
78
83
 
79
84
  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
85
 
@@ -104,7 +109,7 @@ Generated code is never trimmed. Results stop at roughly 20k tokens (25k for `ex
104
109
 
105
110
  The REST API and this local npm server always return everything untrimmed, so `limit` and
106
111
  `full_output` apply to the remote server only. Every tool on both servers accepts
107
- `render: true` (paid API plans and credit packs only - a free account gets `402 render_requires_plan` and nothing runs, except when the site itself refuses the request: see Notes), plus two more shared arguments:
112
+ `render: true` (free accounts get 5 renders a month, then the call runs static and `usage.renderSkipped` says so; paid API plans and credit packs render without a monthly limit - see Notes), plus two more shared arguments:
108
113
 
109
114
  - `fresh: true` - skip the 24-hour cache and re-extract the live page (charged normally); use when the site just changed
110
115
  - `screenshot: true` (requires `render: true`) - the result gains a `screenshot` field: a public URL of a 1440x900 capture of the page, live for 7 days, at no extra credits - show it to the user to verify an extraction. Pass `'full'` instead of `true` for the entire page height.
@@ -131,8 +136,8 @@ The agent calls `extract_fonts` and returns every family with its font file URLs
131
136
 
132
137
  ## Notes
133
138
 
134
- - 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). Rendering is included in the paid API plans and credit packs, not in the free account: a free account gets `402 render_requires_plan` before anything runs or is charged.
135
- - One exception, and it needs no flag: when the SITE refuses a free account's plain request (401, 403 or 406 - publishers commonly turn away datacenter IPs even for a public page), the server retries that call in a real browser automatically and free, a few times a month. The result then carries `usage.renderRescue` (a sentence to read to the user) and `usage.renderRescuesLeft`. So on a free account, do not treat a 401/403/406 as "this site cannot be extracted" until those are used up.
139
+ - 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). Free accounts get 5 renders a month (`usage.freeRender` reports each one and how many are left); after that a `render: true` call runs as the static extraction and `usage.renderSkipped` says so - never a refusal. Paid API plans and credit packs render without a monthly limit.
140
+ - The same allowance also works without the flag: when the SITE refuses a free account's plain request (401, 403 or 406 - publishers commonly turn away datacenter IPs even for a public page), the server retries that call in a real browser automatically and free, a few times a month. The result then carries `usage.renderRescue` (a sentence to read to the user) and `usage.renderRescuesLeft`. So on a free account, do not treat a 401/403/406 as "this site cannot be extracted" until those are used up.
136
141
  - Every result carries `usage` with the credits left this month. If the account holds a credit pack (one-off credits that never expire, bought at https://miromiro.app/api/pricing), `usage.packCredits` is the pack balance left; packs are drawn after the monthly credits, so a free account with a pack keeps working past its 300 credits.
137
142
  - Respect the sites you extract from: check their terms before reusing assets commercially.
138
143
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miromiro-mcp",
3
- "version": "0.7.4",
3
+ "version": "0.8.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
5
  "mcpName": "app.miromiro/miromiro",
6
6
  "type": "module",
package/src/index.mjs CHANGED
@@ -45,7 +45,7 @@ const NAMED_SECTIONS = ['hero', 'header', 'footer', 'pricing', 'features', 'test
45
45
  // client-rendered React/Next/SPA sites that return little statically.
46
46
  const RENDER_ARG = {
47
47
  type: 'boolean',
48
- description: 'Run the page in a real browser first so its JavaScript executes - use for client-rendered (React/Next/SPA) sites, when a normal call came back thin, or after a 403 target_blocked (bot protection refuses plain HTTP requests but usually lets a browser through). Slower, and adds 5 credits to the call. Paid API plans and credit packs only: on a free account it returns 402 render_requires_plan and nothing runs - never pass it as a first attempt, and when a free account gets that error, retry without it and tell the user this page needs JS rendering, a paid feature. Free accounts still get rendering automatically when the SITE refuses the plain request (401/403/406): the server retries it in a browser at no charge, a few times a month, and the result carries usage.renderRescue explaining what happened - read that sentence out to the user when it appears.'
48
+ description: 'Run the page in a real browser first so its JavaScript executes - use for client-rendered (React/Next/SPA) sites, when a normal call came back thin, or after a 403 target_blocked (bot protection refuses plain HTTP requests but usually lets a browser through). Slower, and adds 5 credits to the call. Never pass it as a first attempt: call static first and render only when the result is thin or the hint asks for it. Free accounts get 5 renders a month; after that a render=true call runs as the static extraction and usage.renderSkipped says so (never a refusal) - relay that sentence and do not pass render=true again that month. Paid API plans and credit packs render without a monthly limit. Free accounts also get rendering automatically, from the same allowance, when the SITE refuses the plain request (401/403/406): the server retries it in a browser at no charge and the result carries usage.renderRescue - read that sentence out to the user when it appears.'
49
49
  }
50
50
 
51
51
  // Shared input arg: skip the 24h cache for this one call (charged normally).
@@ -82,14 +82,14 @@ const TOOLS = [
82
82
  title: 'Extract section as code',
83
83
  endpoint: 'code',
84
84
  description:
85
- "Use when the user asks to rebuild, clone, copy, port, restyle or 'make it look like' part of a real website, or wants a site's section as Tailwind, HTML+CSS, React (JSX) or Vue. Give it the URL and, when the user named a part of the page, section (hero, header, footer, pricing, features, testimonials, faq, cta) - never invent a CSS selector. With neither, it auto-detects the hero; every call also returns `sections`, each other extractable section with a label and a ready selector for a follow-up call. Returns a self-contained component with the page's real resolved styles (true colors, fonts, spacing, copy) plus the tokens and assets it uses, so you adapt real values instead of estimating from a screenshot. For jsx/vue, repeated structures collapse into a data array rendered with .map()/v-for; ::before/::after come back as real elements; @keyframes ship in the css field. The `theme` field says which theme the code actually is - sites with a prefers-color-scheme dark variant return the light branch unless you pass theme='dark'; say which you returned. Not for: whole-page scraping, page text, or sites that need a login. Reads static HTML/CSS by default; if the result is thin or carries a `hint`, call again with render=true (+5 credits).",
85
+ "Use when the user asks to rebuild, clone, copy, port, restyle or 'make it look like' part of a real website, or wants a site's section as Tailwind, HTML+CSS, React (JSX) or Vue - or as a file to import into a site builder: framework=elementor for an Elementor template (WordPress), framework=shopify for a Shopify theme section. Give it the URL and, when the user named a part of the page, section (hero, header, footer, pricing, features, testimonials, faq, cta) - never invent a CSS selector. With neither, it auto-detects the hero; every call also returns `sections`, each other extractable section with a label and a ready selector for a follow-up call. Returns a self-contained component with the page's real resolved styles (true colors, fonts, spacing, copy) plus the tokens and assets it uses, so you adapt real values instead of estimating from a screenshot. For jsx/vue, repeated structures collapse into a data array rendered with .map()/v-for; ::before/::after come back as real elements; @keyframes ship in the css field. The `theme` field says which theme the code actually is - sites with a prefers-color-scheme dark variant return the light branch unless you pass theme='dark'; say which you returned. Not for: whole-page scraping, page text, or sites that need a login. Reads static HTML/CSS by default; if the result is thin or carries a `hint`, call again with render=true (+5 credits).",
86
86
  inputSchema: {
87
87
  type: 'object',
88
88
  properties: {
89
89
  url: { type: 'string', description: 'The website URL, e.g. stripe.com' },
90
90
  section: { type: 'string', enum: NAMED_SECTIONS, description: "Which part of the page, by name - use this when the user asked for the pricing, footer, header/nav, features, testimonials, FAQ or call-to-action block. Resolved on the page itself from its ids, classes, headings and, failing those, its content (prices, blockquotes, FAQ items), so it works without seeing the markup and on pages that label nothing. Omit for the hero. If the page has no such section you get the hero plus a `note` listing what is there." },
91
91
  selector: { type: 'string', description: 'CSS selector for the section, ONLY when you have one from a previous result\'s `sections` list (e.g. "section.pricing-grid"). Do not guess selectors: a selector that matches nothing falls back to the hero with a `note`, and the guess costs a call. Prefer `section` for named parts of a page.' },
92
- framework: { type: 'string', enum: ['tailwind', 'html', 'jsx', 'vue'], description: 'The code to generate (default tailwind).' },
92
+ framework: { type: 'string', enum: ['tailwind', 'html', 'jsx', 'vue', 'elementor', 'shopify'], description: "The code to generate (default tailwind). 'elementor' returns an importable Elementor template: tell the user to save `code` as a .json file and import it in WordPress under Templates > Saved Templates > Import. 'shopify' returns a theme section: save `code` as sections/<name>.liquid in the theme, then add it from the theme editor. Use the theme code editor's sections folder, not a Custom Liquid block (that block is capped at 50 KB and cannot hold the schema). Both arrive as one styled HTML block (edited as code, not as native widgets), with the section's responsive and hover styles included and its CSS scoped so it cannot clash with the theme. Give the user the file contents complete and unmodified." },
93
93
  name: { type: 'string', description: 'Component name for the jsx target (default ExtractedSection). Ignored by tailwind, html and vue. Renaming never re-runs the extraction - it costs 0 credits on a cached section.' },
94
94
  theme: { type: 'string', enum: ['light', 'dark'], description: "Which colour scheme to extract (default light). Pass 'dark' when the user is looking at the site in dark mode or asks for the dark version: the page's prefers-color-scheme: dark rules and custom properties become the component itself instead of riding along as dark: variants. Works whether the site expresses its themes as @media (prefers-color-scheme) or as a root attribute/class the CSS is scoped to ([data-theme=dark], html.dark). Sites that switch themes purely in JavaScript cannot be selected this way - you get the page default plus a hint saying so. No extra credits." },
95
95
  render: RENDER_ARG, fresh: FRESH_ARG, screenshot: SCREENSHOT_ARG
@@ -118,9 +118,9 @@ const TOOLS = [
118
118
  }
119
119
  }
120
120
  },
121
- framework: { type: 'string', description: 'tailwind | html | jsx | vue' },
122
- code: { type: 'string', description: 'The generated, paste-ready component code. For jsx/vue, repeated structures (tiers, cards, list items) are collapsed into a data array rendered with .map()/v-for' },
123
- css: { type: 'string', description: 'Companion CSS: always set for framework=html; for other frameworks it carries the @keyframes the section uses (add to the global stylesheet)' },
121
+ framework: { type: 'string', description: 'tailwind | html | jsx | vue | elementor | shopify' },
122
+ code: { type: 'string', description: 'The generated, paste-ready component code. For jsx/vue, repeated structures (tiers, cards, list items) are collapsed into a data array rendered with .map()/v-for. For elementor this is the complete template JSON (a .json file); for shopify the complete section file (a .liquid file) - hand either over whole' },
123
+ css: { type: 'string', description: 'Companion CSS: always set for framework=html; for tailwind/jsx/vue it carries the @keyframes the section uses (add to the global stylesheet); null for elementor/shopify, whose file already contains its CSS' },
124
124
  tokens: { type: 'object', description: 'Design tokens the extracted section actually uses' },
125
125
  theme: { type: 'string', description: "light | dark: which one the code you just received actually is. Measured from the page, never echoed from your request - so if you asked for a theme the site cannot serve, this reports what you really got. Tell the user which theme they got when it may not match what they see on the site" },
126
126
  assets: { description: 'Images/fonts the section references' },
@@ -235,7 +235,7 @@ const TOOLS = [
235
235
  title: 'Extract images',
236
236
  endpoint: 'images',
237
237
  description:
238
- "Use when the user wants the images from a page - photos, illustrations, backgrounds, favicon, social image - as resolved URLs tagged by source (img, srcset, picture, CSS background, favicon, og). Not for: icons and vector logos (use extract_svgs) or fonts (use extract_fonts). If images are mounted by JavaScript, call again with render=true (+5 credits).",
238
+ "Use when the user wants the images from a page - photos, illustrations, backgrounds, favicon, social image - as resolved URLs tagged by source (img, srcset, picture, CSS background, favicon, og). A direct image-file URL comes back as the single result (source file). Not for: icons and vector logos (use extract_svgs) or fonts (use extract_fonts). If images are mounted by JavaScript, call again with render=true (+5 credits).",
239
239
  inputSchema: {
240
240
  type: 'object',
241
241
  properties: { url: { type: 'string', description: 'The website URL' }, render: RENDER_ARG, fresh: FRESH_ARG, screenshot: SCREENSHOT_ARG },
@@ -399,7 +399,7 @@ const TOOLS = [
399
399
  title: 'Extract videos',
400
400
  endpoint: 'videos',
401
401
  description:
402
- 'Use when the user wants the videos on a page: <video>/<source> files, og:video, YouTube/Vimeo/Wistia/Loom embeds and direct video links, each tagged by source with a poster when declared. `source: "embed"` is a player page, not a downloadable file. Most players mount with JavaScript: if the result is thin or empty, call again with render=true (+5 credits).',
402
+ 'Use when the user wants the videos on a page: <video>/<source> files, og:video, YouTube/Vimeo/Wistia/Loom embeds and direct video links, each tagged by source with a poster when declared. `source: "embed"` is a player page, not a downloadable file; a direct video-file URL comes back as the single result (source file). A YouTube, Vimeo, TikTok, Dailymotion or Loom video URL is answered from the URL alone with its player embed (source platform, no fetch, never pass render for these) - tell the user the file itself cannot be downloaded through MiroMiro. Most other players mount with JavaScript: if the result is thin or empty, call again with render=true (+5 credits).',
403
403
  inputSchema: {
404
404
  type: 'object',
405
405
  properties: { url: { type: 'string', description: 'The website URL' }, render: RENDER_ARG, fresh: FRESH_ARG, screenshot: SCREENSHOT_ARG },
@@ -435,7 +435,7 @@ const TOOLS = [
435
435
  title: 'Extract audio',
436
436
  endpoint: 'audio',
437
437
  description:
438
- 'Use when the user wants the audio files on a page: <audio>/<source> files, og:audio, podcast enclosures and download links, each tagged by source. If a JavaScript player mounts the audio at runtime, call again with render=true (+5 credits).',
438
+ 'Use when the user wants the audio files on a page: <audio>/<source> files, og:audio, podcast enclosures and download links, each tagged by source; a direct audio-file URL comes back as the single result (source file). If a JavaScript player mounts the audio at runtime, call again with render=true (+5 credits).',
439
439
  inputSchema: {
440
440
  type: 'object',
441
441
  properties: { url: { type: 'string', description: 'The website URL' }, render: RENDER_ARG, fresh: FRESH_ARG, screenshot: SCREENSHOT_ARG },
@@ -531,7 +531,7 @@ const PROMPTS = [
531
531
  arguments: [
532
532
  PROMPT_URL_ARG,
533
533
  { name: 'section', description: `Which part: ${NAMED_SECTIONS.join(', ')} (default hero)` },
534
- { name: 'framework', description: 'tailwind (default), html, jsx or vue' }
534
+ { name: 'framework', description: 'tailwind (default), html, jsx, vue, elementor or shopify' }
535
535
  ],
536
536
  render: ({ url, section, framework }) =>
537
537
  `Rebuild the ${section || 'hero'} section of ${url} as ${framework || 'tailwind'} code I can paste into my project. ` +
@@ -580,6 +580,7 @@ const INSTRUCTIONS =
580
580
  '`hint`, or a 403 target_blocked comes back, retry the SAME url with render=true (+5 credits) before telling the ' +
581
581
  'user a site cannot be extracted. Theme: a site with a prefers-color-scheme dark variant returns its light branch ' +
582
582
  'by default; pass theme=dark to extract_code for the dark version and report the `theme` field you actually got. ' +
583
+ 'For WordPress/Elementor or Shopify users, extract_code framework=elementor|shopify returns an importable template (.json) or theme section (.liquid) instead of component code. ' +
583
584
  'extract_design_tokens returns values and counts, not layout - not a substitute for extract_code on a rebuild; ' +
584
585
  'format=dtcg gives a W3C Design Tokens file. Explain results, do not dump them: extract_brand\'s ' +
585
586
  'signals.explanation says whether the colors were declared by the site or inferred (primarySource "usage" = a ' +
@@ -587,7 +588,7 @@ const INSTRUCTIONS =
587
588
  'fresh=true bypasses the 24h cache. Every result carries `usage` (credits left; `packCredits` = never-expiring pack balance when the account holds one).'
588
589
 
589
590
  const server = new Server(
590
- { name: 'miromiro', version: '0.7.4' },
591
+ { name: 'miromiro', version: '0.8.0' },
591
592
  { capabilities: { tools: {}, prompts: {} }, instructions: INSTRUCTIONS }
592
593
  )
593
594