wunderui-mcp 1.0.3 → 1.2.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/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "wunderui-mcp",
3
- "version": "1.0.3",
3
+ "version": "1.2.0",
4
+ "mcpName": "io.github.Kl-webmedia/wunderui-mcp",
4
5
  "description": "MCP server that gives AI coding agents the WunderUI design system: tokens, component props, templates and starters. Works with Claude Code, Cursor, Windsurf, Cline and Codex.",
5
6
  "keywords": [
6
7
  "mcp",
package/src/index.mjs CHANGED
@@ -373,6 +373,97 @@ server.registerTool(
373
373
  })
374
374
  )
375
375
 
376
+ /* ---------------------------------------------- skills, instructions, Pro */
377
+
378
+ const siteUrl = () => (process.env.WUNDERUI_SITE ?? design.site ?? "https://wunderui.com").replace(/\/$/, "")
379
+ const licenseKey = () => process.env.WUNDERUI_LICENSE_KEY?.trim() || null
380
+ /** The key goes to wunderui.com (or a local dev server) only, never to another WUNDERUI_SITE. */
381
+ const keyAllowed = () => {
382
+ try {
383
+ const { protocol, hostname } = new URL(siteUrl())
384
+ return (protocol === "https:" && (hostname === "wunderui.com" || hostname === "www.wunderui.com")) || ["localhost", "127.0.0.1", "[::1]"].includes(hostname)
385
+ } catch {
386
+ return false
387
+ }
388
+ }
389
+ const authHeaders = () => (licenseKey() && keyAllowed() ? { authorization: `Bearer ${licenseKey()}` } : {})
390
+ let instructions = null
391
+ try {
392
+ instructions = readFileSync(resolve(dataDir, "INSTRUCTIONS.md"), "utf8")
393
+ } catch {}
394
+
395
+ server.registerTool(
396
+ "get_instructions",
397
+ {
398
+ title: "Get the short WunderUI rules",
399
+ description:
400
+ "The short, always-on rules for building with WunderUI (components, tokens, motion, copy) — the text to keep in CLAUDE.md / AGENTS.md. Read this first; use get_design_md for the full context.",
401
+ inputSchema: {},
402
+ },
403
+ async () => {
404
+ if (instructions) return text(instructions)
405
+ const r = await fetch(`${siteUrl()}/INSTRUCTIONS.md`, { signal: AbortSignal.timeout(8000) }).catch(() => null)
406
+ return text(r?.ok ? await r.text() : "INSTRUCTIONS.md is not available offline; use get_design_md.")
407
+ }
408
+ )
409
+
410
+ server.registerTool(
411
+ "list_skills",
412
+ {
413
+ title: "List WunderUI agent skills",
414
+ description:
415
+ "The agent skills that build and check with WunderUI (set up a project, build screens, theme from a brand colour, migrate from shadcn/MUI/Chakra, token check, motion and accessibility audits, Figma). Free skills work for everyone; Pro skills need WUNDERUI_LICENSE_KEY.",
416
+ inputSchema: {},
417
+ },
418
+ async () =>
419
+ text({
420
+ licenseKeySet: Boolean(licenseKey()),
421
+ skills: (design.skills ?? []).map((skill) => ({ name: skill.name, tier: skill.tier, description: skill.description })),
422
+ use: "get_skill({ name }) returns the skill's instructions so you can follow them without installing it.",
423
+ })
424
+ )
425
+
426
+ server.registerTool(
427
+ "get_skill",
428
+ {
429
+ title: "Get a WunderUI agent skill",
430
+ description:
431
+ "Returns one skill's instructions (its SKILL.md) from wunderui.com, so the agent can follow the workflow without installing the skill. Pro skills are sent only with a valid Pro key in WUNDERUI_LICENSE_KEY. Scripts the skill mentions come with the installed skill (wunderui.com/skills).",
432
+ inputSchema: { name: z.string().describe("Skill name, e.g. wunderui-theme or wunderui-screen") },
433
+ },
434
+ async ({ name }) => {
435
+ const r = await fetch(`${siteUrl()}/api/skills/${encodeURIComponent(name)}`, { headers: authHeaders(), signal: AbortSignal.timeout(10000) }).catch(() => null)
436
+ if (!r) return text("wunderui.com is not reachable — install the skill from https://wunderui.com/skills instead.")
437
+ const body = await r.json().catch(() => null)
438
+ if (r.status === 401) return text(`${name} is a Pro skill. Set WUNDERUI_LICENSE_KEY to your WunderUI Pro key (https://wunderui.com/#pricing).`)
439
+ if (!r.ok || !body?.skill) return text(`No skill called "${name}". Available: ${(design.skills ?? []).map((s) => s.name).join(", ")}`)
440
+ return text(body.skill)
441
+ }
442
+ )
443
+
444
+ server.registerTool(
445
+ "get_block_source",
446
+ {
447
+ title: "Get a WunderUI block's source (Pro)",
448
+ description:
449
+ "The React source of a UI block (e.g. category 'app', block 'billing'), optionally one screen, plus the shared helpers it imports. Needs a WunderUI Pro key in WUNDERUI_LICENSE_KEY. Use https://wunderui.com/api/registry/index to see every block.",
450
+ inputSchema: {
451
+ category: z.string(),
452
+ block: z.string(),
453
+ screen: z.string().optional(),
454
+ },
455
+ },
456
+ async ({ category, block, screen }) => {
457
+ if (!licenseKey()) return text("Blocks come with WunderUI Pro. Set WUNDERUI_LICENSE_KEY to your Pro key (https://wunderui.com/#pricing), or browse the free previews at https://wunderui.com/blocks.")
458
+ const url = `${siteUrl()}/api/registry/block/${encodeURIComponent(category)}/${encodeURIComponent(block)}${screen ? `?screen=${encodeURIComponent(screen)}` : ""}`
459
+ const r = await fetch(url, { headers: authHeaders(), signal: AbortSignal.timeout(15000) }).catch(() => null)
460
+ if (!r) return text("wunderui.com is not reachable.")
461
+ if (r.status === 401) return text("The key was not accepted for blocks (blocks need a Pro key).")
462
+ if (!r.ok) return text(`No block ${category}/${block}${screen ? ` screen ${screen}` : ""}.`)
463
+ return text(await r.json())
464
+ }
465
+ )
466
+
376
467
  /* --------------------------------------------------------------------- run */
377
468
 
378
469
  async function main() {