wayfinder-mcp 0.1.0 → 0.1.2

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 +11 -9
  2. package/package.json +1 -1
  3. package/server.js +17 -1
package/README.md CHANGED
@@ -29,7 +29,10 @@ the plan lives in `~/.wayfinder/wayfinder-plan.json`, the bridge binds
29
29
  JSON-RPC over stdin/stdout. No network, no auth dance.
30
30
  - **Tools** are named functions with a JSON schema: `get_summary`,
31
31
  `get_plan`, `add_node`, `update_node`, `remove_node`, `connect`,
32
- `disconnect`, `complete_node`, `set_text`, `replace_plan`.
32
+ `disconnect`, `complete_node`, `set_text`, `replace_plan`, `layout`.
33
+ New nodes are auto-placed (after their prerequisites, nudged until free,
34
+ so siblings never stack), and `layout` arranges the whole map by depth —
35
+ agents should call it after building a fresh map.
33
36
  - **Validation is shared** with the site: `graph.js` is a byte-identical copy
34
37
  of `wayfinder/dist/graph.js` (enforced by `npm run check`), so agent edits
35
38
  obey the same rules as the UI (version 1, ≤2000 nodes, ≤10000 edges,
@@ -40,17 +43,16 @@ the plan lives in `~/.wayfinder/wayfinder-plan.json`, the bridge binds
40
43
  (`WAYFINDER_NO_BRIDGE=1` disables it). It answers Chrome's private-network
41
44
  preflight so the **deployed site** can reach each visitor's own bridge.
42
45
 
43
- ## For the site owner: publish once
46
+ ## For the site owner: releasing a new version
44
47
 
45
- `wayfinder-mcp` is free on npm at the time of writing. One-time, from `mcp/`:
48
+ Releases are manual (two commands, ~2 minutes). From `mcp/`:
46
49
 
47
- ```sh
48
- npm login
49
- npm publish --access public
50
- ```
50
+ 1. Bump `version` in `mcp/package.json`.
51
+ 2. Create a short-lived granular token: npmjs.com → Access Tokens → Generate New Token → Granular, scoped to `wayfinder-mcp`, Read + Write.
52
+ 3. `npm config set //registry.npmjs.org/:_authToken 'paste-token-here' && npm publish --access public`
53
+ 4. Revoke the token on the website and clear it locally: `npm config delete //registry.npmjs.org/:_authToken`
51
54
 
52
- Then redeploy the site so the dialog's `npx -y wayfinder-mcp` snippet resolves.
53
- Bump `version` in `mcp/package.json` for later releases (`0.1.0` now).
55
+ (Browser `npm login` tokens cannot publish — always use a granular token.)
54
56
 
55
57
  ## For developers (repo clone)
56
58
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wayfinder-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Local MCP server + loopback bridge so AI agents can read and edit Wayfinder life-map DAGs",
5
5
  "license": "MIT",
6
6
  "repository": {
package/server.js CHANGED
@@ -19,7 +19,7 @@ import {
19
19
  CallToolRequestSchema,
20
20
  ListToolsRequestSchema,
21
21
  } from '@modelcontextprotocol/sdk/types.js';
22
- import { validatePlan, canConnect } from './graph.js';
22
+ import { validatePlan, canConnect, layoutNodes } from './graph.js';
23
23
  import { resolvePlanPath } from './paths.js';
24
24
  import { loadPlan, startBridge } from './bridge.js';
25
25
 
@@ -141,6 +141,11 @@ const TOOLS = [
141
141
  properties: { plan: { type: 'object', description: 'Full version-1 plan JSON' } },
142
142
  },
143
143
  },
144
+ {
145
+ name: 'layout',
146
+ description: 'Auto-arrange every node by graph depth (same as the site Arrange button). Call after building or importing a fresh map so nothing overlaps.',
147
+ inputSchema: { type: 'object', properties: {} },
148
+ },
144
149
  ];
145
150
 
146
151
  const server = new Server({ name: 'wayfinder', version: '1.0.0' }, { capabilities: { tools: {} } });
@@ -180,7 +185,12 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
180
185
  const ps = nodes.filter((n) => parents.includes(n.id));
181
186
  node.x = Math.max(...ps.map((p) => p.x)) + 270;
182
187
  node.y = ps.reduce((t, p) => t + p.y, 0) / ps.length;
188
+ } else {
189
+ // First free grid slot, so parentless nodes never stack on each other.
190
+ node.x = 55; node.y = 55;
183
191
  }
192
+ // Nudge down until free: siblings sharing parents would otherwise overlap exactly.
193
+ while (nodes.some((n) => Math.abs(n.x - node.x) < 235 && Math.abs(n.y - node.y) < 165)) node.y += 184;
184
194
  return text({ ok: true, node: savePlan({ ...plan, nodes, edges }).nodes.find((n) => n.id === id) });
185
195
  }
186
196
 
@@ -252,6 +262,12 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
252
262
  return text({ ok: true, plan: savePlan(args.plan) });
253
263
  }
254
264
 
265
+ if (name === 'layout') {
266
+ layoutNodes(plan.nodes, plan.edges);
267
+ savePlan(plan);
268
+ return text({ ok: true, nodes: plan.nodes.length });
269
+ }
270
+
255
271
  throw new Error(`unknown tool ${name}`);
256
272
  } catch (err) {
257
273
  return { content: [{ type: 'text', text: `Error: ${err.message}` }], isError: true };