@kanso-protocol/mcp 0.1.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 +129 -0
- package/dist/index.js +343 -0
- package/figma-mapping.json +91 -0
- package/manifest.json +10693 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# @kanso-protocol/mcp
|
|
2
|
+
|
|
3
|
+
[Model Context Protocol](https://modelcontextprotocol.io) server for [Kanso Protocol](https://gregnblack.github.io/kanso-protocol/) — exposes the catalog of components, patterns, and design tokens to AI assistants so they can author Kanso UI without leaving the editor.
|
|
4
|
+
|
|
5
|
+
## What you get
|
|
6
|
+
|
|
7
|
+
Eleven tools, all running locally over stdio.
|
|
8
|
+
|
|
9
|
+
**Catalog** — typed metadata extracted from the source code at build time:
|
|
10
|
+
|
|
11
|
+
| Tool | What it returns |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `catalog_overview` | Version, totals, links — call once per session. |
|
|
14
|
+
| `list_components` | Compact list of every `@kanso-protocol/*` component with selector, package, ARIA role. |
|
|
15
|
+
| `get_component` | Full record — inputs (name/type/default), outputs, ARIA role, keyboard patterns, Storybook URL, **plus the Figma node ref**. |
|
|
16
|
+
| `list_patterns` | Same shape, for higher-level pattern packages. |
|
|
17
|
+
| `get_pattern` | Full record for one pattern. |
|
|
18
|
+
| `list_tokens` | Every CSS variable from `tokens.css`, optionally filtered by category or substring. |
|
|
19
|
+
| `get_token` | One token by name (with or without the `--kp-` prefix). |
|
|
20
|
+
|
|
21
|
+
**Figma bridge** — points at the canonical public Kanso Design System file, so any Figma-MCP-aware client (Claude Code with the Figma MCP installed, Cursor, etc.) can pull the visual reference for the same component the assistant is writing code for:
|
|
22
|
+
|
|
23
|
+
| Tool | What it returns |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `figma_context` | File key + URL of the Kanso Figma library, IDs of the Components / Patterns / Foundations / Examples pages, and the linked Tabler Icon Pack file. |
|
|
26
|
+
| `figma_for_component` | `{ fileKey, nodeId, url }` for the component's master frame — feed straight into Figma MCP's `get_design_context` or `get_screenshot`. |
|
|
27
|
+
| `figma_for_pattern` | Same shape for higher-level patterns. |
|
|
28
|
+
| `figma_for_icon` | Tabler icon library context + a search hint the assistant uses with Figma MCP's `search_design_system`. |
|
|
29
|
+
|
|
30
|
+
The data comes from a manifest baked at build time — no network calls, deterministic, fast.
|
|
31
|
+
|
|
32
|
+
### Customizing the Figma bridge
|
|
33
|
+
|
|
34
|
+
By default the bridge points at the **public canonical Kanso Design System file**. That's the right target for most users — even if you fork the system, the canonical Figma file is still your visual source of truth.
|
|
35
|
+
|
|
36
|
+
Override when you need to:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# point at a forked / private Figma file
|
|
40
|
+
KANSO_FIGMA_FILE_KEY=<your-file-key> npx @kanso-protocol/mcp
|
|
41
|
+
|
|
42
|
+
# (optional) override the URL too
|
|
43
|
+
KANSO_FIGMA_FILE_URL=https://figma.com/design/<key>/<name> npx @kanso-protocol/mcp
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The component/pattern/foundation node IDs in the shipped mapping point at the canonical file's nodes; they will not match arbitrary forked layouts. If you've heavily restructured your Figma file, set `KANSO_FIGMA_FILE_KEY` to it and the bridge will return your file key with the original node IDs (use them as a hint, not a guarantee), or stop using `figma_for_component` entirely and fall back to Figma MCP's `search_design_system` with the right file key.
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
The server runs via `npx`, so there's nothing to install globally. Pick the snippet for your editor:
|
|
51
|
+
|
|
52
|
+
### Claude Code
|
|
53
|
+
|
|
54
|
+
One command from the project root:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
claude mcp add kanso -- npx @kanso-protocol/mcp
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
…or drop this into your project's `.mcp.json` (create the file if missing):
|
|
61
|
+
|
|
62
|
+
```jsonc
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"kanso": { "command": "npx", "args": ["@kanso-protocol/mcp"] }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Restart Claude Code, then run `/mcp` to confirm `kanso` appears as ✔ connected with 7 tools.
|
|
71
|
+
|
|
72
|
+
### Cursor
|
|
73
|
+
|
|
74
|
+
Open *Settings → Features → MCP* → **Add new MCP server**:
|
|
75
|
+
|
|
76
|
+
- Name: `kanso`
|
|
77
|
+
- Type: `command`
|
|
78
|
+
- Command: `npx @kanso-protocol/mcp`
|
|
79
|
+
|
|
80
|
+
…or edit `~/.cursor/mcp.json` (project-scoped: `.cursor/mcp.json` in repo root):
|
|
81
|
+
|
|
82
|
+
```jsonc
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"kanso": { "command": "npx", "args": ["@kanso-protocol/mcp"] }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### VS Code (Continue, Cline, GitHub Copilot agent mode)
|
|
91
|
+
|
|
92
|
+
Add to the extension's MCP config — for Continue this is `~/.continue/config.json`, for Cline it's the *Cline MCP Servers* settings panel. Same shape:
|
|
93
|
+
|
|
94
|
+
```jsonc
|
|
95
|
+
{
|
|
96
|
+
"mcpServers": {
|
|
97
|
+
"kanso": { "command": "npx", "args": ["@kanso-protocol/mcp"] }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Any other MCP-aware client (Zed, Windsurf, Goose, custom)
|
|
103
|
+
|
|
104
|
+
Any client that speaks the [MCP stdio transport](https://modelcontextprotocol.io/docs/concepts/transports) works:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
command: npx
|
|
108
|
+
args: ["@kanso-protocol/mcp"]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Usage
|
|
112
|
+
|
|
113
|
+
Once connected, ask the assistant things like:
|
|
114
|
+
|
|
115
|
+
- *"What size ramp does `kp-input` support, and which validators does form-field translate by default?"*
|
|
116
|
+
- *"Show me every danger-color token."*
|
|
117
|
+
- *"Which Kanso component should I use for a settings sidebar with collapsible sections?"*
|
|
118
|
+
- *"Generate an inline edit form using kp-form-field, kp-input, and kp-button."*
|
|
119
|
+
|
|
120
|
+
The assistant calls `list_components` / `get_component` / `list_tokens` under the hood and answers from the live catalog instead of guessing.
|
|
121
|
+
|
|
122
|
+
## What's not in 0.1.x
|
|
123
|
+
|
|
124
|
+
- **No Figma bridge yet.** The next minor will add `figma_component_for(name)` and `figma_token_for(name)` so an agent can hop straight from a Kanso component to its Figma master node and pull a screenshot via the Figma MCP.
|
|
125
|
+
- **No example-generation tool.** Pure metadata for now — let the agent compose snippets from the inputs/outputs it learns. We'll revisit if it turns out hand-crafting matters.
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT — © GregNBlack. Part of the [Kanso Protocol](https://github.com/GregNBlack/kanso-protocol) monorepo.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Kanso Protocol — Model Context Protocol server.
|
|
4
|
+
*
|
|
5
|
+
* Exposes the component / pattern / token catalog so an AI agent in Claude
|
|
6
|
+
* Code / Cursor / VS Code can author Kanso UI without the developer having
|
|
7
|
+
* to paste docs or sift through package READMEs.
|
|
8
|
+
*
|
|
9
|
+
* Transport: stdio. Launch via `npx @kanso-protocol/mcp`.
|
|
10
|
+
* Manifest: ships next to the compiled JS (packages/mcp/manifest.json),
|
|
11
|
+
* regenerated by `scripts/generate-mcp-manifest.js` on every build.
|
|
12
|
+
*
|
|
13
|
+
* Implementation note — uses the low-level `Server` API rather than the
|
|
14
|
+
* high-level `McpServer`. The latter infers tool input shapes through
|
|
15
|
+
* deeply generic Zod plumbing, which trips TS's "Type instantiation is
|
|
16
|
+
* excessively deep" check in CI. Hand-rolled JSON Schema avoids the
|
|
17
|
+
* inference cost entirely and keeps the build deterministic.
|
|
18
|
+
*/
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
import { fileURLToPath } from 'node:url';
|
|
21
|
+
import { dirname, resolve } from 'node:path';
|
|
22
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
23
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
24
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
25
|
+
function loadManifest() {
|
|
26
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
// When installed via npm, the manifest lives one level up (next to package.json).
|
|
28
|
+
const candidates = [
|
|
29
|
+
resolve(here, '..', 'manifest.json'),
|
|
30
|
+
resolve(here, 'manifest.json'),
|
|
31
|
+
];
|
|
32
|
+
for (const p of candidates) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(readFileSync(p, 'utf8'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
/* try next */
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
throw new Error('manifest.json not found — run `node scripts/generate-mcp-manifest.js` first.');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve the Figma file context with environment-variable overrides:
|
|
44
|
+
* KANSO_FIGMA_FILE_KEY — point at a forked / private Kanso file instead of
|
|
45
|
+
* the canonical public one. Useful when a team
|
|
46
|
+
* branches the system internally.
|
|
47
|
+
* KANSO_FIGMA_FILE_URL — full URL override; rarely needed (auto-derived
|
|
48
|
+
* from fileKey if missing).
|
|
49
|
+
* If the manifest has no figma block (e.g., the mapping JSON wasn't shipped),
|
|
50
|
+
* we return null and the bridge tools downgrade to a "not configured" error.
|
|
51
|
+
*/
|
|
52
|
+
function resolveFigma(fig) {
|
|
53
|
+
if (!fig)
|
|
54
|
+
return null;
|
|
55
|
+
const overrideKey = process.env.KANSO_FIGMA_FILE_KEY;
|
|
56
|
+
const overrideUrl = process.env.KANSO_FIGMA_FILE_URL;
|
|
57
|
+
if (!overrideKey && !overrideUrl)
|
|
58
|
+
return fig;
|
|
59
|
+
const fileKey = overrideKey ?? fig.fileKey;
|
|
60
|
+
const url = overrideUrl ?? `https://www.figma.com/design/${fileKey}/Design-System`;
|
|
61
|
+
return { ...fig, fileKey, url };
|
|
62
|
+
}
|
|
63
|
+
function nodeUrl(fileKey, nodeId) {
|
|
64
|
+
return `https://www.figma.com/design/${fileKey}/Design-System?node-id=${nodeId.replace(':', '-')}`;
|
|
65
|
+
}
|
|
66
|
+
function summarize(c) {
|
|
67
|
+
return {
|
|
68
|
+
name: c.name,
|
|
69
|
+
selector: c.selector,
|
|
70
|
+
package: c.package,
|
|
71
|
+
summary: c.description.split('\n')[0] || '',
|
|
72
|
+
ariaRole: c.ariaRole,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const manifest = loadManifest();
|
|
76
|
+
const figma = resolveFigma(manifest.figma);
|
|
77
|
+
const tools = [
|
|
78
|
+
{
|
|
79
|
+
name: 'catalog_overview',
|
|
80
|
+
title: 'Catalog overview',
|
|
81
|
+
description: 'One-shot summary: version, totals, and generation timestamp. Call once at session start to get a feel for what the MCP knows.',
|
|
82
|
+
inputSchema: { type: 'object', properties: {} },
|
|
83
|
+
handler: () => JSON.stringify({
|
|
84
|
+
version: manifest.version,
|
|
85
|
+
generatedAt: manifest.generatedAt,
|
|
86
|
+
totals: manifest.totals,
|
|
87
|
+
docs: 'https://gregnblack.github.io/kanso-protocol/',
|
|
88
|
+
repo: 'https://github.com/GregNBlack/kanso-protocol',
|
|
89
|
+
}, null, 2),
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'list_components',
|
|
93
|
+
title: 'List Kanso components',
|
|
94
|
+
description: 'Return a compact list of every component in @kanso-protocol/* — one line per item with selector, package, ARIA role, and a one-line summary. Use this first to discover what exists before calling get_component for details.',
|
|
95
|
+
inputSchema: { type: 'object', properties: {} },
|
|
96
|
+
handler: () => JSON.stringify(manifest.components.map(summarize), null, 2),
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'get_component',
|
|
100
|
+
title: 'Get one Kanso component',
|
|
101
|
+
description: 'Full metadata for a single component: description, usage example, every @Input (name/type/default/description), every @Output with its payload type, ARIA role, detected keyboard patterns, and the Storybook docs URL. Accepts either the short name ("button") or the selector ("kp-button").',
|
|
102
|
+
inputSchema: {
|
|
103
|
+
type: 'object',
|
|
104
|
+
properties: {
|
|
105
|
+
name: { type: 'string', description: 'Component short name (e.g. "button") or selector (e.g. "kp-button")' },
|
|
106
|
+
},
|
|
107
|
+
required: ['name'],
|
|
108
|
+
},
|
|
109
|
+
handler: (args) => {
|
|
110
|
+
const raw = String(args.name ?? '');
|
|
111
|
+
const key = raw.replace(/^kp-/, '').toLowerCase();
|
|
112
|
+
const hit = manifest.components.find((c) => c.name === key || c.selector === `kp-${key}` || c.className.toLowerCase() === `kp${key.replace(/-/g, '')}component`);
|
|
113
|
+
if (!hit)
|
|
114
|
+
return { error: `No component named "${raw}". Call list_components to see available names.` };
|
|
115
|
+
return JSON.stringify(hit, null, 2);
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'list_patterns',
|
|
120
|
+
title: 'List Kanso patterns',
|
|
121
|
+
description: 'Patterns are larger compositions (page shells, layouts, bars) built from components — e.g. app-shell, page-header, filter-bar. Same shape as components in the manifest.',
|
|
122
|
+
inputSchema: { type: 'object', properties: {} },
|
|
123
|
+
handler: () => JSON.stringify(manifest.patterns.map(summarize), null, 2),
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
name: 'get_pattern',
|
|
127
|
+
title: 'Get one Kanso pattern',
|
|
128
|
+
description: 'Full metadata for a single pattern — inputs, outputs, description, docs URL.',
|
|
129
|
+
inputSchema: {
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: { name: { type: 'string', description: 'Pattern short name (e.g. "page-header")' } },
|
|
132
|
+
required: ['name'],
|
|
133
|
+
},
|
|
134
|
+
handler: (args) => {
|
|
135
|
+
const raw = String(args.name ?? '');
|
|
136
|
+
const key = raw.replace(/^kp-/, '').toLowerCase();
|
|
137
|
+
const hit = manifest.patterns.find((p) => p.name === key || p.selector === `kp-${key}`);
|
|
138
|
+
if (!hit)
|
|
139
|
+
return { error: `No pattern named "${raw}". Call list_patterns to see available names.` };
|
|
140
|
+
return JSON.stringify(hit, null, 2);
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
name: 'list_tokens',
|
|
145
|
+
title: 'List Kanso design tokens',
|
|
146
|
+
description: 'Return every CSS custom property from the built tokens.css. Optionally filter by category ("color" | "spacing" | "radius" | "motion" | "font" | …) or by a substring of the name.',
|
|
147
|
+
inputSchema: {
|
|
148
|
+
type: 'object',
|
|
149
|
+
properties: {
|
|
150
|
+
category: { type: 'string', description: 'Top-level category (e.g. "color"). Omit to return all.' },
|
|
151
|
+
contains: { type: 'string', description: 'Case-insensitive substring to match anywhere in the token name.' },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
handler: (args) => {
|
|
155
|
+
const category = args.category ? String(args.category) : undefined;
|
|
156
|
+
const needle = args.contains ? String(args.contains).toLowerCase() : undefined;
|
|
157
|
+
const rows = manifest.tokens.filter((t) => {
|
|
158
|
+
if (category && t.category !== category)
|
|
159
|
+
return false;
|
|
160
|
+
if (needle && !t.name.toLowerCase().includes(needle))
|
|
161
|
+
return false;
|
|
162
|
+
return true;
|
|
163
|
+
});
|
|
164
|
+
return JSON.stringify(rows, null, 2);
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: 'get_token',
|
|
169
|
+
title: 'Get one Kanso token',
|
|
170
|
+
description: 'Look up a single CSS variable. Accepts with or without the "--kp-" prefix.',
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: 'object',
|
|
173
|
+
properties: {
|
|
174
|
+
name: { type: 'string', description: 'Token name, with or without "--kp-" prefix (e.g. "color-blue-600" or "--kp-color-blue-600")' },
|
|
175
|
+
},
|
|
176
|
+
required: ['name'],
|
|
177
|
+
},
|
|
178
|
+
handler: (args) => {
|
|
179
|
+
const raw = String(args.name ?? '');
|
|
180
|
+
const normalized = raw.startsWith('--kp-') ? raw : `--kp-${raw.replace(/^kp-/, '')}`;
|
|
181
|
+
const hit = manifest.tokens.find((t) => t.name === normalized);
|
|
182
|
+
if (!hit)
|
|
183
|
+
return { error: `No token named "${normalized}". Call list_tokens to discover.` };
|
|
184
|
+
return JSON.stringify(hit, null, 2);
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
// ─── Figma bridge ─────────────────────────────────────────────────────
|
|
188
|
+
// These tools return the Figma context the assistant needs to immediately
|
|
189
|
+
// call the official Figma MCP (https://help.figma.com/hc/articles/32132100833559)
|
|
190
|
+
// — `get_design_context`, `get_screenshot`, `get_variable_defs`, etc. —
|
|
191
|
+
// without an extra discovery step. Each response includes:
|
|
192
|
+
// - fileKey + nodeId (feed straight into the Figma MCP)
|
|
193
|
+
// - url (clickable link for the human in the loop)
|
|
194
|
+
// - relatedFigmaTools (which Figma MCP tools to chain next)
|
|
195
|
+
// The default file key points at the canonical public Kanso Design System
|
|
196
|
+
// file. Override with KANSO_FIGMA_FILE_KEY env var when working off a fork.
|
|
197
|
+
{
|
|
198
|
+
name: 'figma_context',
|
|
199
|
+
title: 'Figma library context',
|
|
200
|
+
description: 'Top-level Figma metadata: canonical Design System file key + URL, page IDs (Components / Patterns / Foundations / Examples), and the linked Tabler icon library file. Call once when starting Figma-aware work so subsequent tools have the right fileKey to target.',
|
|
201
|
+
inputSchema: { type: 'object', properties: {} },
|
|
202
|
+
handler: () => {
|
|
203
|
+
if (!figma) {
|
|
204
|
+
return { error: 'Figma bridge is not configured. The manifest was generated without a figma-mapping.json — re-run `npm run generate:mcp-manifest` after copying the mapping into the package.' };
|
|
205
|
+
}
|
|
206
|
+
return JSON.stringify({
|
|
207
|
+
fileKey: figma.fileKey,
|
|
208
|
+
fileName: figma.fileName,
|
|
209
|
+
url: figma.url,
|
|
210
|
+
pages: figma.pages,
|
|
211
|
+
iconLibrary: figma.iconLibrary,
|
|
212
|
+
foundationsPages: Object.entries(figma.foundations).map(([key, nodeId]) => ({
|
|
213
|
+
name: key,
|
|
214
|
+
nodeId,
|
|
215
|
+
url: nodeUrl(figma.fileKey, nodeId),
|
|
216
|
+
})),
|
|
217
|
+
notes: [
|
|
218
|
+
'Pass fileKey to Figma MCP tools (get_design_context, get_screenshot, get_variable_defs).',
|
|
219
|
+
'KANSO_FIGMA_FILE_KEY env var overrides the canonical key when working from a fork.',
|
|
220
|
+
],
|
|
221
|
+
}, null, 2);
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
name: 'figma_for_component',
|
|
226
|
+
title: 'Figma node for a Kanso component',
|
|
227
|
+
description: 'Resolve a Kanso component name to its master frame in the Figma library. Returns fileKey + nodeId you feed into the Figma MCP\'s get_design_context (for code-and-screenshot) or get_screenshot (for a flat preview).',
|
|
228
|
+
inputSchema: {
|
|
229
|
+
type: 'object',
|
|
230
|
+
properties: {
|
|
231
|
+
name: { type: 'string', description: 'Component short name (e.g. "button") or selector (e.g. "kp-button")' },
|
|
232
|
+
},
|
|
233
|
+
required: ['name'],
|
|
234
|
+
},
|
|
235
|
+
handler: (args) => {
|
|
236
|
+
if (!figma)
|
|
237
|
+
return { error: 'Figma bridge is not configured.' };
|
|
238
|
+
const raw = String(args.name ?? '');
|
|
239
|
+
const key = raw.replace(/^kp-/, '').toLowerCase();
|
|
240
|
+
const hit = manifest.components.find((c) => c.name === key || c.selector === `kp-${key}` || c.className.toLowerCase() === `kp${key.replace(/-/g, '')}component`);
|
|
241
|
+
if (!hit)
|
|
242
|
+
return { error: `No component named "${raw}". Call list_components to see available names.` };
|
|
243
|
+
if (!hit.figma) {
|
|
244
|
+
return { error: `Component "${hit.name}" exists but has no Figma node mapping. Add it to packages/mcp/figma-mapping.json and regenerate the manifest.` };
|
|
245
|
+
}
|
|
246
|
+
return JSON.stringify({
|
|
247
|
+
component: hit.name,
|
|
248
|
+
package: hit.package,
|
|
249
|
+
fileKey: figma.fileKey,
|
|
250
|
+
nodeId: hit.figma.nodeId,
|
|
251
|
+
url: nodeUrl(figma.fileKey, hit.figma.nodeId),
|
|
252
|
+
relatedFigmaTools: [
|
|
253
|
+
{ tool: 'get_design_context', purpose: 'Pull the master frame as code + screenshot.' },
|
|
254
|
+
{ tool: 'get_screenshot', purpose: 'Plain PNG of the master frame (no code).' },
|
|
255
|
+
{ tool: 'get_variable_defs', purpose: 'List the CSS variables this frame consumes.' },
|
|
256
|
+
],
|
|
257
|
+
}, null, 2);
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
name: 'figma_for_pattern',
|
|
262
|
+
title: 'Figma node for a Kanso pattern',
|
|
263
|
+
description: 'Same shape as figma_for_component but resolves higher-level patterns (app-shell, page-header, sidebar, …).',
|
|
264
|
+
inputSchema: {
|
|
265
|
+
type: 'object',
|
|
266
|
+
properties: { name: { type: 'string', description: 'Pattern short name (e.g. "page-header")' } },
|
|
267
|
+
required: ['name'],
|
|
268
|
+
},
|
|
269
|
+
handler: (args) => {
|
|
270
|
+
if (!figma)
|
|
271
|
+
return { error: 'Figma bridge is not configured.' };
|
|
272
|
+
const raw = String(args.name ?? '');
|
|
273
|
+
const key = raw.replace(/^kp-/, '').toLowerCase();
|
|
274
|
+
const hit = manifest.patterns.find((p) => p.name === key || p.selector === `kp-${key}`);
|
|
275
|
+
if (!hit)
|
|
276
|
+
return { error: `No pattern named "${raw}". Call list_patterns to see available names.` };
|
|
277
|
+
if (!hit.figma) {
|
|
278
|
+
return { error: `Pattern "${hit.name}" exists but has no Figma node mapping.` };
|
|
279
|
+
}
|
|
280
|
+
return JSON.stringify({
|
|
281
|
+
pattern: hit.name,
|
|
282
|
+
package: hit.package,
|
|
283
|
+
fileKey: figma.fileKey,
|
|
284
|
+
nodeId: hit.figma.nodeId,
|
|
285
|
+
url: nodeUrl(figma.fileKey, hit.figma.nodeId),
|
|
286
|
+
}, null, 2);
|
|
287
|
+
},
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: 'figma_for_icon',
|
|
291
|
+
title: 'Figma context for the icon library',
|
|
292
|
+
description: 'Returns the Tabler Icon Pack file context — separate from the main Design System file. The Kanso Icon component wraps an instance from this library, so any glyph the assistant might want to insert lives there. Pass `query` to receive a search hint the assistant feeds into Figma MCP\'s search_design_system tool.',
|
|
293
|
+
inputSchema: {
|
|
294
|
+
type: 'object',
|
|
295
|
+
properties: {
|
|
296
|
+
query: { type: 'string', description: 'Optional icon name or concept (e.g. "search", "trash"). The response includes a searchQuery + library key the agent can use with Figma MCP\'s search_design_system.' },
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
handler: (args) => {
|
|
300
|
+
if (!figma?.iconLibrary)
|
|
301
|
+
return { error: 'Icon library mapping is not configured.' };
|
|
302
|
+
const query = args.query ? String(args.query).trim() : null;
|
|
303
|
+
return JSON.stringify({
|
|
304
|
+
fileKey: figma.iconLibrary.fileKey,
|
|
305
|
+
libraryName: figma.iconLibrary.name,
|
|
306
|
+
url: `https://www.figma.com/design/${figma.iconLibrary.fileKey}/${encodeURIComponent(figma.iconLibrary.name)}`,
|
|
307
|
+
suggestedSearchQuery: query,
|
|
308
|
+
relatedFigmaTools: [
|
|
309
|
+
{ tool: 'search_design_system', purpose: 'Find the icon by name across the Tabler Icon Pack.' },
|
|
310
|
+
{ tool: 'get_libraries', purpose: 'Confirm the library is subscribed in the target file.' },
|
|
311
|
+
],
|
|
312
|
+
usageNote: 'In Kanso, every icon slot expects an instance of an Icon-component wrapper, not a raw Tabler glyph. Find the Tabler glyph here, then wrap it via the Kanso `kp-icon` component (see get_component name="icon" if it exists, otherwise the icon slot in the parent component\'s docs).',
|
|
313
|
+
}, null, 2);
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
];
|
|
317
|
+
// ─── Wire up the server ─────────────────────────────────────────────────
|
|
318
|
+
const server = new Server({ name: 'kanso-protocol', version: manifest.version }, { capabilities: { tools: {} } });
|
|
319
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
320
|
+
tools: tools.map(({ name, title, description, inputSchema }) => ({
|
|
321
|
+
name,
|
|
322
|
+
title,
|
|
323
|
+
description,
|
|
324
|
+
inputSchema,
|
|
325
|
+
})),
|
|
326
|
+
}));
|
|
327
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
328
|
+
const tool = tools.find((t) => t.name === req.params.name);
|
|
329
|
+
if (!tool) {
|
|
330
|
+
return {
|
|
331
|
+
content: [{ type: 'text', text: `Unknown tool: ${req.params.name}` }],
|
|
332
|
+
isError: true,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
const args = (req.params.arguments ?? {});
|
|
336
|
+
const result = tool.handler(args);
|
|
337
|
+
if (typeof result === 'object' && result && 'error' in result) {
|
|
338
|
+
return { content: [{ type: 'text', text: result.error }], isError: true };
|
|
339
|
+
}
|
|
340
|
+
return { content: [{ type: 'text', text: result }] };
|
|
341
|
+
});
|
|
342
|
+
const transport = new StdioServerTransport();
|
|
343
|
+
await server.connect(transport);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": "Bridge from Kanso component / pattern / token names to Figma node IDs in the Design System library. Generated initially via the Figma MCP — keep in sync when components are added or renamed in Figma. Each `nodeId` can be passed to Figma MCP tools (get_design_context, get_screenshot) along with the fileKey.",
|
|
3
|
+
"fileKey": "ahRfe4BdMAyoK0I3lnicp6",
|
|
4
|
+
"fileName": "Design-System",
|
|
5
|
+
"url": "https://www.figma.com/design/ahRfe4BdMAyoK0I3lnicp6/Design-System",
|
|
6
|
+
"pages": {
|
|
7
|
+
"components": { "id": "0:1", "name": "🧩 Components" },
|
|
8
|
+
"patterns": { "id": "3612:2", "name": "📐 Patterns" },
|
|
9
|
+
"foundations": { "id": "6:4", "name": "🎨 Foundations" },
|
|
10
|
+
"examples": { "id": "3753:2", "name": "🖼️ Example Pages" },
|
|
11
|
+
"assets": { "id": "17:2", "name": "⚙️ Assets" },
|
|
12
|
+
"manifest": { "id": "6:3", "name": "📋 Manifest" },
|
|
13
|
+
"cover": { "id": "6:2", "name": "📖 Cover" }
|
|
14
|
+
},
|
|
15
|
+
"iconLibrary": {
|
|
16
|
+
"fileKey": "0bgkes9nJYbk0vonduKaVZ",
|
|
17
|
+
"name": "Tabler Icon Pack",
|
|
18
|
+
"$comment": "Single source of icon glyphs for Kanso. The Icon component wraps an instance from this library."
|
|
19
|
+
},
|
|
20
|
+
"components": {
|
|
21
|
+
"button": "3805:10993",
|
|
22
|
+
"input": "3805:10997",
|
|
23
|
+
"checkbox": "3805:11001",
|
|
24
|
+
"radio": "3805:11005",
|
|
25
|
+
"toggle": "3805:11009",
|
|
26
|
+
"form-field": "3805:11016",
|
|
27
|
+
"menu": "3805:11020",
|
|
28
|
+
"textarea": "3805:11024",
|
|
29
|
+
"select": "3805:11028",
|
|
30
|
+
"number-stepper": "3805:11032",
|
|
31
|
+
"segmented-control": "3805:11036",
|
|
32
|
+
"badge": "3805:11040",
|
|
33
|
+
"alert": "3805:11044",
|
|
34
|
+
"tooltip": "3805:11048",
|
|
35
|
+
"popover": "3805:11052",
|
|
36
|
+
"progress": "3805:11056",
|
|
37
|
+
"tabs": "3805:11068",
|
|
38
|
+
"breadcrumbs": "3805:11072",
|
|
39
|
+
"pagination": "3805:11076",
|
|
40
|
+
"dialog": "3805:11083",
|
|
41
|
+
"divider": "3805:11087",
|
|
42
|
+
"card": "3805:11091",
|
|
43
|
+
"empty-state": "3805:11095",
|
|
44
|
+
"drawer": "3805:11099",
|
|
45
|
+
"accordion": "3805:11103",
|
|
46
|
+
"skeleton": "3805:11107",
|
|
47
|
+
"slider": "3805:11111",
|
|
48
|
+
"combobox": "3805:11115",
|
|
49
|
+
"datepicker": "3805:11119",
|
|
50
|
+
"timepicker": "3805:11123",
|
|
51
|
+
"toast": "3805:11127",
|
|
52
|
+
"table": "3805:11131",
|
|
53
|
+
"tree": "3805:11135",
|
|
54
|
+
"avatar": "3805:11139",
|
|
55
|
+
"avatar-group": "3805:11139",
|
|
56
|
+
"rich-text-editor": "3849:11648"
|
|
57
|
+
},
|
|
58
|
+
"patterns": {
|
|
59
|
+
"app-shell": "3822:7265",
|
|
60
|
+
"banner": "3822:7272",
|
|
61
|
+
"container": "3822:7280",
|
|
62
|
+
"filter-bar": "3822:7288",
|
|
63
|
+
"form-section": "3822:7296",
|
|
64
|
+
"grid": "3822:7304",
|
|
65
|
+
"header": "3822:7312",
|
|
66
|
+
"nav-item": "3822:7320",
|
|
67
|
+
"notification-center": "3822:7327",
|
|
68
|
+
"page-error": "3822:7334",
|
|
69
|
+
"page-header": "3822:7341",
|
|
70
|
+
"search-bar": "3822:7356",
|
|
71
|
+
"settings-panel": "3822:7364",
|
|
72
|
+
"sidebar": "3822:7372",
|
|
73
|
+
"stat-card": "3822:7387",
|
|
74
|
+
"table-toolbar": "3822:7395",
|
|
75
|
+
"theme-toggle": "3822:7403",
|
|
76
|
+
"user-menu": "3822:7411",
|
|
77
|
+
"stack": "3825:7265",
|
|
78
|
+
"row": "3825:7273"
|
|
79
|
+
},
|
|
80
|
+
"foundations": {
|
|
81
|
+
"typography": "3043:2",
|
|
82
|
+
"colors": "3051:2",
|
|
83
|
+
"spacing": "3055:2",
|
|
84
|
+
"sizing": "3056:2",
|
|
85
|
+
"radius": "3057:2",
|
|
86
|
+
"elevation": "3881:2",
|
|
87
|
+
"overlays": "3881:30",
|
|
88
|
+
"foreground-on-dark": "3881:73",
|
|
89
|
+
"motion": "3881:93"
|
|
90
|
+
}
|
|
91
|
+
}
|