@linktr.ee/arbor-mcp 0.1.2 → 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.
Files changed (3) hide show
  1. package/README.md +105 -23
  2. package/dist/index.js +6052 -77
  3. package/package.json +4 -3
package/README.md CHANGED
@@ -6,41 +6,100 @@ Claude Desktop, Cursor, Zed, etc.).
6
6
 
7
7
  ## What it does
8
8
 
9
- Currently registers a single tool:
9
+ Registers a set of read-only Arbor design system tools. The
10
+ component-resolution tool is documented in detail below; the DS-as-data
11
+ metadata tools (component inventory, single-component lookup, version
12
+ history) are described under [DS-as-data tools](#ds-as-data-tools).
10
13
 
11
- ### `arbor.open_in_playroom`
14
+ ### `arbor_open_in_playroom`
15
+
16
+ > The legacy name `arbor.open_in_playroom` remains registered as a deprecated
17
+ > alias (it routes to the same handler) and will be removed in the next major.
18
+ > Prefer the snake_case name.
12
19
 
13
20
  Resolves an Arbor PascalCase component name into a Playroom share URL with
14
21
  the canonical default snippet pre-loaded.
15
22
 
16
- **Input:**
23
+ **Input** (validated by a strict Zod schema — unknown keys are rejected):
17
24
 
18
25
  ```json
19
26
  { "componentName": "Button" }
20
27
  ```
21
28
 
22
- **Output:** a `text` content item containing a JSON-stringified
23
- `CanonicalLookupResult`:
29
+ **Output:** `structuredContent` matching the tool's `outputSchema` (the machine
30
+ payload), plus a concise one-line `text` summary (the human mirror — it does
31
+ **not** duplicate the full JSON):
24
32
 
25
33
  ```json
26
34
  {
27
35
  "coverage": "mapped",
28
36
  "arborComponentName": "Button",
29
37
  "snippet": "<Button>Save changes</Button>",
30
- "url": "https://arbor.linktr.ee/playroom/#?code=PEJ1dHRvbj5TYXZlIGNoYW5nZXM8L0J1dHRvbj4="
38
+ "url": "https://arbor.linktr.ee/playroom/?slug=flowering-sapling-glen-5",
39
+ "source": "minted",
40
+ "slug": "flowering-sapling-glen-5"
31
41
  }
32
42
  ```
33
43
 
34
- `coverage` is one of:
44
+ A `mapped` result carries the pretty `?slug=` URL when slug minting succeeds
45
+ (`source: "minted"` or `"cached"`). If minting is unavailable it degrades to a
46
+ self-contained lz-string `#?code=…` URL with `degraded: true` and a `reason`,
47
+ and no `slug`.
48
+
49
+ `coverage` (for this by-name tool) is one of:
35
50
 
36
51
  - `mapped` — the component appears in `figma-mappings.ts` AND has a
37
52
  canonical entry in `compositions.ts`. High confidence.
38
- - `fallback` — best-effort name matching (e.g. "Header Bar" → `HeaderBar`).
39
- Used by the Figma plugin's selection-driven path; the by-name MCP tool
40
- almost always returns `mapped` or `not_found`.
41
53
  - `not_found` — the URL points at the Playroom homepage; agents should
42
54
  surface this to the human rather than silently following the link.
43
55
 
56
+ > A third coverage, `fallback` (best-effort fuzzy name matching), exists only
57
+ > on the Figma plugin's selection-driven node-ID path. This by-name MCP tool
58
+ > never returns it — its `outputSchema` is `mapped | not_found`.
59
+
60
+ ## DS-as-data tools
61
+
62
+ Three read-only metadata tools (shipping in a forthcoming release) expose the
63
+ design system as queryable data. They answer "what components / versions exist"
64
+ — **not** token values. (For token values, read the compiled CSS in
65
+ `packages/design-tokens/dist/web/` or the shadcn `registry.json`, which are
66
+ fresher.)
67
+
68
+ - **`arbor_list_versions`** — design system version history: each entry is
69
+ `{ id, name, createdAt, readOnly }`.
70
+ - **`arbor_list_components`** — component inventory: `name`, `id`, and a short
71
+ `description` per component. Returns top-level components only (Figma variant
72
+ instances are filtered out). Accepts an optional case-insensitive `query`
73
+ string that filters by name or description.
74
+ - **`arbor_get_component`** — one component's metadata, looked up by `name`
75
+ (case-insensitive) or `id`. Returns `{ found, component }`.
76
+
77
+ (A fourth tool, `arbor_get_docs`, is deliberately **not** built yet — deferred
78
+ until a docs page-content route exists.)
79
+
80
+ ### Runtime config + graceful degradation
81
+
82
+ These tools read the design system's metadata from an internal Lambda
83
+ federation proxy. Two env vars configure the connection — distributed
84
+ internally through the same channel as `FIGMA_ACCESS_TOKEN`:
85
+
86
+ | Variable | Purpose |
87
+ | --------------------------------- | -------------------------------------------------------- |
88
+ | `ARBOR_MCP_SUPERNOVA_PROXY_URL` | Full proxy URL, including the `/supernova/query` path |
89
+ | `ARBOR_MCP_SUPERNOVA_PROXY_TOKEN` | Shared inbound bearer token presented to the proxy |
90
+
91
+ When either var is unset — or the proxy is unreachable — the tools **degrade
92
+ gracefully**: they return a successful result with `source: "unavailable"` and
93
+ a `detail` string explaining why. They never throw and never crash the server.
94
+ An agent should treat `"unavailable"` as "couldn't reach the data" rather than
95
+ "the design system has no components/versions."
96
+
97
+ > **Metadata, not token values.** These tools surface Supernova *metadata*
98
+ > (component names, ids, descriptions, version history) only. They deliberately
99
+ > do **not** return token values — those live in the compiled CSS
100
+ > (`packages/design-tokens/dist/web/`) and the shadcn `registry.json`, which are
101
+ > the fresher source of truth.
102
+
44
103
  ## Install
45
104
 
46
105
  The package is published publicly to npm as `@linktr.ee/arbor-mcp`. The
@@ -48,8 +107,13 @@ expected use is `npx`-style spawning by an MCP client — see the wiring
48
107
  section below. No global install required.
49
108
 
50
109
  ```bash
51
- # Smoke-check the latest release
52
- npx -y @linktr.ee/arbor-mcp --help 2>&1 | head -5
110
+ # Smoke-check the latest release. The server speaks JSON-RPC over stdio (there
111
+ # is no --help flag); pipe a handshake in so stdin closes and the process
112
+ # exits. Expect a tools/list response naming arbor_open_in_playroom.
113
+ printf '%s\n%s\n' \
114
+ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \
115
+ '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
116
+ | npx -y @linktr.ee/arbor-mcp
53
117
  ```
54
118
 
55
119
  If you're working inside the Arbor monorepo, build the local source
@@ -101,7 +165,7 @@ For portability across clients that don't substitute path variables
101
165
 
102
166
  ### Verify the connection
103
167
 
104
- After restarting your MCP client, the `arbor.open_in_playroom` tool should
168
+ After restarting your MCP client, the `arbor_open_in_playroom` tool should
105
169
  appear in the available-tools list. Ask the agent something like:
106
170
 
107
171
  > Use the arbor MCP server to open Button in Playroom.
@@ -115,8 +179,9 @@ The agent will call the tool and return the URL.
115
179
  # iterating on tool handlers.
116
180
  yarn workspace @linktr.ee/arbor-mcp dev
117
181
 
118
- # Run the unit tests (covers tool metadata, input validation, lookup
119
- # happy/not-found paths, and the 9-of-9 figma-mappings coverage gate).
182
+ # Run the tests: the in-memory Client handshake + protocol contract
183
+ # (server.test.ts) plus tool metadata, input validation, lookup
184
+ # happy/not-found paths, and the figma-mappings coverage gate.
120
185
  yarn workspace @linktr.ee/arbor-mcp test
121
186
 
122
187
  # Smoke-test the full JSON-RPC handshake end-to-end.
@@ -129,20 +194,29 @@ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion
129
194
  packages/arbor-mcp/
130
195
  ├── src/
131
196
  │ ├── index.ts # entrypoint: connects stdio transport
132
- │ ├── server.ts # creates the Server + registers handlers
197
+ │ ├── server.ts # creates the McpServer + registers ARBOR_TOOLS
133
198
  │ └── tools/
134
- └── open-in-playroom.ts # tool metadata + dispatch function
199
+ ├── registry.ts # ArborTool convention + registerArborTool
200
+ │ └── open-in-playroom.ts # tool: schemas, annotations, handler
135
201
  ├── tests/
202
+ │ ├── server.test.ts # in-memory Client handshake + contract
136
203
  │ └── open-in-playroom.test.ts # node --test, validation + coverage
137
204
  ├── build.mjs # esbuild bundle to dist/index.js
138
205
  └── tsconfig.json # rootDir: ../.. for cross-package imports
139
206
  ```
140
207
 
141
- The `arbor.open_in_playroom` handler delegates to `lookupByArborName` in
208
+ The `arbor_open_in_playroom` handler delegates to `lookupByArborName` in
142
209
  `apps/playroom/src/canonical-snippet-lookup.ts` — the same module the
143
210
  Figma plugin's "Open in Playroom" relaunch button uses. This keeps the
144
211
  agent-facing surface and the designer-facing surface in lockstep.
145
212
 
213
+ The server is built on the SDK's high-level `McpServer` + `registerTool`
214
+ (the modern, non-deprecated API). Every tool is an `ArborTool` registry
215
+ object (`tools/registry.ts`): a `.strict()` Zod `inputSchema`, a Zod
216
+ `outputSchema` (the handler returns matching `structuredContent`),
217
+ annotation hints, and a total handler. Input-validation and handler errors
218
+ surface as `isError: true` results, never protocol rejections.
219
+
146
220
  ## Why bundle vs. emit `tsc` files?
147
221
 
148
222
  `tsc` would emit one `.js` per `.ts` and preserve cross-package directory
@@ -155,9 +229,17 @@ runs in the build script for type checking.
155
229
 
156
230
  ## Future tools
157
231
 
158
- When adding a second tool, follow the existing pattern:
232
+ When adding a second tool, follow the registry convention:
233
+
234
+ 1. New file under `src/tools/<name>.ts` exporting an `ArborTool` object
235
+ (see `tools/registry.ts`): `name`, optional deprecated `aliases`, a
236
+ `config` with `title` / `description` / `.strict()` Zod `inputSchema` /
237
+ Zod `outputSchema` / `annotations`, and a total `handler` returning
238
+ `{ content, structuredContent }`.
239
+ 2. Add it to the `ARBOR_TOOLS` array in `src/server.ts`.
240
+ 3. Mirror the test file shape under `tests/` (drive it through the in-memory
241
+ `Client` as in `server.test.ts`, plus pure-handler unit tests).
159
242
 
160
- 1. New file under `src/tools/<name>.ts` exporting `TOOL_NAME`,
161
- `TOOL_DESCRIPTION`, `TOOL_INPUT_SCHEMA`, and a `run<Name>` handler.
162
- 2. Register it in `src/server.ts` alongside the existing entry.
163
- 3. Mirror the test file shape under `tests/`.
243
+ Pass `inputSchema` as a `z.object({...}).strict()` (a ZodObject, **not** a
244
+ raw shape) the SDK only preserves `.strict()` rejection of unknown keys
245
+ for a ZodObject; a raw shape is rebuilt non-strict and silently strips them.