@linktr.ee/arbor-mcp 0.1.2 → 0.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.
Files changed (3) hide show
  1. package/README.md +58 -22
  2. package/dist/index.js +5816 -78
  3. package/package.json +4 -3
package/README.md CHANGED
@@ -8,39 +8,52 @@ Claude Desktop, Cursor, Zed, etc.).
8
8
 
9
9
  Currently registers a single tool:
10
10
 
11
- ### `arbor.open_in_playroom`
11
+ ### `arbor_open_in_playroom`
12
+
13
+ > The legacy name `arbor.open_in_playroom` remains registered as a deprecated
14
+ > alias (it routes to the same handler) and will be removed in the next major.
15
+ > Prefer the snake_case name.
12
16
 
13
17
  Resolves an Arbor PascalCase component name into a Playroom share URL with
14
18
  the canonical default snippet pre-loaded.
15
19
 
16
- **Input:**
20
+ **Input** (validated by a strict Zod schema — unknown keys are rejected):
17
21
 
18
22
  ```json
19
23
  { "componentName": "Button" }
20
24
  ```
21
25
 
22
- **Output:** a `text` content item containing a JSON-stringified
23
- `CanonicalLookupResult`:
26
+ **Output:** `structuredContent` matching the tool's `outputSchema` (the machine
27
+ payload), plus a concise one-line `text` summary (the human mirror — it does
28
+ **not** duplicate the full JSON):
24
29
 
25
30
  ```json
26
31
  {
27
32
  "coverage": "mapped",
28
33
  "arborComponentName": "Button",
29
34
  "snippet": "<Button>Save changes</Button>",
30
- "url": "https://arbor.linktr.ee/playroom/#?code=PEJ1dHRvbj5TYXZlIGNoYW5nZXM8L0J1dHRvbj4="
35
+ "url": "https://arbor.linktr.ee/playroom/?slug=flowering-sapling-glen-5",
36
+ "source": "minted",
37
+ "slug": "flowering-sapling-glen-5"
31
38
  }
32
39
  ```
33
40
 
34
- `coverage` is one of:
41
+ A `mapped` result carries the pretty `?slug=` URL when slug minting succeeds
42
+ (`source: "minted"` or `"cached"`). If minting is unavailable it degrades to a
43
+ self-contained lz-string `#?code=…` URL with `degraded: true` and a `reason`,
44
+ and no `slug`.
45
+
46
+ `coverage` (for this by-name tool) is one of:
35
47
 
36
48
  - `mapped` — the component appears in `figma-mappings.ts` AND has a
37
49
  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
50
  - `not_found` — the URL points at the Playroom homepage; agents should
42
51
  surface this to the human rather than silently following the link.
43
52
 
53
+ > A third coverage, `fallback` (best-effort fuzzy name matching), exists only
54
+ > on the Figma plugin's selection-driven node-ID path. This by-name MCP tool
55
+ > never returns it — its `outputSchema` is `mapped | not_found`.
56
+
44
57
  ## Install
45
58
 
46
59
  The package is published publicly to npm as `@linktr.ee/arbor-mcp`. The
@@ -48,8 +61,13 @@ expected use is `npx`-style spawning by an MCP client — see the wiring
48
61
  section below. No global install required.
49
62
 
50
63
  ```bash
51
- # Smoke-check the latest release
52
- npx -y @linktr.ee/arbor-mcp --help 2>&1 | head -5
64
+ # Smoke-check the latest release. The server speaks JSON-RPC over stdio (there
65
+ # is no --help flag); pipe a handshake in so stdin closes and the process
66
+ # exits. Expect a tools/list response naming arbor_open_in_playroom.
67
+ printf '%s\n%s\n' \
68
+ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"smoke","version":"0"}}}' \
69
+ '{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
70
+ | npx -y @linktr.ee/arbor-mcp
53
71
  ```
54
72
 
55
73
  If you're working inside the Arbor monorepo, build the local source
@@ -101,7 +119,7 @@ For portability across clients that don't substitute path variables
101
119
 
102
120
  ### Verify the connection
103
121
 
104
- After restarting your MCP client, the `arbor.open_in_playroom` tool should
122
+ After restarting your MCP client, the `arbor_open_in_playroom` tool should
105
123
  appear in the available-tools list. Ask the agent something like:
106
124
 
107
125
  > Use the arbor MCP server to open Button in Playroom.
@@ -115,8 +133,9 @@ The agent will call the tool and return the URL.
115
133
  # iterating on tool handlers.
116
134
  yarn workspace @linktr.ee/arbor-mcp dev
117
135
 
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).
136
+ # Run the tests: the in-memory Client handshake + protocol contract
137
+ # (server.test.ts) plus tool metadata, input validation, lookup
138
+ # happy/not-found paths, and the figma-mappings coverage gate.
120
139
  yarn workspace @linktr.ee/arbor-mcp test
121
140
 
122
141
  # Smoke-test the full JSON-RPC handshake end-to-end.
@@ -129,20 +148,29 @@ printf '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion
129
148
  packages/arbor-mcp/
130
149
  ├── src/
131
150
  │ ├── index.ts # entrypoint: connects stdio transport
132
- │ ├── server.ts # creates the Server + registers handlers
151
+ │ ├── server.ts # creates the McpServer + registers ARBOR_TOOLS
133
152
  │ └── tools/
134
- └── open-in-playroom.ts # tool metadata + dispatch function
153
+ ├── registry.ts # ArborTool convention + registerArborTool
154
+ │ └── open-in-playroom.ts # tool: schemas, annotations, handler
135
155
  ├── tests/
156
+ │ ├── server.test.ts # in-memory Client handshake + contract
136
157
  │ └── open-in-playroom.test.ts # node --test, validation + coverage
137
158
  ├── build.mjs # esbuild bundle to dist/index.js
138
159
  └── tsconfig.json # rootDir: ../.. for cross-package imports
139
160
  ```
140
161
 
141
- The `arbor.open_in_playroom` handler delegates to `lookupByArborName` in
162
+ The `arbor_open_in_playroom` handler delegates to `lookupByArborName` in
142
163
  `apps/playroom/src/canonical-snippet-lookup.ts` — the same module the
143
164
  Figma plugin's "Open in Playroom" relaunch button uses. This keeps the
144
165
  agent-facing surface and the designer-facing surface in lockstep.
145
166
 
167
+ The server is built on the SDK's high-level `McpServer` + `registerTool`
168
+ (the modern, non-deprecated API). Every tool is an `ArborTool` registry
169
+ object (`tools/registry.ts`): a `.strict()` Zod `inputSchema`, a Zod
170
+ `outputSchema` (the handler returns matching `structuredContent`),
171
+ annotation hints, and a total handler. Input-validation and handler errors
172
+ surface as `isError: true` results, never protocol rejections.
173
+
146
174
  ## Why bundle vs. emit `tsc` files?
147
175
 
148
176
  `tsc` would emit one `.js` per `.ts` and preserve cross-package directory
@@ -155,9 +183,17 @@ runs in the build script for type checking.
155
183
 
156
184
  ## Future tools
157
185
 
158
- When adding a second tool, follow the existing pattern:
186
+ When adding a second tool, follow the registry convention:
187
+
188
+ 1. New file under `src/tools/<name>.ts` exporting an `ArborTool` object
189
+ (see `tools/registry.ts`): `name`, optional deprecated `aliases`, a
190
+ `config` with `title` / `description` / `.strict()` Zod `inputSchema` /
191
+ Zod `outputSchema` / `annotations`, and a total `handler` returning
192
+ `{ content, structuredContent }`.
193
+ 2. Add it to the `ARBOR_TOOLS` array in `src/server.ts`.
194
+ 3. Mirror the test file shape under `tests/` (drive it through the in-memory
195
+ `Client` as in `server.test.ts`, plus pure-handler unit tests).
159
196
 
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/`.
197
+ Pass `inputSchema` as a `z.object({...}).strict()` (a ZodObject, **not** a
198
+ raw shape) the SDK only preserves `.strict()` rejection of unknown keys
199
+ for a ZodObject; a raw shape is rebuilt non-strict and silently strips them.