@nonbot/mcp 0.3.1 → 0.3.3

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.
@@ -0,0 +1,20 @@
1
+ export interface EdgeLibrary {
2
+ id: string;
3
+ name: string;
4
+ path: 'curve' | 'circuit';
5
+ hierarchyWidth: number;
6
+ dependencyWidth: number;
7
+ ink: 'gradient' | 'solid';
8
+ color?: string;
9
+ pads: boolean;
10
+ corner: number;
11
+ }
12
+ export declare const NONBOT_DEFAULT: EdgeLibrary;
13
+ export declare const NONBOT_CIRCUIT_BOARD: EdgeLibrary;
14
+ export declare const BUILT_IN_EDGE_LIBRARIES: Record<string, EdgeLibrary>;
15
+ export declare function safeEdgeLibrary(x: unknown): EdgeLibrary | null;
16
+ export declare function resolveEdgeLibraryArg(arg: unknown): {
17
+ library: EdgeLibrary | null;
18
+ } | {
19
+ error: string;
20
+ };
@@ -0,0 +1,44 @@
1
+ export const NONBOT_DEFAULT = {
2
+ id: 'nonbot-default', name: 'non.bot default', path: 'curve', hierarchyWidth: 3, dependencyWidth: 2, ink: 'gradient', pads: false, corner: 0,
3
+ };
4
+ export const NONBOT_CIRCUIT_BOARD = {
5
+ id: 'nonbot-circuit-board', name: 'NON.BOT Circuit Board', path: 'circuit', hierarchyWidth: 2, dependencyWidth: 1.5, ink: 'solid', color: '#C8894A', pads: true, corner: 14,
6
+ };
7
+ export const BUILT_IN_EDGE_LIBRARIES = {
8
+ [NONBOT_DEFAULT.id]: NONBOT_DEFAULT,
9
+ [NONBOT_CIRCUIT_BOARD.id]: NONBOT_CIRCUIT_BOARD,
10
+ };
11
+ const ID_RE = /^[a-z0-9][a-z0-9:_-]{0,63}$/;
12
+ const HEX_RE = /^#[0-9a-fA-F]{6}$/;
13
+ const clamp = (v, min, max) => typeof v === 'number' && Number.isFinite(v) ? Math.round(Math.min(max, Math.max(min, v)) * 2) / 2 : null;
14
+ export function safeEdgeLibrary(x) {
15
+ if (!x || typeof x !== 'object')
16
+ return null;
17
+ const o = x;
18
+ if (typeof o.id !== 'string' || !ID_RE.test(o.id) || typeof o.name !== 'string' || !o.name.trim())
19
+ return null;
20
+ if (o.path !== 'curve' && o.path !== 'circuit')
21
+ return null;
22
+ if (o.ink !== 'gradient' && o.ink !== 'solid')
23
+ return null;
24
+ const hierarchyWidth = clamp(o.hierarchyWidth, 1, 8);
25
+ const dependencyWidth = clamp(o.dependencyWidth, 1, 6);
26
+ if (hierarchyWidth === null || dependencyWidth === null)
27
+ return null;
28
+ const color = typeof o.color === 'string' && HEX_RE.test(o.color) ? o.color.toUpperCase() : undefined;
29
+ if (o.ink === 'solid' && !color)
30
+ return null;
31
+ return {
32
+ id: o.id, name: o.name.trim().slice(0, 48), path: o.path, hierarchyWidth, dependencyWidth, ink: o.ink,
33
+ ...(color ? { color } : {}), pads: o.pads === true, corner: Math.round(clamp(o.corner ?? 0, 0, 40) ?? 0),
34
+ };
35
+ }
36
+ export function resolveEdgeLibraryArg(arg) {
37
+ const lib = typeof arg === 'string' ? BUILT_IN_EDGE_LIBRARIES[arg] ?? null : safeEdgeLibrary(arg);
38
+ if (!lib) {
39
+ return { error: typeof arg === 'string'
40
+ ? `Unknown edge library "${arg}". Built in: ${Object.keys(BUILT_IN_EDGE_LIBRARIES).join(', ')} — or pass a whole library object (a WhyStore item's \`library\`).`
41
+ : 'edge_library is not a whole library: needs id, name, path (curve|circuit), hierarchyWidth 1–8, dependencyWidth 1–6, ink (gradient|solid; solid needs color #RRGGBB), pads, corner 0–40.' };
42
+ }
43
+ return { library: lib.id === NONBOT_DEFAULT.id ? null : lib };
44
+ }
@@ -73,6 +73,10 @@ export function renderPreviewSvg(nodes, opts = {}) {
73
73
  const stroke = big ? '#7367F0' : done ? '#9BCFAF' : '#3E3860';
74
74
  const fs = big ? 26 : lv === 'feature' || lv === 'key-result' ? 21 : 18;
75
75
  parts.push(`<rect x="${x}" y="${y}" width="${s.width}" height="${s.height}" rx="10" fill="${fill}" stroke="${stroke}" stroke-width="${big ? 4 : 2}"/>`);
76
+ if (lv === 'milestone') {
77
+ parts.push(`<text x="${x + 10}" y="${y + Math.round(s.height / 2) + 5}" font-size="13" font-weight="600" fill="${ink}">${esc(fit(String(n.data?.title ?? '').trim(), s.width - 20, 13))}</text>`);
78
+ continue;
79
+ }
76
80
  parts.push(`<text x="${x + 14}" y="${y + 24}" font-size="13" fill="${done ? '#4E8A66' : '#8F87B5'}" letter-spacing="1">${esc(lv.toUpperCase())}${done ? ' · DONE' : n.data?.state ? ' · ' + esc(String(n.data.state).toUpperCase()) : ''}</text>`);
77
81
  parts.push(`<text x="${x + 14}" y="${y + 28 + fs * 1.3}" font-size="${fs}" font-weight="${big ? 700 : 500}" fill="${ink}">${esc(fit(String(n.data?.title ?? '').trim(), s.width - 28, fs))}</text>`);
78
82
  }
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { ApiContext } from './config.js';
3
3
  export declare const SERVER_NAME = "nonbot-mcp";
4
- export declare const SERVER_VERSION = "0.3.1";
4
+ export declare const SERVER_VERSION = "0.3.3";
5
5
  export declare const INSTRUCTIONS: string;
6
6
  export declare function registerAll(server: McpServer, ctx: ApiContext): void;
7
7
  export declare function createNonbotMcpServer(ctx: ApiContext): McpServer;
@@ -10,7 +10,7 @@ import { registerRadbugsTools } from './toolsets/radbugs.js';
10
10
  import { registerResources } from './resources.js';
11
11
  import { registerPrompts } from './prompts.js';
12
12
  export const SERVER_NAME = 'nonbot-mcp';
13
- export const SERVER_VERSION = '0.3.1';
13
+ export const SERVER_VERSION = '0.3.3';
14
14
  export const INSTRUCTIONS = [
15
15
  'non.bot MCP — work against the caller’s real product-management data, not a copy.',
16
16
  'Two domains: PORTFOLIOS (the SAFe portfolio canvas — epics → features → stories) and',
@@ -7,6 +7,7 @@ import { layoutReleaseLane } from '../releases.js';
7
7
  import { emptyIp, IP_KINDS, IP_NODE_SIZE } from '../ipShape.js';
8
8
  import { withMapHeaders } from '../mapHeaders.js';
9
9
  import { renderPreviewSvg } from '../preview.js';
10
+ import { resolveEdgeLibraryArg } from '../edgeLibraries.js';
10
11
  import { applyStyleRules, CARD_STYLES, TITLE_SIZES, ACCENTS, SHOW_KEYS, STATES as STYLE_STATES } from '../style.js';
11
12
  const STATES = ['funnel', 'reviewing', 'analyzing', 'ready', 'implementing', 'done'];
12
13
  const OKR_STATUSES = ['on-track', 'at-risk', 'behind'];
@@ -424,17 +425,21 @@ export function registerBuildTools(server, ctx) {
424
425
  });
425
426
  server.registerTool('portfolio_set_meta', {
426
427
  title: 'Set portfolio settings',
427
- description: 'Graph-level settings that travel with the portfolio (the `__graph_meta__` record): `context_text` (the portfolio\'s context notes — what a newcomer reads first), `palette` ({ id, name, swatches: [{hex, label?}] ≤ 10, roles?: {epic, feature, story, …: hex | "p:N"} }), `canvas_style`, `sprint_settings`. Only the keys you pass change; everything else in the record is kept.',
428
+ description: 'Graph-level settings that travel with the portfolio (the `__graph_meta__` record): `context_text` (the portfolio\'s context notes — what a newcomer reads first), `palette` ({ id, name, swatches: [{hex, label?}] ≤ 10, roles?: {epic, feature, story, …: hex | "p:N"} }), `canvas_style`, `sprint_settings`, `edge_library` (how the wires are drawn: "nonbot-default" — the canvas as it always was — or "nonbot-circuit-board" — NON.BOT Circuit Board, copper traces with 45° corners, pads and vias — or a whole library object from the WhyStore Edges catalog). Only the keys you pass change; everything else in the record is kept.',
428
429
  inputSchema: {
429
430
  portfolio_id: z.string(),
430
431
  context_text: z.string().max(20000).optional(),
431
432
  palette: z.object({ id: z.string().min(1).max(64), name: z.string().min(1).max(64), swatches: z.array(z.object({ hex: z.string().regex(/^#[0-9a-fA-F]{6}$/), label: z.string().max(32).optional() })).max(10), roles: z.record(z.string(), z.string()).optional() }).optional(),
432
433
  canvas_style: z.record(z.string(), z.unknown()).optional(),
433
434
  sprint_settings: z.record(z.string(), z.unknown()).optional(),
435
+ edge_library: z.union([z.string().max(64), z.record(z.string(), z.unknown())]).optional(),
434
436
  expected_version: z.number().int().optional(),
435
437
  },
436
438
  annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
437
439
  }, async (a) => {
440
+ const edge = a.edge_library === undefined ? undefined : resolveEdgeLibraryArg(a.edge_library);
441
+ if (edge && 'error' in edge)
442
+ return fail(edge.error);
438
443
  const g = await loadGraph(ctx, a.portfolio_id);
439
444
  const cur = g.nodes.find(n => n.id === '__graph_meta__' || n.type === 'graph-meta');
440
445
  const data = { ...(cur?.data ?? {}) };
@@ -444,6 +449,14 @@ export function registerBuildTools(server, ctx) {
444
449
  data.palette = { ...a.palette, swatches: a.palette.swatches.map(s => ({ ...s, hex: s.hex.toUpperCase() })), roles: a.palette.roles ?? {} };
445
450
  if (a.canvas_style)
446
451
  data.canvasStyle = { ...(data.canvasStyle ?? {}), ...a.canvas_style };
452
+ if (edge) {
453
+ const cs = { ...(data.canvasStyle ?? {}) };
454
+ if (edge.library)
455
+ cs.edgeLibrary = edge.library;
456
+ else
457
+ delete cs.edgeLibrary;
458
+ data.canvasStyle = cs;
459
+ }
447
460
  if (a.sprint_settings)
448
461
  data.sprintSettings = { ...(data.sprintSettings ?? {}), ...a.sprint_settings };
449
462
  const meta = { id: '__graph_meta__', type: 'graph-meta', position: { x: 0, y: 0 }, data };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nonbot/mcp",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "type": "module",
5
5
  "description": "The non.bot MCP server — one server for your portfolios, decision graph, and Runs. Runs over stdio (Claude Code, Cursor, VS Code) with a pat_ token. Built on MCP protocol 2025-11-25.",
6
6
  "keywords": [