@nonbot/mcp 0.0.0-stage → 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.
- package/LICENSE +43 -0
- package/README.md +249 -2
- package/dist/core/config.d.ts +9 -0
- package/dist/core/config.js +34 -0
- package/dist/core/http.d.ts +3 -0
- package/dist/core/http.js +20 -0
- package/dist/core/ipShape.d.ts +7 -0
- package/dist/core/ipShape.js +45 -0
- package/dist/core/layout.d.ts +38 -0
- package/dist/core/layout.js +193 -0
- package/dist/core/prompts.d.ts +3 -0
- package/dist/core/prompts.js +45 -0
- package/dist/core/resources.d.ts +3 -0
- package/dist/core/resources.js +23 -0
- package/dist/core/server.d.ts +7 -0
- package/dist/core/server.js +46 -0
- package/dist/core/toolsets/build.d.ts +105 -0
- package/dist/core/toolsets/build.js +367 -0
- package/dist/core/toolsets/ip.d.ts +3 -0
- package/dist/core/toolsets/ip.js +130 -0
- package/dist/core/toolsets/monitor.d.ts +3 -0
- package/dist/core/toolsets/monitor.js +160 -0
- package/dist/core/toolsets/portfolio.d.ts +3 -0
- package/dist/core/toolsets/portfolio.js +521 -0
- package/dist/core/toolsets/run.d.ts +3 -0
- package/dist/core/toolsets/run.js +202 -0
- package/dist/core/toolsets/whygraph.d.ts +3 -0
- package/dist/core/toolsets/whygraph.js +346 -0
- package/dist/core/validate.d.ts +21 -0
- package/dist/core/validate.js +45 -0
- package/dist/stdio.d.ts +2 -0
- package/dist/stdio.js +72 -0
- package/package.json +65 -4
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { IP_NODE_SIZE } from './ipShape.js';
|
|
2
|
+
export const PX_PER_WEEK = 120;
|
|
3
|
+
const WEEK_MS = 7 * 24 * 60 * 60 * 1000;
|
|
4
|
+
const ITEM_TOP_HANDLE_X = 8;
|
|
5
|
+
const LANE_GAP_X = 24;
|
|
6
|
+
const ROW_GAP = 32;
|
|
7
|
+
const BLOCK_GAP = 120;
|
|
8
|
+
export const LEVELS = ['objective', 'key-result', 'epic', 'feature', 'story', 'enabler', 'milestone'];
|
|
9
|
+
const DEFAULT_DURATION = { epic: 8, feature: 4, story: 1, enabler: 2, milestone: 0, objective: 12, 'key-result': 12 };
|
|
10
|
+
const DEFAULT_WIDTH = { epic: 280, feature: 260, story: 240, enabler: 240, milestone: 120, objective: 280, 'key-result': 260 };
|
|
11
|
+
const MIN_PAINT = { epic: 360, feature: 360, story: 352, enabler: 352 };
|
|
12
|
+
const HEIGHT = { epic: 265, feature: 265, story: 220, enabler: 220, milestone: 54, objective: 200, 'key-result': 220 };
|
|
13
|
+
const HERO_EXTRA = 150;
|
|
14
|
+
export function calendarStart(now) {
|
|
15
|
+
const s = new Date(now.getFullYear(), now.getMonth() - 3, 1);
|
|
16
|
+
const d = s.getDay();
|
|
17
|
+
s.setDate(s.getDate() + (d === 0 ? -6 : 1 - d));
|
|
18
|
+
return s;
|
|
19
|
+
}
|
|
20
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
21
|
+
export const isoDate = (d) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
22
|
+
function parseIso(s) {
|
|
23
|
+
if (typeof s !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(s))
|
|
24
|
+
return null;
|
|
25
|
+
const out = new Date(Number(s.slice(0, 4)), Number(s.slice(5, 7)) - 1, Number(s.slice(8, 10)));
|
|
26
|
+
return Number.isNaN(out.getTime()) ? null : out;
|
|
27
|
+
}
|
|
28
|
+
export function columnOf(date, now) {
|
|
29
|
+
return Math.round((date.getTime() - calendarStart(now).getTime()) / WEEK_MS);
|
|
30
|
+
}
|
|
31
|
+
export function dateAtColumn(col, now) {
|
|
32
|
+
const d = calendarStart(now);
|
|
33
|
+
d.setDate(d.getDate() + Math.round(col * 7));
|
|
34
|
+
return d;
|
|
35
|
+
}
|
|
36
|
+
export function todayColumn(now) {
|
|
37
|
+
return Math.max(0, Math.floor((now.getTime() - calendarStart(now).getTime()) / WEEK_MS));
|
|
38
|
+
}
|
|
39
|
+
export function sprintAtColumn(col) {
|
|
40
|
+
const c = Math.max(0, col);
|
|
41
|
+
const L = ['S1', 'S2', 'S3', 'S4', 'S5', 'IP'];
|
|
42
|
+
return { pi: `PI-${Math.floor(c / 12) + 1}`, sprint: L[Math.floor((c % 12) / 2)] ?? 'S1' };
|
|
43
|
+
}
|
|
44
|
+
export function scheduleFields(col, weeks, now) {
|
|
45
|
+
const s = sprintAtColumn(col);
|
|
46
|
+
const e = sprintAtColumn(col + Math.round(weeks));
|
|
47
|
+
return {
|
|
48
|
+
durationWeeks: weeks,
|
|
49
|
+
startDate: isoDate(dateAtColumn(col, now)),
|
|
50
|
+
endDate: isoDate(dateAtColumn(col + weeks, now)),
|
|
51
|
+
pi: s.pi, sprint: s.sprint, endPi: e.pi, endSprint: e.sprint,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
export const levelOf = (n) => LEVELS.includes(n.data?.hierarchyLevel) ? n.data.hierarchyLevel : 'story';
|
|
55
|
+
export const isItem = (n) => n.type === 'portfolio-item';
|
|
56
|
+
export const isIpNode = (n) => n.type === 'rad-module' && n.data?.subtype === 'ip';
|
|
57
|
+
export function layoutTimeline(nodes, opts = {}) {
|
|
58
|
+
const now = opts.now ?? new Date();
|
|
59
|
+
const heights = opts.heights ?? {};
|
|
60
|
+
const items = nodes.filter(isItem);
|
|
61
|
+
const byId = new Map(items.map(n => [n.id, n]));
|
|
62
|
+
const positions = new Map();
|
|
63
|
+
const widthOverride = new Map();
|
|
64
|
+
const geo = new Map();
|
|
65
|
+
for (const n of items) {
|
|
66
|
+
const level = levelOf(n);
|
|
67
|
+
const start = parseIso(n.data?.startDate);
|
|
68
|
+
const col = start ? columnOf(start, now) : Math.round((n.position?.x ?? 0) / PX_PER_WEEK);
|
|
69
|
+
const weeks = typeof n.data?.durationWeeks === 'number' ? n.data.durationWeeks : DEFAULT_DURATION[level];
|
|
70
|
+
const natural = weeks > 0 ? weeks * PX_PER_WEEK : DEFAULT_WIDTH[level];
|
|
71
|
+
const min = MIN_PAINT[level] ?? 0;
|
|
72
|
+
widthOverride.set(n.id, natural < min ? min : null);
|
|
73
|
+
const hero = Array.isArray(n.data?.pastedImages) && n.data.pastedImages.length > 0;
|
|
74
|
+
geo.set(n.id, { x: col * PX_PER_WEEK, width: Math.max(natural, min), height: heights[n.id] ?? HEIGHT[level] + (hero && (level === 'epic' || level === 'feature') ? HERO_EXTRA : 0) });
|
|
75
|
+
}
|
|
76
|
+
const kids = new Map();
|
|
77
|
+
for (const n of items) {
|
|
78
|
+
const p = n.data?.parentItemId;
|
|
79
|
+
if (p && geo.has(p))
|
|
80
|
+
kids.set(p, [...(kids.get(p) ?? []), n.id]);
|
|
81
|
+
}
|
|
82
|
+
const right = new Map();
|
|
83
|
+
const paintedRight = (id, depth = 0) => {
|
|
84
|
+
if (right.has(id))
|
|
85
|
+
return right.get(id);
|
|
86
|
+
const g = geo.get(id);
|
|
87
|
+
let r = g.x + g.width;
|
|
88
|
+
if (depth < 12)
|
|
89
|
+
for (const k of kids.get(id) ?? [])
|
|
90
|
+
r = Math.max(r, paintedRight(k, depth + 1) + 20);
|
|
91
|
+
right.set(id, r);
|
|
92
|
+
return r;
|
|
93
|
+
};
|
|
94
|
+
for (const [id, g] of geo)
|
|
95
|
+
g.width = paintedRight(id) - g.x;
|
|
96
|
+
const epicOf = (n) => {
|
|
97
|
+
let cur = n;
|
|
98
|
+
for (let i = 0; cur && i < 12; i++) {
|
|
99
|
+
if (levelOf(cur) === 'epic')
|
|
100
|
+
return cur.id;
|
|
101
|
+
cur = cur.data?.parentItemId ? byId.get(cur.data.parentItemId) : undefined;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
};
|
|
105
|
+
const placeBand = (slots, top) => {
|
|
106
|
+
const lanes = [];
|
|
107
|
+
const laneOf = new Map();
|
|
108
|
+
for (const s of slots) {
|
|
109
|
+
const free = (i) => !lanes[i] || lanes[i].spans.every(([a, b]) => s.x + s.width + LANE_GAP_X <= a || b + LANE_GAP_X <= s.x);
|
|
110
|
+
const parentLane = s.from !== undefined ? laneOf.get(s.from) : undefined;
|
|
111
|
+
let l = 0;
|
|
112
|
+
if (parentLane !== undefined && free(parentLane))
|
|
113
|
+
l = parentLane;
|
|
114
|
+
else
|
|
115
|
+
while (!free(l))
|
|
116
|
+
l++;
|
|
117
|
+
if (!lanes[l])
|
|
118
|
+
lanes[l] = { spans: [], height: 0 };
|
|
119
|
+
lanes[l].spans.push([s.x, s.x + s.width]);
|
|
120
|
+
lanes[l].height = Math.max(lanes[l].height, s.height);
|
|
121
|
+
laneOf.set(s.id, l);
|
|
122
|
+
}
|
|
123
|
+
const laneY = [];
|
|
124
|
+
let y = top;
|
|
125
|
+
lanes.forEach((lane, i) => { laneY[i] = y; y += lane.height + 24; });
|
|
126
|
+
for (const s of slots)
|
|
127
|
+
positions.set(s.id, { x: s.x, y: laneY[laneOf.get(s.id)] });
|
|
128
|
+
return lanes.length ? y - 24 + ROW_GAP : top;
|
|
129
|
+
};
|
|
130
|
+
const slot = (id, from) => ({ id, ...geo.get(id), ...(from ? { from } : {}) });
|
|
131
|
+
const byX = (a, b) => geo.get(a.id).x - geo.get(b.id).x || a.id.localeCompare(b.id);
|
|
132
|
+
let y = 0;
|
|
133
|
+
for (const o of items.filter(n => levelOf(n) === 'objective').sort(byX))
|
|
134
|
+
y = placeBand([slot(o.id)], y);
|
|
135
|
+
y = placeBand(items.filter(n => levelOf(n) === 'key-result').sort(byX).map(n => slot(n.id)), y);
|
|
136
|
+
const ipNodes = nodes.filter(isIpNode);
|
|
137
|
+
const ipFirstSource = new Map();
|
|
138
|
+
const ipUnplaced = [];
|
|
139
|
+
for (const n of ipNodes) {
|
|
140
|
+
const src = (n.data?.attachedItemIds ?? []).find((id) => geo.has(id));
|
|
141
|
+
if (src)
|
|
142
|
+
ipFirstSource.set(n.id, src);
|
|
143
|
+
else
|
|
144
|
+
ipUnplaced.push(n.id);
|
|
145
|
+
}
|
|
146
|
+
const epics = items.filter(n => levelOf(n) === 'epic').sort(byX);
|
|
147
|
+
const rest = items.filter(n => !['objective', 'key-result', 'epic'].includes(levelOf(n)));
|
|
148
|
+
for (const epic of [...epics.map(e => e.id), null]) {
|
|
149
|
+
const inBlock = rest.filter(n => epicOf(n) === epic);
|
|
150
|
+
const blockIds = new Set([...(epic ? [epic] : []), ...inBlock.map(n => n.id)]);
|
|
151
|
+
const ips = ipNodes.filter(n => blockIds.has(ipFirstSource.get(n.id) ?? ''));
|
|
152
|
+
if (!epic && !inBlock.length && !ips.length)
|
|
153
|
+
continue;
|
|
154
|
+
if (y > 0)
|
|
155
|
+
y += BLOCK_GAP - ROW_GAP;
|
|
156
|
+
if (epic)
|
|
157
|
+
y = placeBand([slot(epic)], y);
|
|
158
|
+
if (ips.length) {
|
|
159
|
+
const src = (id) => geo.get(ipFirstSource.get(id));
|
|
160
|
+
y = placeBand(ips.map(n => ({ id: n.id, x: src(n.id).x + ITEM_TOP_HANDLE_X - IP_NODE_SIZE.width / 2, width: IP_NODE_SIZE.width, height: IP_NODE_SIZE.height })).sort((a, b) => a.x - b.x), y) - ROW_GAP + 16;
|
|
161
|
+
}
|
|
162
|
+
const features = inBlock.filter(n => levelOf(n) === 'feature').sort(byX);
|
|
163
|
+
const leaves = inBlock.filter(n => levelOf(n) === 'story' || levelOf(n) === 'enabler').sort(byX);
|
|
164
|
+
const underFeature = (f) => leaves.filter(n => n.data?.parentItemId === f);
|
|
165
|
+
const pool = features.map(f => slot(f.id));
|
|
166
|
+
for (const f of features)
|
|
167
|
+
pool.push(...underFeature(f.id).map(n => slot(n.id, f.id)));
|
|
168
|
+
const featureIds = new Set(features.map(f => f.id));
|
|
169
|
+
pool.push(...leaves.filter(n => !featureIds.has(n.data?.parentItemId)).map(n => slot(n.id)));
|
|
170
|
+
pool.push(...inBlock.filter(n => levelOf(n) === 'milestone').sort(byX).map(n => slot(n.id)));
|
|
171
|
+
y = placeBand(pool, y);
|
|
172
|
+
}
|
|
173
|
+
ipUnplaced.forEach((id, k) => positions.set(id, { x: k * (IP_NODE_SIZE.width + LANE_GAP_X), y: y + BLOCK_GAP }));
|
|
174
|
+
return { positions, widthOverride };
|
|
175
|
+
}
|
|
176
|
+
export function applyLayout(nodes, res) {
|
|
177
|
+
return nodes.map(n => {
|
|
178
|
+
const p = res.positions.get(n.id);
|
|
179
|
+
if (!p)
|
|
180
|
+
return n;
|
|
181
|
+
const out = { ...n, position: { x: Math.round(p.x), y: Math.round(p.y) } };
|
|
182
|
+
if (res.widthOverride.has(n.id)) {
|
|
183
|
+
const data = { ...(n.data ?? {}) };
|
|
184
|
+
const w = res.widthOverride.get(n.id);
|
|
185
|
+
if (w)
|
|
186
|
+
data.manualWidthOverride = w;
|
|
187
|
+
else
|
|
188
|
+
delete data.manualWidthOverride;
|
|
189
|
+
out.data = data;
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export function registerPrompts(server, _ctx) {
|
|
3
|
+
server.registerPrompt('wsjf_prioritize', {
|
|
4
|
+
title: 'WSJF-prioritize a portfolio',
|
|
5
|
+
description: 'Rank a portfolio’s items by Weighted Shortest Job First.',
|
|
6
|
+
argsSchema: { portfolioId: z.string().describe('Portfolio id (from portfolio_list).') },
|
|
7
|
+
}, ({ portfolioId }) => ({
|
|
8
|
+
messages: [
|
|
9
|
+
{
|
|
10
|
+
role: 'user',
|
|
11
|
+
content: {
|
|
12
|
+
type: 'text',
|
|
13
|
+
text: [
|
|
14
|
+
`Load portfolio "${portfolioId}" with the portfolio_get tool.`,
|
|
15
|
+
'For each epic and feature, estimate the four WSJF inputs on a relative 1–10 scale:',
|
|
16
|
+
'User/Business Value, Time Criticality, Risk Reduction & Opportunity Enablement, and Job Size.',
|
|
17
|
+
'Compute WSJF = (Value + Time Criticality + Risk/Opportunity) / Job Size.',
|
|
18
|
+
'Return a table ranked by WSJF (highest first) showing each input and the score,',
|
|
19
|
+
'then recommend the top 5 to pull next, one line of justification each.',
|
|
20
|
+
].join(' '),
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
}));
|
|
25
|
+
server.registerPrompt('decision_check', {
|
|
26
|
+
title: 'Check a plan against WhyGraph rules',
|
|
27
|
+
description: 'Evaluate a plan or decision against the team’s committed decisions and LAWs.',
|
|
28
|
+
argsSchema: { plan: z.string().describe('The plan, change, or decision to check.') },
|
|
29
|
+
}, ({ plan }) => ({
|
|
30
|
+
messages: [
|
|
31
|
+
{
|
|
32
|
+
role: 'user',
|
|
33
|
+
content: {
|
|
34
|
+
type: 'text',
|
|
35
|
+
text: [
|
|
36
|
+
`Call whygraph_context with task="${plan}" to load the LAWs and prior decisions that apply.`,
|
|
37
|
+
'Evaluate the plan against each. Treat a conflict with a LAW (locked decision) as a blocker;',
|
|
38
|
+
'treat tension with a lower-weight decision as a caution. Cite the decision id for every point.',
|
|
39
|
+
'If nothing conflicts, say so explicitly and name the decisions you checked against.',
|
|
40
|
+
].join(' '),
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { api, expectJson } from './http.js';
|
|
3
|
+
export function registerResources(server, ctx) {
|
|
4
|
+
server.registerResource('portfolio', new ResourceTemplate('portfolio://{id}', { list: undefined }), {
|
|
5
|
+
title: 'Portfolio graph',
|
|
6
|
+
description: 'The full node/edge graph for one portfolio as JSON. Read portfolio://<id> (id from portfolio_list) to load it directly as context — no tool call needed.',
|
|
7
|
+
mimeType: 'application/json',
|
|
8
|
+
}, async (uri, variables) => {
|
|
9
|
+
const id = String(variables.id);
|
|
10
|
+
const res = await api(ctx, `/api/portfolio/graph?id=${encodeURIComponent(id)}`);
|
|
11
|
+
const data = await expectJson(res, 'portfolio resource');
|
|
12
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }] };
|
|
13
|
+
});
|
|
14
|
+
server.registerResource('whygraph-decisions', 'whygraph://decisions', {
|
|
15
|
+
title: 'WhyGraph decisions',
|
|
16
|
+
description: 'Every PM decision plus the RELATED_TO relationships between them, as JSON.',
|
|
17
|
+
mimeType: 'application/json',
|
|
18
|
+
}, async (uri) => {
|
|
19
|
+
const res = await api(ctx, '/api/whygraph/decisions');
|
|
20
|
+
const data = await expectJson(res, 'whygraph resource');
|
|
21
|
+
return { contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }] };
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import type { ApiContext } from './config.js';
|
|
3
|
+
export declare const SERVER_NAME = "nonbot-mcp";
|
|
4
|
+
export declare const SERVER_VERSION = "0.2.0";
|
|
5
|
+
export declare const INSTRUCTIONS: string;
|
|
6
|
+
export declare function registerAll(server: McpServer, ctx: ApiContext): void;
|
|
7
|
+
export declare function createNonbotMcpServer(ctx: ApiContext): McpServer;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { registerPortfolioTools } from './toolsets/portfolio.js';
|
|
3
|
+
import { registerIpTools } from './toolsets/ip.js';
|
|
4
|
+
import { registerBuildTools } from './toolsets/build.js';
|
|
5
|
+
import { registerWhygraphTools } from './toolsets/whygraph.js';
|
|
6
|
+
import { registerRunTools } from './toolsets/run.js';
|
|
7
|
+
import { registerMonitorTools } from './toolsets/monitor.js';
|
|
8
|
+
import { registerResources } from './resources.js';
|
|
9
|
+
import { registerPrompts } from './prompts.js';
|
|
10
|
+
export const SERVER_NAME = 'nonbot-mcp';
|
|
11
|
+
export const SERVER_VERSION = '0.2.0';
|
|
12
|
+
export const INSTRUCTIONS = [
|
|
13
|
+
'non.bot MCP — work against the caller’s real product-management data, not a copy.',
|
|
14
|
+
'Two domains: PORTFOLIOS (the SAFe portfolio canvas — epics → features → stories) and',
|
|
15
|
+
'WHYGRAPH (the PM decision graph — the rules and decisions the team has committed to).',
|
|
16
|
+
'Read before you write: call the *_list / *_get / *_search tools first.',
|
|
17
|
+
'BUILDING: portfolio_create makes an empty portfolio; portfolio_add_items adds items of any level',
|
|
18
|
+
'(parents / OKR links / dependencies by id or by a key from the same call) and lays the Timeline out;',
|
|
19
|
+
'portfolio_patch_item changes one item; portfolio_layout re-lays everything; portfolio_set_meta sets',
|
|
20
|
+
'context text and palette; portfolio_asset_upload stores an image; ip_create adds an IP node over its sources.',
|
|
21
|
+
'Whole-graph rewrites (restructures, deletes): portfolio_get for the graph + version, then portfolio_update',
|
|
22
|
+
'(pass expectedVersion for safe concurrent editing).',
|
|
23
|
+
'Before a non-trivial decision, call whygraph_context to surface the LAWs and prior',
|
|
24
|
+
'decisions that apply. WhyGraph is read/reasoning-only over this transport.',
|
|
25
|
+
'MONITORING: run_feed / run_daemon_status / run_machines / run_prompts_list answer',
|
|
26
|
+
'"what are my runs and daemon doing?"; run_snapshot_request + run_snapshot_get read a',
|
|
27
|
+
'live run’s terminal output; run_prompt_answer unblocks a waiting run (supervisor',
|
|
28
|
+
'tokens with the explicit runs:respond scope only).',
|
|
29
|
+
'IP NODES: ip_list / ip_get read a patent or trademark disclosure; ip_update edits it (op list + expected_version);',
|
|
30
|
+
'ip_evidence_add adds evidence the SERVER hashes, timestamps and numbers — never send a hash or a time.',
|
|
31
|
+
].join(' ');
|
|
32
|
+
export function registerAll(server, ctx) {
|
|
33
|
+
registerPortfolioTools(server, ctx);
|
|
34
|
+
registerBuildTools(server, ctx);
|
|
35
|
+
registerIpTools(server, ctx);
|
|
36
|
+
registerWhygraphTools(server, ctx);
|
|
37
|
+
registerRunTools(server, ctx);
|
|
38
|
+
registerMonitorTools(server, ctx);
|
|
39
|
+
registerResources(server, ctx);
|
|
40
|
+
registerPrompts(server, ctx);
|
|
41
|
+
}
|
|
42
|
+
export function createNonbotMcpServer(ctx) {
|
|
43
|
+
const server = new McpServer({ name: SERVER_NAME, version: SERVER_VERSION }, { instructions: INSTRUCTIONS });
|
|
44
|
+
registerAll(server, ctx);
|
|
45
|
+
return server;
|
|
46
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import type { ApiContext } from '../config.js';
|
|
4
|
+
import { type LayoutNode } from '../layout.js';
|
|
5
|
+
type Node = LayoutNode & {
|
|
6
|
+
id: string;
|
|
7
|
+
type?: string;
|
|
8
|
+
data?: Record<string, any>;
|
|
9
|
+
};
|
|
10
|
+
type Edge = {
|
|
11
|
+
id: string;
|
|
12
|
+
source: string;
|
|
13
|
+
target: string;
|
|
14
|
+
[k: string]: unknown;
|
|
15
|
+
};
|
|
16
|
+
interface Graph {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
nodes: Node[];
|
|
20
|
+
edges: Edge[];
|
|
21
|
+
viewport: unknown;
|
|
22
|
+
version?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare const toHtml: (s: string) => string;
|
|
25
|
+
export declare function newItemId(now?: number): string;
|
|
26
|
+
declare const itemSpec: z.ZodObject<{
|
|
27
|
+
key: z.ZodOptional<z.ZodString>;
|
|
28
|
+
level: z.ZodEnum<["objective", "key-result", "epic", "feature", "story", "enabler", "milestone"]>;
|
|
29
|
+
title: z.ZodString;
|
|
30
|
+
description: z.ZodOptional<z.ZodString>;
|
|
31
|
+
bluf: z.ZodOptional<z.ZodString>;
|
|
32
|
+
acceptance_criteria: z.ZodOptional<z.ZodString>;
|
|
33
|
+
hypothesis: z.ZodOptional<z.ZodString>;
|
|
34
|
+
state: z.ZodOptional<z.ZodEnum<["funnel", "reviewing", "analyzing", "ready", "implementing", "done"]>>;
|
|
35
|
+
parent: z.ZodOptional<z.ZodString>;
|
|
36
|
+
okr: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
37
|
+
depends_on: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
38
|
+
start_week: z.ZodOptional<z.ZodNumber>;
|
|
39
|
+
start_date: z.ZodOptional<z.ZodString>;
|
|
40
|
+
duration_weeks: z.ZodOptional<z.ZodNumber>;
|
|
41
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
42
|
+
wsjf: z.ZodOptional<z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], null>>;
|
|
43
|
+
owner: z.ZodOptional<z.ZodString>;
|
|
44
|
+
kr_target: z.ZodOptional<z.ZodString>;
|
|
45
|
+
kr_current: z.ZodOptional<z.ZodString>;
|
|
46
|
+
okr_status: z.ZodOptional<z.ZodEnum<["on-track", "at-risk", "behind"]>>;
|
|
47
|
+
images: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
48
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
49
|
+
}, "strip", z.ZodTypeAny, {
|
|
50
|
+
title: string;
|
|
51
|
+
level: "epic" | "objective" | "key-result" | "feature" | "story" | "enabler" | "milestone";
|
|
52
|
+
parent?: string | undefined;
|
|
53
|
+
description?: string | undefined;
|
|
54
|
+
acceptance_criteria?: string | undefined;
|
|
55
|
+
hypothesis?: string | undefined;
|
|
56
|
+
owner?: string | undefined;
|
|
57
|
+
key?: string | undefined;
|
|
58
|
+
bluf?: string | undefined;
|
|
59
|
+
state?: "done" | "funnel" | "reviewing" | "analyzing" | "ready" | "implementing" | undefined;
|
|
60
|
+
okr?: string[] | undefined;
|
|
61
|
+
depends_on?: string[] | undefined;
|
|
62
|
+
start_week?: number | undefined;
|
|
63
|
+
start_date?: string | undefined;
|
|
64
|
+
duration_weeks?: number | undefined;
|
|
65
|
+
tags?: string[] | undefined;
|
|
66
|
+
wsjf?: [number, number, number, number] | undefined;
|
|
67
|
+
kr_target?: string | undefined;
|
|
68
|
+
kr_current?: string | undefined;
|
|
69
|
+
okr_status?: "on-track" | "at-risk" | "behind" | undefined;
|
|
70
|
+
images?: string[] | undefined;
|
|
71
|
+
fields?: Record<string, unknown> | undefined;
|
|
72
|
+
}, {
|
|
73
|
+
title: string;
|
|
74
|
+
level: "epic" | "objective" | "key-result" | "feature" | "story" | "enabler" | "milestone";
|
|
75
|
+
parent?: string | undefined;
|
|
76
|
+
description?: string | undefined;
|
|
77
|
+
acceptance_criteria?: string | undefined;
|
|
78
|
+
hypothesis?: string | undefined;
|
|
79
|
+
owner?: string | undefined;
|
|
80
|
+
key?: string | undefined;
|
|
81
|
+
bluf?: string | undefined;
|
|
82
|
+
state?: "done" | "funnel" | "reviewing" | "analyzing" | "ready" | "implementing" | undefined;
|
|
83
|
+
okr?: string[] | undefined;
|
|
84
|
+
depends_on?: string[] | undefined;
|
|
85
|
+
start_week?: number | undefined;
|
|
86
|
+
start_date?: string | undefined;
|
|
87
|
+
duration_weeks?: number | undefined;
|
|
88
|
+
tags?: string[] | undefined;
|
|
89
|
+
wsjf?: [number, number, number, number] | undefined;
|
|
90
|
+
kr_target?: string | undefined;
|
|
91
|
+
kr_current?: string | undefined;
|
|
92
|
+
okr_status?: "on-track" | "at-risk" | "behind" | undefined;
|
|
93
|
+
images?: string[] | undefined;
|
|
94
|
+
fields?: Record<string, unknown> | undefined;
|
|
95
|
+
}>;
|
|
96
|
+
type ItemSpec = z.infer<typeof itemSpec>;
|
|
97
|
+
export declare function buildItems(g: Graph, specs: ItemSpec[], now?: Date): {
|
|
98
|
+
nodes: Node[];
|
|
99
|
+
edges: Edge[];
|
|
100
|
+
ids: Record<string, string>;
|
|
101
|
+
errors: string[];
|
|
102
|
+
patched: Map<string, Record<string, any>>;
|
|
103
|
+
};
|
|
104
|
+
export declare function registerBuildTools(server: McpServer, ctx: ApiContext): void;
|
|
105
|
+
export {};
|