@nimara-app/mcp 0.6.0 → 0.8.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/README.md +3 -1
- package/dist/index.js +316 -165
- package/dist/index.js.map +4 -4
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
9
9
|
// package.json
|
|
10
10
|
var package_default = {
|
|
11
11
|
name: "@nimara-app/mcp",
|
|
12
|
-
version: "0.
|
|
12
|
+
version: "0.8.0",
|
|
13
13
|
private: false,
|
|
14
14
|
mcpName: "io.github.langskip-studios/nimara",
|
|
15
15
|
description: "Nimara MCP server \u2014 AI tool integration for Claude, Codex, and others",
|
|
@@ -20,7 +20,8 @@ var package_default = {
|
|
|
20
20
|
"nimara-mcp": "dist/index.js"
|
|
21
21
|
},
|
|
22
22
|
files: [
|
|
23
|
-
"dist",
|
|
23
|
+
"dist/index.js",
|
|
24
|
+
"dist/index.js.map",
|
|
24
25
|
"README.md"
|
|
25
26
|
],
|
|
26
27
|
keywords: [
|
|
@@ -45,7 +46,8 @@ var package_default = {
|
|
|
45
46
|
dev: "node build.mjs",
|
|
46
47
|
start: "node dist/index.js",
|
|
47
48
|
typecheck: "tsc --noEmit",
|
|
48
|
-
prepublishOnly: "npm run typecheck && npm run build"
|
|
49
|
+
prepublishOnly: "npm run typecheck && npm run build",
|
|
50
|
+
test: "vitest run"
|
|
49
51
|
},
|
|
50
52
|
dependencies: {
|
|
51
53
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
@@ -53,10 +55,15 @@ var package_default = {
|
|
|
53
55
|
zod: "^3.23.0"
|
|
54
56
|
},
|
|
55
57
|
devDependencies: {
|
|
58
|
+
"@clerk/hono": "^0.1.79",
|
|
59
|
+
"@clerk/mcp-tools": "^0.6.0",
|
|
56
60
|
"@nimara/convex": "workspace:*",
|
|
57
61
|
"@types/node": "^20.0.0",
|
|
58
62
|
esbuild: "^0.23.0",
|
|
59
|
-
|
|
63
|
+
hono: "^4.13.8",
|
|
64
|
+
typescript: "^5.0.0",
|
|
65
|
+
vitest: "^2.1.9",
|
|
66
|
+
wrangler: "4.132.0"
|
|
60
67
|
},
|
|
61
68
|
engines: {
|
|
62
69
|
node: ">=20"
|
|
@@ -74,13 +81,33 @@ A status must be one of the project's \`statuses\` from list_projects, spelled e
|
|
|
74
81
|
|
|
75
82
|
When you pick up an item: update_work_item { status: <in-progress status>, agentWorking: true, model: <the model you run as> }. That lights its row for 15 minutes and lets the app say whose agent, and which, is on it; any write or read of the item renews it, and on a long stretch with no Nimara calls, heartbeat_work_item every few minutes keeps it lit. Post a comment at real milestones. If you stop early, agentWorking: false. Moving to the last status clears it.
|
|
76
83
|
|
|
77
|
-
|
|
84
|
+
People are userIds from list_project_members: mention one in a comment as [@Name](user:<userId>), or hand them the item with update_work_item's assigneeId / reviewerId; each notifies them. Append agent research to a description under the exact heading "## Research (auto)", never editing the human text above it. When you open a PR, put the displayId in its title and it links itself; unlink_pr removes a PR that landed on the wrong item.`;
|
|
78
85
|
|
|
79
86
|
// src/convex.ts
|
|
80
87
|
import { ConvexHttpClient } from "convex/browser";
|
|
88
|
+
import { ConvexError } from "convex/values";
|
|
81
89
|
var cachedClient = null;
|
|
90
|
+
function describeConvexError(err) {
|
|
91
|
+
if (err instanceof ConvexError) {
|
|
92
|
+
const data = err.data;
|
|
93
|
+
if (typeof data === "string" && data.trim()) return data;
|
|
94
|
+
if (typeof data === "object" && data !== null) {
|
|
95
|
+
const { code, message } = data;
|
|
96
|
+
if ((code === "user" || code === "forbidden") && typeof message === "string" && message.trim()) {
|
|
97
|
+
return message;
|
|
98
|
+
}
|
|
99
|
+
if (code === "unauthenticated") return "This token is not valid. Generate a new one in Nimara \u2192 Settings \u2192 Personal Access Tokens.";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
103
|
+
const requestId = /\[Request ID: ([^\]]+)\]/.exec(raw)?.[1];
|
|
104
|
+
if (requestId || err instanceof ConvexError) {
|
|
105
|
+
return requestId ? `Nimara could not complete that call (request ${requestId}). Check the arguments and try again.` : "Nimara could not complete that call. Check the arguments and try again.";
|
|
106
|
+
}
|
|
107
|
+
return `Nimara could not reach its backend: ${raw || "unknown transport error"}. Nothing is wrong with the arguments; check the server's NIMARA_CONVEX_URL and try again.`;
|
|
108
|
+
}
|
|
82
109
|
function getConvexClient() {
|
|
83
|
-
if (cachedClient) return cachedClient;
|
|
110
|
+
if (cachedClient) return wrap(cachedClient);
|
|
84
111
|
const url = process.env.NIMARA_CONVEX_URL;
|
|
85
112
|
if (!url) {
|
|
86
113
|
throw new Error(
|
|
@@ -88,9 +115,22 @@ function getConvexClient() {
|
|
|
88
115
|
);
|
|
89
116
|
}
|
|
90
117
|
cachedClient = new ConvexHttpClient(url);
|
|
91
|
-
return cachedClient;
|
|
118
|
+
return wrap(cachedClient);
|
|
92
119
|
}
|
|
93
|
-
function
|
|
120
|
+
function wrap(client) {
|
|
121
|
+
return {
|
|
122
|
+
action: async (ref, args) => {
|
|
123
|
+
try {
|
|
124
|
+
return await client.action(ref, args);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
throw new Error(describeConvexError(err));
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function getToken(extra) {
|
|
132
|
+
const fromRequest = extra?.authInfo?.token;
|
|
133
|
+
if (fromRequest) return fromRequest;
|
|
94
134
|
const token = process.env.NIMARA_TOKEN;
|
|
95
135
|
if (!token) {
|
|
96
136
|
throw new Error(
|
|
@@ -122,13 +162,14 @@ function registerListOrgs(server) {
|
|
|
122
162
|
description: "List all organizations (and the personal workspace) the authenticated user belongs to. Use this first to discover org IDs needed by other tools.",
|
|
123
163
|
inputSchema: {},
|
|
124
164
|
annotations: {
|
|
165
|
+
title: "List organizations",
|
|
125
166
|
readOnlyHint: true,
|
|
126
167
|
openWorldHint: false
|
|
127
168
|
}
|
|
128
169
|
},
|
|
129
|
-
async () => {
|
|
170
|
+
async (_args, extra) => {
|
|
130
171
|
const client = getConvexClient();
|
|
131
|
-
const orgs = await client.action(api.mcp.listOrgs, { token: getToken() });
|
|
172
|
+
const orgs = await client.action(api.mcp.listOrgs, { token: getToken(extra) });
|
|
132
173
|
return {
|
|
133
174
|
content: [{ type: "text", text: JSON.stringify(orgs, null, 2) }]
|
|
134
175
|
};
|
|
@@ -145,14 +186,15 @@ function registerListProjects(server) {
|
|
|
145
186
|
description: "List projects in a given organization. Call list_orgs first to find the orgId. Each project includes `statuses` \u2014 its workflow in board order. Read that before setting a status on any item: those names are the only accepted values, and anything else is rejected.",
|
|
146
187
|
inputSchema: { orgId: z.string().describe("orgId from list_orgs") },
|
|
147
188
|
annotations: {
|
|
189
|
+
title: "List projects",
|
|
148
190
|
readOnlyHint: true,
|
|
149
191
|
openWorldHint: false
|
|
150
192
|
}
|
|
151
193
|
},
|
|
152
|
-
async ({ orgId }) => {
|
|
194
|
+
async ({ orgId }, extra) => {
|
|
153
195
|
const client = getConvexClient();
|
|
154
196
|
const projects = await client.action(api.mcp.listProjects, {
|
|
155
|
-
token: getToken(),
|
|
197
|
+
token: getToken(extra),
|
|
156
198
|
orgId
|
|
157
199
|
});
|
|
158
200
|
return {
|
|
@@ -195,6 +237,7 @@ function registerListWorkItems(server) {
|
|
|
195
237
|
)
|
|
196
238
|
},
|
|
197
239
|
annotations: {
|
|
240
|
+
title: "List work items",
|
|
198
241
|
readOnlyHint: true,
|
|
199
242
|
openWorldHint: false
|
|
200
243
|
}
|
|
@@ -208,9 +251,9 @@ function registerListWorkItems(server) {
|
|
|
208
251
|
type,
|
|
209
252
|
parentId,
|
|
210
253
|
needsReview
|
|
211
|
-
}) => {
|
|
254
|
+
}, extra) => {
|
|
212
255
|
const page = await getConvexClient().action(api.mcp.listWorkItems, {
|
|
213
|
-
token: getToken(),
|
|
256
|
+
token: getToken(extra),
|
|
214
257
|
projectId,
|
|
215
258
|
limit,
|
|
216
259
|
cursor,
|
|
@@ -251,13 +294,14 @@ function registerGetWorkItems(server) {
|
|
|
251
294
|
)
|
|
252
295
|
},
|
|
253
296
|
annotations: {
|
|
297
|
+
title: "Get work items by id",
|
|
254
298
|
readOnlyHint: true,
|
|
255
299
|
openWorldHint: false
|
|
256
300
|
}
|
|
257
301
|
},
|
|
258
|
-
async ({ displayIds, workItemIds, projectId }) => {
|
|
302
|
+
async ({ displayIds, workItemIds, projectId }, extra) => {
|
|
259
303
|
const result = await getConvexClient().action(api.mcp.getWorkItems, {
|
|
260
|
-
token: getToken(),
|
|
304
|
+
token: getToken(extra),
|
|
261
305
|
displayIds,
|
|
262
306
|
workItemIds,
|
|
263
307
|
projectId
|
|
@@ -287,11 +331,14 @@ function registerSearchWorkItems(server) {
|
|
|
287
331
|
),
|
|
288
332
|
limit: z4.number().int().min(1).max(25).optional().describe("Maximum results (default and maximum 25).")
|
|
289
333
|
},
|
|
290
|
-
annotations: {
|
|
334
|
+
annotations: {
|
|
335
|
+
title: "Search work items",
|
|
336
|
+
readOnlyHint: true
|
|
337
|
+
}
|
|
291
338
|
},
|
|
292
|
-
async ({ projectId, q, limit }) => {
|
|
339
|
+
async ({ projectId, q, limit }, extra) => {
|
|
293
340
|
const result = await getConvexClient().action(api.mcp.searchWorkItems, {
|
|
294
|
-
token: getToken(),
|
|
341
|
+
token: getToken(extra),
|
|
295
342
|
projectId,
|
|
296
343
|
q,
|
|
297
344
|
limit
|
|
@@ -318,13 +365,14 @@ function registerListAiReviewQueue(server) {
|
|
|
318
365
|
)
|
|
319
366
|
},
|
|
320
367
|
annotations: {
|
|
368
|
+
title: "List the AI review queue",
|
|
321
369
|
readOnlyHint: true,
|
|
322
370
|
openWorldHint: false
|
|
323
371
|
}
|
|
324
372
|
},
|
|
325
|
-
async ({ projectId, limit, cursor }) => {
|
|
373
|
+
async ({ projectId, limit, cursor }, extra) => {
|
|
326
374
|
const page = await getConvexClient().action(api.mcp.listAiReviewQueue, {
|
|
327
|
-
token: getToken(),
|
|
375
|
+
token: getToken(extra),
|
|
328
376
|
projectId,
|
|
329
377
|
limit,
|
|
330
378
|
cursor
|
|
@@ -347,13 +395,14 @@ function registerGetActiveBlock(server) {
|
|
|
347
395
|
projectId: z6.string().describe("projectId from list_projects")
|
|
348
396
|
},
|
|
349
397
|
annotations: {
|
|
398
|
+
title: "Get the active block",
|
|
350
399
|
readOnlyHint: true,
|
|
351
400
|
openWorldHint: false
|
|
352
401
|
}
|
|
353
402
|
},
|
|
354
|
-
async ({ projectId }) => {
|
|
403
|
+
async ({ projectId }, extra) => {
|
|
355
404
|
const block = await getConvexClient().action(api.mcp.getActiveBlock, {
|
|
356
|
-
token: getToken(),
|
|
405
|
+
token: getToken(extra),
|
|
357
406
|
projectId
|
|
358
407
|
});
|
|
359
408
|
return {
|
|
@@ -400,16 +449,17 @@ function registerCreateWorkItem(server) {
|
|
|
400
449
|
priority: z7.enum(["p0", "p1", "p2", "p3"]).optional().describe("p0 highest, p3 lowest. Defaults to p2.")
|
|
401
450
|
},
|
|
402
451
|
annotations: {
|
|
452
|
+
title: "Create a work item",
|
|
403
453
|
readOnlyHint: false,
|
|
404
454
|
destructiveHint: false,
|
|
405
455
|
idempotentHint: false,
|
|
406
456
|
openWorldHint: false
|
|
407
457
|
}
|
|
408
458
|
},
|
|
409
|
-
async (args) => {
|
|
459
|
+
async (args, extra) => {
|
|
410
460
|
const client = getConvexClient();
|
|
411
461
|
const result = await client.action(api.mcp.createWorkItem, {
|
|
412
|
-
token: getToken(),
|
|
462
|
+
token: getToken(extra),
|
|
413
463
|
...args
|
|
414
464
|
});
|
|
415
465
|
return {
|
|
@@ -432,7 +482,9 @@ function registerCreateProject(server) {
|
|
|
432
482
|
prefix: z8.string().min(1).max(8).describe(
|
|
433
483
|
"Short uppercase prefix for work item IDs (e.g. 'NIM'). Unique per org."
|
|
434
484
|
),
|
|
435
|
-
description: z8.string().optional().describe(
|
|
485
|
+
description: z8.string().max(280).optional().describe(
|
|
486
|
+
"Optional, and usually best left out. At most one or two plain sentences (280 characters) saying what the project is for \u2014 it shows behind an info button on the project page, so headings, stack notes, decisions and lineage do not belong here. Put that kind of context in a document (create_document) or the project's README instead."
|
|
487
|
+
),
|
|
436
488
|
isPrivate: z8.boolean().optional().describe(
|
|
437
489
|
"If true, only explicitly added members see it. Defaults to false."
|
|
438
490
|
),
|
|
@@ -441,16 +493,17 @@ function registerCreateProject(server) {
|
|
|
441
493
|
)
|
|
442
494
|
},
|
|
443
495
|
annotations: {
|
|
496
|
+
title: "Create a project",
|
|
444
497
|
readOnlyHint: false,
|
|
445
498
|
destructiveHint: false,
|
|
446
499
|
idempotentHint: false,
|
|
447
500
|
openWorldHint: false
|
|
448
501
|
}
|
|
449
502
|
},
|
|
450
|
-
async (args) => {
|
|
503
|
+
async (args, extra) => {
|
|
451
504
|
const client = getConvexClient();
|
|
452
505
|
const result = await client.action(api.mcp.createProject, {
|
|
453
|
-
token: getToken(),
|
|
506
|
+
token: getToken(extra),
|
|
454
507
|
...args
|
|
455
508
|
});
|
|
456
509
|
return {
|
|
@@ -495,7 +548,7 @@ function registerUpdateWorkItem(server) {
|
|
|
495
548
|
server.registerTool(
|
|
496
549
|
"update_work_item",
|
|
497
550
|
{
|
|
498
|
-
description: "Update an existing work item: title, description, status, priority, parent, or review flags. Any update also stamps the item as AI-touched.",
|
|
551
|
+
description: "Update an existing work item: title, description, status, priority, parent, assignee, reviewer, or review flags. Any update also stamps the item as AI-touched.",
|
|
499
552
|
inputSchema: {
|
|
500
553
|
workItemId: z9.string().describe("workItemId from list_work_items"),
|
|
501
554
|
title: z9.string().min(1).optional().describe(TITLE_GUIDANCE),
|
|
@@ -505,6 +558,12 @@ function registerUpdateWorkItem(server) {
|
|
|
505
558
|
status: z9.string().optional().describe(STATUS_GUIDANCE),
|
|
506
559
|
priority: z9.enum(["p0", "p1", "p2", "p3"]).optional(),
|
|
507
560
|
parentId: z9.string().nullable().optional().describe("New parent workItemId, null to move to root."),
|
|
561
|
+
assigneeId: z9.string().nullable().optional().describe(
|
|
562
|
+
"userId of the person to assign, from list_project_members; null to unassign. The person is notified. Must be someone on the project."
|
|
563
|
+
),
|
|
564
|
+
reviewerId: z9.string().nullable().optional().describe(
|
|
565
|
+
"userId of the person to ask for review, from list_project_members; null to clear. The person is notified. Pair it with the Review status when you hand work over. Moving an item back up the workflow clears the reviewer unless the same call names one."
|
|
566
|
+
),
|
|
508
567
|
needsReview: z9.boolean().optional().describe(
|
|
509
568
|
"Flag the item for manual human review \u2014 set true when your enrichment was thin or uncertain."
|
|
510
569
|
),
|
|
@@ -520,15 +579,16 @@ function registerUpdateWorkItem(server) {
|
|
|
520
579
|
model: z9.string().optional().describe("With agentWorking: the model you are running as, e.g. claude-opus-5, so the app can say which agent is on this. Your harness is reported automatically.")
|
|
521
580
|
},
|
|
522
581
|
annotations: {
|
|
582
|
+
title: "Update a work item",
|
|
523
583
|
readOnlyHint: false,
|
|
524
584
|
destructiveHint: true,
|
|
525
585
|
idempotentHint: true,
|
|
526
586
|
openWorldHint: false
|
|
527
587
|
}
|
|
528
588
|
},
|
|
529
|
-
async ({ model, ...args }) => {
|
|
589
|
+
async ({ model, ...args }, extra) => {
|
|
530
590
|
const result = await getConvexClient().action(api.mcp.updateWorkItem, {
|
|
531
|
-
token: getToken(),
|
|
591
|
+
token: getToken(extra),
|
|
532
592
|
...args,
|
|
533
593
|
agent: agentArg(model)
|
|
534
594
|
});
|
|
@@ -557,6 +617,7 @@ function registerMoveWorkItem(server) {
|
|
|
557
617
|
)
|
|
558
618
|
},
|
|
559
619
|
annotations: {
|
|
620
|
+
title: "Move a work item to another project",
|
|
560
621
|
readOnlyHint: false,
|
|
561
622
|
// Renumbering display IDs and deleting label/milestone links are
|
|
562
623
|
// overwrites, not additions — this belongs with the tools a client
|
|
@@ -568,10 +629,10 @@ function registerMoveWorkItem(server) {
|
|
|
568
629
|
openWorldHint: false
|
|
569
630
|
}
|
|
570
631
|
},
|
|
571
|
-
async ({ workItemId, targetProjectId }) => {
|
|
632
|
+
async ({ workItemId, targetProjectId }, extra) => {
|
|
572
633
|
const result = await getConvexClient().action(
|
|
573
634
|
api.mcp.moveWorkItemToProject,
|
|
574
|
-
{ token: getToken(), workItemId, targetProjectId }
|
|
635
|
+
{ token: getToken(extra), workItemId, targetProjectId }
|
|
575
636
|
);
|
|
576
637
|
return {
|
|
577
638
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
@@ -586,26 +647,27 @@ function registerCreateDocument(server) {
|
|
|
586
647
|
server.registerTool(
|
|
587
648
|
"create_document",
|
|
588
649
|
{
|
|
589
|
-
description: "Create a markdown project
|
|
650
|
+
description: "Create a markdown document in a project: a spec, a design, a report, research. Pass workItemId to link it to the item it is about. Leave type alone unless the document is THE project brief.",
|
|
590
651
|
inputSchema: {
|
|
591
652
|
projectId: z11.string().describe("projectId from list_projects"),
|
|
592
653
|
title: z11.string().min(1),
|
|
593
|
-
type: z11.enum(["prd", "fd"]).describe(
|
|
594
|
-
"
|
|
654
|
+
type: z11.enum(["note", "prd", "fd"]).optional().describe(
|
|
655
|
+
"Defaults to 'note', which is right for almost everything. 'prd' is reserved for the project's own product requirements brief, one per project, never linked to a work item. 'fd' is an older spelling of 'note' and still accepted."
|
|
595
656
|
),
|
|
596
|
-
workItemId: z11.string().optional().describe("
|
|
657
|
+
workItemId: z11.string().optional().describe("The work item this document is about, so it shows on that item. Not allowed with type 'prd'."),
|
|
597
658
|
content: z11.string().optional().describe("Optional initial markdown content.")
|
|
598
659
|
},
|
|
599
660
|
annotations: {
|
|
661
|
+
title: "Create a document",
|
|
600
662
|
readOnlyHint: false,
|
|
601
663
|
destructiveHint: false,
|
|
602
664
|
idempotentHint: false,
|
|
603
665
|
openWorldHint: false
|
|
604
666
|
}
|
|
605
667
|
},
|
|
606
|
-
async (args) => {
|
|
668
|
+
async (args, extra) => {
|
|
607
669
|
const result = await getConvexClient().action(api.mcp.createDocument, {
|
|
608
|
-
token: getToken(),
|
|
670
|
+
token: getToken(extra),
|
|
609
671
|
...args
|
|
610
672
|
});
|
|
611
673
|
return {
|
|
@@ -630,15 +692,16 @@ function registerUpdateDocument(server) {
|
|
|
630
692
|
title: z12.string().min(1).optional().describe("New title (optional).")
|
|
631
693
|
},
|
|
632
694
|
annotations: {
|
|
695
|
+
title: "Update a document",
|
|
633
696
|
readOnlyHint: false,
|
|
634
697
|
destructiveHint: true,
|
|
635
698
|
idempotentHint: true,
|
|
636
699
|
openWorldHint: false
|
|
637
700
|
}
|
|
638
701
|
},
|
|
639
|
-
async (args) => {
|
|
702
|
+
async (args, extra) => {
|
|
640
703
|
const result = await getConvexClient().action(api.mcp.updateDocument, {
|
|
641
|
-
token: getToken(),
|
|
704
|
+
token: getToken(extra),
|
|
642
705
|
...args
|
|
643
706
|
});
|
|
644
707
|
return {
|
|
@@ -660,13 +723,14 @@ function registerListDocuments(server) {
|
|
|
660
723
|
workItemId: z13.string().optional().describe("Optional workItemId to list linked FD documents.")
|
|
661
724
|
},
|
|
662
725
|
annotations: {
|
|
726
|
+
title: "List documents",
|
|
663
727
|
readOnlyHint: true,
|
|
664
728
|
openWorldHint: false
|
|
665
729
|
}
|
|
666
730
|
},
|
|
667
|
-
async (args) => {
|
|
731
|
+
async (args, extra) => {
|
|
668
732
|
const result = await getConvexClient().action(api.mcp.listDocuments, {
|
|
669
|
-
token: getToken(),
|
|
733
|
+
token: getToken(extra),
|
|
670
734
|
...args
|
|
671
735
|
});
|
|
672
736
|
return {
|
|
@@ -699,15 +763,16 @@ function registerCreateProjectLink(server) {
|
|
|
699
763
|
category: projectLinkCategory.optional().describe("Defaults to other.")
|
|
700
764
|
},
|
|
701
765
|
annotations: {
|
|
766
|
+
title: "Add a project link",
|
|
702
767
|
readOnlyHint: false,
|
|
703
768
|
destructiveHint: false,
|
|
704
769
|
idempotentHint: false,
|
|
705
770
|
openWorldHint: false
|
|
706
771
|
}
|
|
707
772
|
},
|
|
708
|
-
async (args) => {
|
|
773
|
+
async (args, extra) => {
|
|
709
774
|
const result = await getConvexClient().action(api.mcp.createProjectLink, {
|
|
710
|
-
token: getToken(),
|
|
775
|
+
token: getToken(extra),
|
|
711
776
|
...args
|
|
712
777
|
});
|
|
713
778
|
return {
|
|
@@ -728,13 +793,14 @@ function registerListProjectLinks(server) {
|
|
|
728
793
|
projectId: z15.string().describe("projectId from list_projects")
|
|
729
794
|
},
|
|
730
795
|
annotations: {
|
|
796
|
+
title: "List project links",
|
|
731
797
|
readOnlyHint: true,
|
|
732
798
|
openWorldHint: false
|
|
733
799
|
}
|
|
734
800
|
},
|
|
735
|
-
async ({ projectId }) => {
|
|
801
|
+
async ({ projectId }, extra) => {
|
|
736
802
|
const result = await getConvexClient().action(api.mcp.listProjectLinks, {
|
|
737
|
-
token: getToken(),
|
|
803
|
+
token: getToken(extra),
|
|
738
804
|
projectId
|
|
739
805
|
});
|
|
740
806
|
return {
|
|
@@ -744,24 +810,85 @@ function registerListProjectLinks(server) {
|
|
|
744
810
|
);
|
|
745
811
|
}
|
|
746
812
|
|
|
747
|
-
// src/tools/list-
|
|
813
|
+
// src/tools/list-project-members.ts
|
|
748
814
|
import { z as z16 } from "zod";
|
|
815
|
+
function registerListProjectMembers(server) {
|
|
816
|
+
server.registerTool(
|
|
817
|
+
"list_project_members",
|
|
818
|
+
{
|
|
819
|
+
description: "Who can open a project: userId, name, email and role for everyone with access, the same list the app's assignee picker shows. Use it to turn a name into the userId that update_work_item's assigneeId and reviewerId take, and to build a mention \u2014 [@Name](user:<userId>) in a comment notifies that person.",
|
|
820
|
+
inputSchema: {
|
|
821
|
+
projectId: z16.string().describe("projectId from list_projects")
|
|
822
|
+
},
|
|
823
|
+
annotations: {
|
|
824
|
+
title: "List project members",
|
|
825
|
+
readOnlyHint: true,
|
|
826
|
+
openWorldHint: false
|
|
827
|
+
}
|
|
828
|
+
},
|
|
829
|
+
async ({ projectId }, extra) => {
|
|
830
|
+
const result = await getConvexClient().action(api.mcp.listProjectMembers, {
|
|
831
|
+
token: getToken(extra),
|
|
832
|
+
projectId
|
|
833
|
+
});
|
|
834
|
+
return {
|
|
835
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// src/tools/unlink-pr.ts
|
|
842
|
+
import { z as z17 } from "zod";
|
|
843
|
+
function registerUnlinkPr(server) {
|
|
844
|
+
server.registerTool(
|
|
845
|
+
"unlink_pr",
|
|
846
|
+
{
|
|
847
|
+
description: "Take a pull request off a work item. PRs link themselves when their title or body names a display id, so a sample id in a PR description attaches that PR to a real item; this undoes it. Returns what was removed and what is still linked, so a wrong number can be corrected without another read. Removing the link does not touch the PR itself.",
|
|
848
|
+
inputSchema: {
|
|
849
|
+
workItemId: z17.string().describe("workItemId of the item the PR is wrongly on"),
|
|
850
|
+
prNumber: z17.number().int().positive().describe("The PR number, e.g. 354 for #354")
|
|
851
|
+
},
|
|
852
|
+
annotations: {
|
|
853
|
+
title: "Unlink a pull request",
|
|
854
|
+
readOnlyHint: false,
|
|
855
|
+
destructiveHint: true,
|
|
856
|
+
idempotentHint: true,
|
|
857
|
+
openWorldHint: false
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
async ({ workItemId, prNumber }, extra) => {
|
|
861
|
+
const result = await getConvexClient().action(api.mcp.unlinkPR, {
|
|
862
|
+
token: getToken(extra),
|
|
863
|
+
workItemId,
|
|
864
|
+
prNumber
|
|
865
|
+
});
|
|
866
|
+
return {
|
|
867
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
868
|
+
};
|
|
869
|
+
}
|
|
870
|
+
);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// src/tools/list-scratch-notes.ts
|
|
874
|
+
import { z as z18 } from "zod";
|
|
749
875
|
function registerListScratchNotes(server) {
|
|
750
876
|
server.registerTool(
|
|
751
877
|
"list_scratch_notes",
|
|
752
878
|
{
|
|
753
879
|
description: "The token owner's OPEN scratch notes: thoughts and ideas they captured in the app with the `s` shortcut before deciding what to do with them. Returns the notes that belong with this project (tagged with it, or tagged with nothing), oldest first, each with noteId, body, projectId and createdAt. These are raw: a note may be one line or a paragraph, may describe one task or three, and may not be a task at all. Read each, decide what it should become (usually one or more work items via create_work_item, sometimes a comment on an existing item, sometimes nothing), do that, then call resolve_scratch_note with the noteId and the workItemId it became so the person sees where their note went. Ask in a comment rather than guess when a note is too thin to act on. Call list_projects first for the projectId.",
|
|
754
880
|
inputSchema: {
|
|
755
|
-
projectId:
|
|
881
|
+
projectId: z18.string().describe("projectId from list_projects")
|
|
756
882
|
},
|
|
757
883
|
annotations: {
|
|
884
|
+
title: "List scratch notes",
|
|
758
885
|
readOnlyHint: true,
|
|
759
886
|
openWorldHint: false
|
|
760
887
|
}
|
|
761
888
|
},
|
|
762
|
-
async ({ projectId }) => {
|
|
889
|
+
async ({ projectId }, extra) => {
|
|
763
890
|
const notes = await getConvexClient().action(api.mcp.listScratchNotes, {
|
|
764
|
-
token: getToken(),
|
|
891
|
+
token: getToken(extra),
|
|
765
892
|
projectId
|
|
766
893
|
});
|
|
767
894
|
return {
|
|
@@ -772,26 +899,27 @@ function registerListScratchNotes(server) {
|
|
|
772
899
|
}
|
|
773
900
|
|
|
774
901
|
// src/tools/resolve-scratch-note.ts
|
|
775
|
-
import { z as
|
|
902
|
+
import { z as z19 } from "zod";
|
|
776
903
|
function registerResolveScratchNote(server) {
|
|
777
904
|
server.registerTool(
|
|
778
905
|
"resolve_scratch_note",
|
|
779
906
|
{
|
|
780
907
|
description: "Mark a scratch note as used, recording the work item it became. Call this after acting on a note from list_scratch_notes: pass the noteId and the workItemId you created or updated from it (omit workItemId only when the note needed no item). The note leaves the person's open list and shows where it went. Only the token owner's own notes.",
|
|
781
908
|
inputSchema: {
|
|
782
|
-
noteId:
|
|
783
|
-
workItemId:
|
|
909
|
+
noteId: z19.string().describe("noteId from list_scratch_notes"),
|
|
910
|
+
workItemId: z19.string().optional().describe("The workItemId the note became, from create_work_item or list_work_items.")
|
|
784
911
|
},
|
|
785
912
|
annotations: {
|
|
913
|
+
title: "Resolve a scratch note",
|
|
786
914
|
readOnlyHint: false,
|
|
787
915
|
destructiveHint: false,
|
|
788
916
|
idempotentHint: true,
|
|
789
917
|
openWorldHint: false
|
|
790
918
|
}
|
|
791
919
|
},
|
|
792
|
-
async ({ noteId, workItemId }) => {
|
|
920
|
+
async ({ noteId, workItemId }, extra) => {
|
|
793
921
|
const result = await getConvexClient().action(api.mcp.resolveScratchNote, {
|
|
794
|
-
token: getToken(),
|
|
922
|
+
token: getToken(extra),
|
|
795
923
|
noteId,
|
|
796
924
|
workItemId
|
|
797
925
|
});
|
|
@@ -803,26 +931,27 @@ function registerResolveScratchNote(server) {
|
|
|
803
931
|
}
|
|
804
932
|
|
|
805
933
|
// src/tools/heartbeat-work-item.ts
|
|
806
|
-
import { z as
|
|
934
|
+
import { z as z20 } from "zod";
|
|
807
935
|
function registerHeartbeatWorkItem(server) {
|
|
808
936
|
server.registerTool(
|
|
809
937
|
"heartbeat_work_item",
|
|
810
938
|
{
|
|
811
939
|
description: 'Say you are still working on an item. Renews (or starts) its 15-minute "agent working" lease and does nothing else: no comment, no activity event, no change to the item. The app lights the row while the lease is live, so on a long task call this every few minutes or every handful of tool calls \u2014 otherwise the row goes quiet while you are still at it. Reads of the item (get_work_items, list_work_item_comments) also renew a live lease, and any update or comment does; this is for the stretches where you do none of those. Cheap and idempotent.',
|
|
812
940
|
inputSchema: {
|
|
813
|
-
workItemId:
|
|
814
|
-
model:
|
|
941
|
+
workItemId: z20.string().describe("workItemId of the item you are working on"),
|
|
942
|
+
model: z20.string().optional().describe("The model you are running as, e.g. claude-opus-5 or gpt-5, so the app can say which agent is on this. Your harness is reported automatically.")
|
|
815
943
|
},
|
|
816
944
|
annotations: {
|
|
945
|
+
title: "Keep an agent lease alive",
|
|
817
946
|
readOnlyHint: false,
|
|
818
947
|
destructiveHint: false,
|
|
819
948
|
idempotentHint: true,
|
|
820
949
|
openWorldHint: false
|
|
821
950
|
}
|
|
822
951
|
},
|
|
823
|
-
async ({ workItemId, model }) => {
|
|
952
|
+
async ({ workItemId, model }, extra) => {
|
|
824
953
|
const result = await getConvexClient().action(api.mcp.heartbeatWorkItem, {
|
|
825
|
-
token: getToken(),
|
|
954
|
+
token: getToken(extra),
|
|
826
955
|
workItemId,
|
|
827
956
|
agent: agentArg(model)
|
|
828
957
|
});
|
|
@@ -834,26 +963,27 @@ function registerHeartbeatWorkItem(server) {
|
|
|
834
963
|
}
|
|
835
964
|
|
|
836
965
|
// src/tools/watch-work-item.ts
|
|
837
|
-
import { z as
|
|
966
|
+
import { z as z21 } from "zod";
|
|
838
967
|
function registerWatchWorkItem(server) {
|
|
839
968
|
server.registerTool(
|
|
840
969
|
"watch_work_item",
|
|
841
970
|
{
|
|
842
971
|
description: "Put the token owner on (or take them off) an item's watch list. A watcher is notified when the item changes status, gets a comment, gets a reviewer, is archived or moved \u2014 not on title or description edits. The item's assignee and reviewer already hear about it. Use this when you start work the person will want to follow, or when they ask to keep an eye on something.",
|
|
843
972
|
inputSchema: {
|
|
844
|
-
workItemId:
|
|
845
|
-
watching:
|
|
973
|
+
workItemId: z21.string().describe("workItemId of the item"),
|
|
974
|
+
watching: z21.boolean().default(true).describe("true to watch, false to stop")
|
|
846
975
|
},
|
|
847
976
|
annotations: {
|
|
977
|
+
title: "Watch a work item",
|
|
848
978
|
readOnlyHint: false,
|
|
849
979
|
destructiveHint: false,
|
|
850
980
|
idempotentHint: true,
|
|
851
981
|
openWorldHint: false
|
|
852
982
|
}
|
|
853
983
|
},
|
|
854
|
-
async ({ workItemId, watching }) => {
|
|
984
|
+
async ({ workItemId, watching }, extra) => {
|
|
855
985
|
const result = await getConvexClient().action(api.mcp.watchWorkItem, {
|
|
856
|
-
token: getToken(),
|
|
986
|
+
token: getToken(extra),
|
|
857
987
|
workItemId,
|
|
858
988
|
watching
|
|
859
989
|
});
|
|
@@ -865,28 +995,29 @@ function registerWatchWorkItem(server) {
|
|
|
865
995
|
}
|
|
866
996
|
|
|
867
997
|
// src/tools/add-work-item-image.ts
|
|
868
|
-
import { z as
|
|
998
|
+
import { z as z22 } from "zod";
|
|
869
999
|
function registerAddWorkItemImage(server) {
|
|
870
1000
|
server.registerTool(
|
|
871
1001
|
"add_work_item_image",
|
|
872
1002
|
{
|
|
873
1003
|
description: "Attach an externally hosted image URL to a work item. Use this when an MCP client has a screenshot, mockup, or generated image URL to preserve with the task.",
|
|
874
1004
|
inputSchema: {
|
|
875
|
-
workItemId:
|
|
876
|
-
imageUrl:
|
|
877
|
-
filename:
|
|
878
|
-
altText:
|
|
1005
|
+
workItemId: z22.string().describe("workItemId from list_work_items"),
|
|
1006
|
+
imageUrl: z22.string().url().describe("Public or otherwise fetchable http(s) image URL"),
|
|
1007
|
+
filename: z22.string().optional().describe("Optional display filename"),
|
|
1008
|
+
altText: z22.string().optional().describe("Optional description/caption for the image")
|
|
879
1009
|
},
|
|
880
1010
|
annotations: {
|
|
1011
|
+
title: "Attach an image to a work item",
|
|
881
1012
|
readOnlyHint: false,
|
|
882
1013
|
destructiveHint: false,
|
|
883
1014
|
idempotentHint: false,
|
|
884
1015
|
openWorldHint: false
|
|
885
1016
|
}
|
|
886
1017
|
},
|
|
887
|
-
async (args) => {
|
|
1018
|
+
async (args, extra) => {
|
|
888
1019
|
const result = await getConvexClient().action(api.mcp.addWorkItemImage, {
|
|
889
|
-
token: getToken(),
|
|
1020
|
+
token: getToken(extra),
|
|
890
1021
|
...args
|
|
891
1022
|
});
|
|
892
1023
|
return {
|
|
@@ -897,25 +1028,26 @@ function registerAddWorkItemImage(server) {
|
|
|
897
1028
|
}
|
|
898
1029
|
|
|
899
1030
|
// src/tools/list-work-item-images.ts
|
|
900
|
-
import { z as
|
|
1031
|
+
import { z as z23 } from "zod";
|
|
901
1032
|
function registerListWorkItemImages(server) {
|
|
902
1033
|
server.registerTool(
|
|
903
1034
|
"list_work_item_images",
|
|
904
1035
|
{
|
|
905
1036
|
description: "List image attachments for a work item, including uploaded images and externally attached MCP images. Call list_work_items first to find the workItemId.",
|
|
906
1037
|
inputSchema: {
|
|
907
|
-
workItemId:
|
|
1038
|
+
workItemId: z23.string().describe("workItemId from list_work_items")
|
|
908
1039
|
},
|
|
909
1040
|
annotations: {
|
|
1041
|
+
title: "List work item images",
|
|
910
1042
|
readOnlyHint: true,
|
|
911
1043
|
openWorldHint: false
|
|
912
1044
|
}
|
|
913
1045
|
},
|
|
914
|
-
async ({ workItemId }) => {
|
|
1046
|
+
async ({ workItemId }, extra) => {
|
|
915
1047
|
const result = await getConvexClient().action(
|
|
916
1048
|
api.mcp.listWorkItemImages,
|
|
917
1049
|
{
|
|
918
|
-
token: getToken(),
|
|
1050
|
+
token: getToken(extra),
|
|
919
1051
|
workItemId
|
|
920
1052
|
}
|
|
921
1053
|
);
|
|
@@ -927,31 +1059,32 @@ function registerListWorkItemImages(server) {
|
|
|
927
1059
|
}
|
|
928
1060
|
|
|
929
1061
|
// src/tools/add-work-item-comment.ts
|
|
930
|
-
import { z as
|
|
1062
|
+
import { z as z24 } from "zod";
|
|
931
1063
|
function registerAddWorkItemComment(server) {
|
|
932
1064
|
server.registerTool(
|
|
933
1065
|
"add_work_item_comment",
|
|
934
1066
|
{
|
|
935
1067
|
description: "Add a timestamped comment to a work item. Use this to record notes, updates, or an 'AI touched' entry instead of editing the description. Comments are stamped with the current time automatically and never overwrite each other.",
|
|
936
1068
|
inputSchema: {
|
|
937
|
-
workItemId:
|
|
938
|
-
body:
|
|
1069
|
+
workItemId: z24.string().describe("workItemId from list_work_items"),
|
|
1070
|
+
body: z24.string().min(1).describe(
|
|
939
1071
|
"Comment text (markdown supported). To pull a person into the thread, mention them as [@Name](user:<userId>) \u2014 they are notified and the app renders it as a chip. userIds come from an item's assigneeId/reviewerId; there is no member lookup tool yet, so only mention ids you have seen."
|
|
940
1072
|
),
|
|
941
|
-
source:
|
|
1073
|
+
source: z24.enum(["user", "ai"]).optional().describe('Who authored it; defaults to "ai" for MCP callers')
|
|
942
1074
|
},
|
|
943
1075
|
annotations: {
|
|
1076
|
+
title: "Comment on a work item",
|
|
944
1077
|
readOnlyHint: false,
|
|
945
1078
|
destructiveHint: false,
|
|
946
1079
|
idempotentHint: false,
|
|
947
1080
|
openWorldHint: false
|
|
948
1081
|
}
|
|
949
1082
|
},
|
|
950
|
-
async (args) => {
|
|
1083
|
+
async (args, extra) => {
|
|
951
1084
|
const result = await getConvexClient().action(
|
|
952
1085
|
api.mcp.addWorkItemComment,
|
|
953
1086
|
{
|
|
954
|
-
token: getToken(),
|
|
1087
|
+
token: getToken(extra),
|
|
955
1088
|
...args,
|
|
956
1089
|
agent: agentArg()
|
|
957
1090
|
}
|
|
@@ -964,25 +1097,26 @@ function registerAddWorkItemComment(server) {
|
|
|
964
1097
|
}
|
|
965
1098
|
|
|
966
1099
|
// src/tools/list-work-item-comments.ts
|
|
967
|
-
import { z as
|
|
1100
|
+
import { z as z25 } from "zod";
|
|
968
1101
|
function registerListWorkItemComments(server) {
|
|
969
1102
|
server.registerTool(
|
|
970
1103
|
"list_work_item_comments",
|
|
971
1104
|
{
|
|
972
1105
|
description: "List the timestamped comments on a work item, oldest first. Use this to check whether the item was already touched/commented before adding a new comment.",
|
|
973
1106
|
inputSchema: {
|
|
974
|
-
workItemId:
|
|
1107
|
+
workItemId: z25.string().describe("workItemId from list_work_items")
|
|
975
1108
|
},
|
|
976
1109
|
annotations: {
|
|
1110
|
+
title: "List work item comments",
|
|
977
1111
|
readOnlyHint: true,
|
|
978
1112
|
openWorldHint: false
|
|
979
1113
|
}
|
|
980
1114
|
},
|
|
981
|
-
async (args) => {
|
|
1115
|
+
async (args, extra) => {
|
|
982
1116
|
const result = await getConvexClient().action(
|
|
983
1117
|
api.mcp.listWorkItemComments,
|
|
984
1118
|
{
|
|
985
|
-
token: getToken(),
|
|
1119
|
+
token: getToken(extra),
|
|
986
1120
|
...args
|
|
987
1121
|
}
|
|
988
1122
|
);
|
|
@@ -994,24 +1128,25 @@ function registerListWorkItemComments(server) {
|
|
|
994
1128
|
}
|
|
995
1129
|
|
|
996
1130
|
// src/tools/list-validations.ts
|
|
997
|
-
import { z as
|
|
1131
|
+
import { z as z26 } from "zod";
|
|
998
1132
|
function registerListValidations(server) {
|
|
999
1133
|
server.registerTool(
|
|
1000
1134
|
"list_validations",
|
|
1001
1135
|
{
|
|
1002
1136
|
description: "List a project's validation graph: core systems and validation/test tasks with their DERIVED state (passing, failing, stale, unvalidated). A task is 'stale' when a system it covers changed since it was last validated, or an upstream dependency is no longer passing. Call list_projects first to find the projectId.",
|
|
1003
1137
|
inputSchema: {
|
|
1004
|
-
projectId:
|
|
1138
|
+
projectId: z26.string().describe("projectId from list_projects")
|
|
1005
1139
|
},
|
|
1006
1140
|
annotations: {
|
|
1141
|
+
title: "List validations",
|
|
1007
1142
|
readOnlyHint: true,
|
|
1008
1143
|
openWorldHint: false
|
|
1009
1144
|
}
|
|
1010
1145
|
},
|
|
1011
|
-
async ({ projectId }) => {
|
|
1146
|
+
async ({ projectId }, extra) => {
|
|
1012
1147
|
const client = getConvexClient();
|
|
1013
1148
|
const graph = await client.action(api.mcp.listValidations, {
|
|
1014
|
-
token: getToken(),
|
|
1149
|
+
token: getToken(extra),
|
|
1015
1150
|
projectId
|
|
1016
1151
|
});
|
|
1017
1152
|
return {
|
|
@@ -1022,27 +1157,28 @@ function registerListValidations(server) {
|
|
|
1022
1157
|
}
|
|
1023
1158
|
|
|
1024
1159
|
// src/tools/record-validation.ts
|
|
1025
|
-
import { z as
|
|
1160
|
+
import { z as z27 } from "zod";
|
|
1026
1161
|
function registerRecordValidation(server) {
|
|
1027
1162
|
server.registerTool(
|
|
1028
1163
|
"record_validation",
|
|
1029
1164
|
{
|
|
1030
1165
|
description: "Check off a validation task by recording a pass or fail. Stamps the task as validated 'now', clearing any stale flag until a covered system changes or an upstream dependency moves again. Get validationTaskId from list_validations.",
|
|
1031
1166
|
inputSchema: {
|
|
1032
|
-
validationTaskId:
|
|
1033
|
-
result:
|
|
1167
|
+
validationTaskId: z27.string().describe("validationTaskId from list_validations"),
|
|
1168
|
+
result: z27.enum(["pass", "fail"]).describe("Outcome of running the test")
|
|
1034
1169
|
},
|
|
1035
1170
|
annotations: {
|
|
1171
|
+
title: "Record a validation result",
|
|
1036
1172
|
readOnlyHint: false,
|
|
1037
1173
|
destructiveHint: true,
|
|
1038
1174
|
idempotentHint: true,
|
|
1039
1175
|
openWorldHint: false
|
|
1040
1176
|
}
|
|
1041
1177
|
},
|
|
1042
|
-
async ({ validationTaskId, result }) => {
|
|
1178
|
+
async ({ validationTaskId, result }, extra) => {
|
|
1043
1179
|
const client = getConvexClient();
|
|
1044
1180
|
const res = await client.action(api.mcp.recordValidation, {
|
|
1045
|
-
token: getToken(),
|
|
1181
|
+
token: getToken(extra),
|
|
1046
1182
|
validationTaskId,
|
|
1047
1183
|
result
|
|
1048
1184
|
});
|
|
@@ -1054,26 +1190,27 @@ function registerRecordValidation(server) {
|
|
|
1054
1190
|
}
|
|
1055
1191
|
|
|
1056
1192
|
// src/tools/mark-system-changed.ts
|
|
1057
|
-
import { z as
|
|
1193
|
+
import { z as z28 } from "zod";
|
|
1058
1194
|
function registerMarkSystemChanged(server) {
|
|
1059
1195
|
server.registerTool(
|
|
1060
1196
|
"mark_system_changed",
|
|
1061
1197
|
{
|
|
1062
1198
|
description: "Mark a core system as changed. This is the single action that invalidates testing: every validation task that covers this system (and everything transitively depending on those tasks) immediately becomes 'stale' and needs revalidation. Get systemId from list_validations.",
|
|
1063
1199
|
inputSchema: {
|
|
1064
|
-
systemId:
|
|
1200
|
+
systemId: z28.string().describe("systemId from list_validations")
|
|
1065
1201
|
},
|
|
1066
1202
|
annotations: {
|
|
1203
|
+
title: "Mark a system as changed",
|
|
1067
1204
|
readOnlyHint: false,
|
|
1068
1205
|
destructiveHint: false,
|
|
1069
1206
|
idempotentHint: false,
|
|
1070
1207
|
openWorldHint: false
|
|
1071
1208
|
}
|
|
1072
1209
|
},
|
|
1073
|
-
async ({ systemId }) => {
|
|
1210
|
+
async ({ systemId }, extra) => {
|
|
1074
1211
|
const client = getConvexClient();
|
|
1075
1212
|
const res = await client.action(api.mcp.markSystemChanged, {
|
|
1076
|
-
token: getToken(),
|
|
1213
|
+
token: getToken(extra),
|
|
1077
1214
|
systemId
|
|
1078
1215
|
});
|
|
1079
1216
|
return {
|
|
@@ -1084,28 +1221,29 @@ function registerMarkSystemChanged(server) {
|
|
|
1084
1221
|
}
|
|
1085
1222
|
|
|
1086
1223
|
// src/tools/create-system.ts
|
|
1087
|
-
import { z as
|
|
1224
|
+
import { z as z29 } from "zod";
|
|
1088
1225
|
function registerCreateSystem(server) {
|
|
1089
1226
|
server.registerTool(
|
|
1090
1227
|
"create_system",
|
|
1091
1228
|
{
|
|
1092
1229
|
description: "Create a core system in a project \u2014 a subsystem (e.g. Auth, Payments, Sync) whose change should invalidate the validation tasks that cover it. Returns the new systemId.",
|
|
1093
1230
|
inputSchema: {
|
|
1094
|
-
projectId:
|
|
1095
|
-
name:
|
|
1096
|
-
description:
|
|
1231
|
+
projectId: z29.string().describe("projectId from list_projects"),
|
|
1232
|
+
name: z29.string().min(1).describe("Short system name, e.g. 'Auth'"),
|
|
1233
|
+
description: z29.string().optional().describe("Optional details")
|
|
1097
1234
|
},
|
|
1098
1235
|
annotations: {
|
|
1236
|
+
title: "Create a system",
|
|
1099
1237
|
readOnlyHint: false,
|
|
1100
1238
|
destructiveHint: false,
|
|
1101
1239
|
idempotentHint: false,
|
|
1102
1240
|
openWorldHint: false
|
|
1103
1241
|
}
|
|
1104
1242
|
},
|
|
1105
|
-
async (args) => {
|
|
1243
|
+
async (args, extra) => {
|
|
1106
1244
|
const client = getConvexClient();
|
|
1107
1245
|
const res = await client.action(api.mcp.createSystem, {
|
|
1108
|
-
token: getToken(),
|
|
1246
|
+
token: getToken(extra),
|
|
1109
1247
|
...args
|
|
1110
1248
|
});
|
|
1111
1249
|
return {
|
|
@@ -1116,34 +1254,35 @@ function registerCreateSystem(server) {
|
|
|
1116
1254
|
}
|
|
1117
1255
|
|
|
1118
1256
|
// src/tools/create-validation-task.ts
|
|
1119
|
-
import { z as
|
|
1257
|
+
import { z as z30 } from "zod";
|
|
1120
1258
|
function registerCreateValidationTask(server) {
|
|
1121
1259
|
server.registerTool(
|
|
1122
1260
|
"create_validation_task",
|
|
1123
1261
|
{
|
|
1124
1262
|
description: "Create a validation/test task in a project. After creating it, use add_system_to_task to declare which systems it exercises (so it goes stale when they change) and add_validation_dependency to order it after other tasks. Returns the new validationTaskId.",
|
|
1125
1263
|
inputSchema: {
|
|
1126
|
-
projectId:
|
|
1127
|
-
title:
|
|
1128
|
-
description:
|
|
1129
|
-
kind:
|
|
1264
|
+
projectId: z30.string().describe("projectId from list_projects"),
|
|
1265
|
+
title: z30.string().min(1).describe("What this test validates"),
|
|
1266
|
+
description: z30.string().optional().describe("Optional steps / details"),
|
|
1267
|
+
kind: z30.enum(["auto", "manual"]).optional().describe(
|
|
1130
1268
|
"'auto' if runnable by a test runner, 'manual' if a human checks it. Defaults to manual."
|
|
1131
1269
|
),
|
|
1132
|
-
workItemId:
|
|
1270
|
+
workItemId: z30.string().optional().describe(
|
|
1133
1271
|
"Optional workItemId to link this test to a feature for traceability"
|
|
1134
1272
|
)
|
|
1135
1273
|
},
|
|
1136
1274
|
annotations: {
|
|
1275
|
+
title: "Create a validation task",
|
|
1137
1276
|
readOnlyHint: false,
|
|
1138
1277
|
destructiveHint: false,
|
|
1139
1278
|
idempotentHint: false,
|
|
1140
1279
|
openWorldHint: false
|
|
1141
1280
|
}
|
|
1142
1281
|
},
|
|
1143
|
-
async (args) => {
|
|
1282
|
+
async (args, extra) => {
|
|
1144
1283
|
const client = getConvexClient();
|
|
1145
1284
|
const res = await client.action(api.mcp.createValidationTask, {
|
|
1146
|
-
token: getToken(),
|
|
1285
|
+
token: getToken(extra),
|
|
1147
1286
|
...args
|
|
1148
1287
|
});
|
|
1149
1288
|
return {
|
|
@@ -1154,27 +1293,28 @@ function registerCreateValidationTask(server) {
|
|
|
1154
1293
|
}
|
|
1155
1294
|
|
|
1156
1295
|
// src/tools/add-system-to-task.ts
|
|
1157
|
-
import { z as
|
|
1296
|
+
import { z as z31 } from "zod";
|
|
1158
1297
|
function registerAddSystemToTask(server) {
|
|
1159
1298
|
server.registerTool(
|
|
1160
1299
|
"add_system_to_task",
|
|
1161
1300
|
{
|
|
1162
1301
|
description: "Declare that a validation task COVERS (exercises) a system. Once linked, the task becomes 'stale' whenever that system is marked changed. Idempotent. Both IDs come from list_validations and must be in the same project.",
|
|
1163
1302
|
inputSchema: {
|
|
1164
|
-
validationTaskId:
|
|
1165
|
-
systemId:
|
|
1303
|
+
validationTaskId: z31.string().describe("validationTaskId from list_validations"),
|
|
1304
|
+
systemId: z31.string().describe("systemId from list_validations")
|
|
1166
1305
|
},
|
|
1167
1306
|
annotations: {
|
|
1307
|
+
title: "Link a system to a task",
|
|
1168
1308
|
readOnlyHint: false,
|
|
1169
1309
|
destructiveHint: false,
|
|
1170
1310
|
idempotentHint: true,
|
|
1171
1311
|
openWorldHint: false
|
|
1172
1312
|
}
|
|
1173
1313
|
},
|
|
1174
|
-
async ({ validationTaskId, systemId }) => {
|
|
1314
|
+
async ({ validationTaskId, systemId }, extra) => {
|
|
1175
1315
|
const client = getConvexClient();
|
|
1176
1316
|
const res = await client.action(api.mcp.addSystemToTask, {
|
|
1177
|
-
token: getToken(),
|
|
1317
|
+
token: getToken(extra),
|
|
1178
1318
|
validationTaskId,
|
|
1179
1319
|
systemId
|
|
1180
1320
|
});
|
|
@@ -1186,27 +1326,28 @@ function registerAddSystemToTask(server) {
|
|
|
1186
1326
|
}
|
|
1187
1327
|
|
|
1188
1328
|
// src/tools/add-validation-dependency.ts
|
|
1189
|
-
import { z as
|
|
1329
|
+
import { z as z32 } from "zod";
|
|
1190
1330
|
function registerAddValidationDependency(server) {
|
|
1191
1331
|
server.registerTool(
|
|
1192
1332
|
"add_validation_dependency",
|
|
1193
1333
|
{
|
|
1194
1334
|
description: "Make one validation task depend on another (validationTaskId depends on dependsOnTaskId). The dependent goes 'stale' whenever the upstream task is not passing or gets revalidated. Cycles and self-dependencies are rejected. Idempotent. Both IDs come from list_validations and must be in the same project.",
|
|
1195
1335
|
inputSchema: {
|
|
1196
|
-
validationTaskId:
|
|
1197
|
-
dependsOnTaskId:
|
|
1336
|
+
validationTaskId: z32.string().describe("The dependent (downstream) task"),
|
|
1337
|
+
dependsOnTaskId: z32.string().describe("The dependency (upstream) task it relies on")
|
|
1198
1338
|
},
|
|
1199
1339
|
annotations: {
|
|
1340
|
+
title: "Add a validation dependency",
|
|
1200
1341
|
readOnlyHint: false,
|
|
1201
1342
|
destructiveHint: false,
|
|
1202
1343
|
idempotentHint: true,
|
|
1203
1344
|
openWorldHint: false
|
|
1204
1345
|
}
|
|
1205
1346
|
},
|
|
1206
|
-
async ({ validationTaskId, dependsOnTaskId }) => {
|
|
1347
|
+
async ({ validationTaskId, dependsOnTaskId }, extra) => {
|
|
1207
1348
|
const client = getConvexClient();
|
|
1208
1349
|
const res = await client.action(api.mcp.addValidationDependency, {
|
|
1209
|
-
token: getToken(),
|
|
1350
|
+
token: getToken(extra),
|
|
1210
1351
|
validationTaskId,
|
|
1211
1352
|
dependsOnTaskId
|
|
1212
1353
|
});
|
|
@@ -1218,23 +1359,24 @@ function registerAddValidationDependency(server) {
|
|
|
1218
1359
|
}
|
|
1219
1360
|
|
|
1220
1361
|
// src/tools/list-labels.ts
|
|
1221
|
-
import { z as
|
|
1362
|
+
import { z as z33 } from "zod";
|
|
1222
1363
|
function registerListLabels(server) {
|
|
1223
1364
|
server.registerTool(
|
|
1224
1365
|
"list_labels",
|
|
1225
1366
|
{
|
|
1226
1367
|
description: "List all labels defined in a project. Returns each label's id, name, and color. Use the labelId with add_label_to_work_item / remove_label_from_work_item.",
|
|
1227
1368
|
inputSchema: {
|
|
1228
|
-
projectId:
|
|
1369
|
+
projectId: z33.string().describe("projectId from list_projects")
|
|
1229
1370
|
},
|
|
1230
1371
|
annotations: {
|
|
1372
|
+
title: "List labels",
|
|
1231
1373
|
readOnlyHint: true,
|
|
1232
1374
|
openWorldHint: false
|
|
1233
1375
|
}
|
|
1234
1376
|
},
|
|
1235
|
-
async (args) => {
|
|
1377
|
+
async (args, extra) => {
|
|
1236
1378
|
const result = await getConvexClient().action(api.mcp.listLabels, {
|
|
1237
|
-
token: getToken(),
|
|
1379
|
+
token: getToken(extra),
|
|
1238
1380
|
...args
|
|
1239
1381
|
});
|
|
1240
1382
|
return {
|
|
@@ -1245,27 +1387,28 @@ function registerListLabels(server) {
|
|
|
1245
1387
|
}
|
|
1246
1388
|
|
|
1247
1389
|
// src/tools/create-label.ts
|
|
1248
|
-
import { z as
|
|
1390
|
+
import { z as z34 } from "zod";
|
|
1249
1391
|
function registerCreateLabel(server) {
|
|
1250
1392
|
server.registerTool(
|
|
1251
1393
|
"create_label",
|
|
1252
1394
|
{
|
|
1253
1395
|
description: "Apply-or-seed a label. Returns the existing label if the name is already taken (idempotent), and otherwise creates it ONLY if the name is one of the curated ones. Any other new name is REFUSED \u2014 the reply carries `refused: true`, the reason, and `available`, the project's existing labels. Agents apply labels; they do not invent them, because a near-duplicate of an existing label splits the vocabulary silently. Prefer list_labels first. Colors are ignored for curated names so the vocabulary looks the same in every project.",
|
|
1254
1396
|
inputSchema: {
|
|
1255
|
-
projectId:
|
|
1256
|
-
name:
|
|
1257
|
-
color:
|
|
1397
|
+
projectId: z34.string().describe("projectId from list_projects"),
|
|
1398
|
+
name: z34.string().min(1).describe("Label name, unique within the project"),
|
|
1399
|
+
color: z34.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Hex color like #2563eb. Defaults to a neutral slate.")
|
|
1258
1400
|
},
|
|
1259
1401
|
annotations: {
|
|
1402
|
+
title: "Create a label",
|
|
1260
1403
|
readOnlyHint: false,
|
|
1261
1404
|
destructiveHint: false,
|
|
1262
1405
|
idempotentHint: true,
|
|
1263
1406
|
openWorldHint: false
|
|
1264
1407
|
}
|
|
1265
1408
|
},
|
|
1266
|
-
async (args) => {
|
|
1409
|
+
async (args, extra) => {
|
|
1267
1410
|
const result = await getConvexClient().action(api.mcp.createLabel, {
|
|
1268
|
-
token: getToken(),
|
|
1411
|
+
token: getToken(extra),
|
|
1269
1412
|
...args
|
|
1270
1413
|
});
|
|
1271
1414
|
return {
|
|
@@ -1276,26 +1419,27 @@ function registerCreateLabel(server) {
|
|
|
1276
1419
|
}
|
|
1277
1420
|
|
|
1278
1421
|
// src/tools/add-label-to-work-item.ts
|
|
1279
|
-
import { z as
|
|
1422
|
+
import { z as z35 } from "zod";
|
|
1280
1423
|
function registerAddLabelToWorkItem(server) {
|
|
1281
1424
|
server.registerTool(
|
|
1282
1425
|
"add_label_to_work_item",
|
|
1283
1426
|
{
|
|
1284
1427
|
description: "Attach a label to a work item (idempotent). The label and work item must be in the same project. Get labelIds from list_labels / create_label.",
|
|
1285
1428
|
inputSchema: {
|
|
1286
|
-
workItemId:
|
|
1287
|
-
labelId:
|
|
1429
|
+
workItemId: z35.string().describe("workItemId from list_work_items"),
|
|
1430
|
+
labelId: z35.string().describe("labelId from list_labels or create_label")
|
|
1288
1431
|
},
|
|
1289
1432
|
annotations: {
|
|
1433
|
+
title: "Add a label to a work item",
|
|
1290
1434
|
readOnlyHint: false,
|
|
1291
1435
|
destructiveHint: false,
|
|
1292
1436
|
idempotentHint: true,
|
|
1293
1437
|
openWorldHint: false
|
|
1294
1438
|
}
|
|
1295
1439
|
},
|
|
1296
|
-
async (args) => {
|
|
1440
|
+
async (args, extra) => {
|
|
1297
1441
|
const result = await getConvexClient().action(api.mcp.setWorkItemLabel, {
|
|
1298
|
-
token: getToken(),
|
|
1442
|
+
token: getToken(extra),
|
|
1299
1443
|
...args,
|
|
1300
1444
|
attached: true
|
|
1301
1445
|
});
|
|
@@ -1307,26 +1451,27 @@ function registerAddLabelToWorkItem(server) {
|
|
|
1307
1451
|
}
|
|
1308
1452
|
|
|
1309
1453
|
// src/tools/remove-label-from-work-item.ts
|
|
1310
|
-
import { z as
|
|
1454
|
+
import { z as z36 } from "zod";
|
|
1311
1455
|
function registerRemoveLabelFromWorkItem(server) {
|
|
1312
1456
|
server.registerTool(
|
|
1313
1457
|
"remove_label_from_work_item",
|
|
1314
1458
|
{
|
|
1315
1459
|
description: "Remove a label from a work item (idempotent \u2014 a no-op if it wasn't attached).",
|
|
1316
1460
|
inputSchema: {
|
|
1317
|
-
workItemId:
|
|
1318
|
-
labelId:
|
|
1461
|
+
workItemId: z36.string().describe("workItemId from list_work_items"),
|
|
1462
|
+
labelId: z36.string().describe("labelId from list_labels")
|
|
1319
1463
|
},
|
|
1320
1464
|
annotations: {
|
|
1465
|
+
title: "Remove a label from a work item",
|
|
1321
1466
|
readOnlyHint: false,
|
|
1322
1467
|
destructiveHint: true,
|
|
1323
1468
|
idempotentHint: true,
|
|
1324
1469
|
openWorldHint: false
|
|
1325
1470
|
}
|
|
1326
1471
|
},
|
|
1327
|
-
async (args) => {
|
|
1472
|
+
async (args, extra) => {
|
|
1328
1473
|
const result = await getConvexClient().action(api.mcp.setWorkItemLabel, {
|
|
1329
|
-
token: getToken(),
|
|
1474
|
+
token: getToken(extra),
|
|
1330
1475
|
...args,
|
|
1331
1476
|
attached: false
|
|
1332
1477
|
});
|
|
@@ -1338,23 +1483,24 @@ function registerRemoveLabelFromWorkItem(server) {
|
|
|
1338
1483
|
}
|
|
1339
1484
|
|
|
1340
1485
|
// src/tools/list-milestones.ts
|
|
1341
|
-
import { z as
|
|
1486
|
+
import { z as z37 } from "zod";
|
|
1342
1487
|
function registerListMilestones(server) {
|
|
1343
1488
|
server.registerTool(
|
|
1344
1489
|
"list_milestones",
|
|
1345
1490
|
{
|
|
1346
1491
|
description: "List all milestones in a project. Returns each milestone's id, name, description, dueDate (unix ms), and status. Use the milestoneId with add_item_to_milestone / remove_item_from_milestone.",
|
|
1347
1492
|
inputSchema: {
|
|
1348
|
-
projectId:
|
|
1493
|
+
projectId: z37.string().describe("projectId from list_projects")
|
|
1349
1494
|
},
|
|
1350
1495
|
annotations: {
|
|
1496
|
+
title: "List milestones",
|
|
1351
1497
|
readOnlyHint: true,
|
|
1352
1498
|
openWorldHint: false
|
|
1353
1499
|
}
|
|
1354
1500
|
},
|
|
1355
|
-
async (args) => {
|
|
1501
|
+
async (args, extra) => {
|
|
1356
1502
|
const result = await getConvexClient().action(api.mcp.listMilestones, {
|
|
1357
|
-
token: getToken(),
|
|
1503
|
+
token: getToken(extra),
|
|
1358
1504
|
...args
|
|
1359
1505
|
});
|
|
1360
1506
|
return {
|
|
@@ -1365,28 +1511,29 @@ function registerListMilestones(server) {
|
|
|
1365
1511
|
}
|
|
1366
1512
|
|
|
1367
1513
|
// src/tools/create-milestone.ts
|
|
1368
|
-
import { z as
|
|
1514
|
+
import { z as z38 } from "zod";
|
|
1369
1515
|
function registerCreateMilestone(server) {
|
|
1370
1516
|
server.registerTool(
|
|
1371
1517
|
"create_milestone",
|
|
1372
1518
|
{
|
|
1373
1519
|
description: "Create a milestone in a project. Returns the milestoneId. Good for grouping work into phases (e.g. 'Phase 1 \u2014 Web', 'Phase 2 \u2014 Native').",
|
|
1374
1520
|
inputSchema: {
|
|
1375
|
-
projectId:
|
|
1376
|
-
name:
|
|
1377
|
-
description:
|
|
1378
|
-
dueDate:
|
|
1521
|
+
projectId: z38.string().describe("projectId from list_projects"),
|
|
1522
|
+
name: z38.string().min(1).describe("Milestone name"),
|
|
1523
|
+
description: z38.string().optional().describe("Optional markdown description"),
|
|
1524
|
+
dueDate: z38.number().optional().describe("Optional due date as a unix timestamp in milliseconds")
|
|
1379
1525
|
},
|
|
1380
1526
|
annotations: {
|
|
1527
|
+
title: "Create a milestone",
|
|
1381
1528
|
readOnlyHint: false,
|
|
1382
1529
|
destructiveHint: false,
|
|
1383
1530
|
idempotentHint: false,
|
|
1384
1531
|
openWorldHint: false
|
|
1385
1532
|
}
|
|
1386
1533
|
},
|
|
1387
|
-
async (args) => {
|
|
1534
|
+
async (args, extra) => {
|
|
1388
1535
|
const result = await getConvexClient().action(api.mcp.createMilestone, {
|
|
1389
|
-
token: getToken(),
|
|
1536
|
+
token: getToken(extra),
|
|
1390
1537
|
...args
|
|
1391
1538
|
});
|
|
1392
1539
|
return {
|
|
@@ -1397,28 +1544,29 @@ function registerCreateMilestone(server) {
|
|
|
1397
1544
|
}
|
|
1398
1545
|
|
|
1399
1546
|
// src/tools/add-item-to-milestone.ts
|
|
1400
|
-
import { z as
|
|
1547
|
+
import { z as z39 } from "zod";
|
|
1401
1548
|
function registerAddItemToMilestone(server) {
|
|
1402
1549
|
server.registerTool(
|
|
1403
1550
|
"add_item_to_milestone",
|
|
1404
1551
|
{
|
|
1405
1552
|
description: "Associate a work item with a milestone (idempotent). Both must be in the same project.",
|
|
1406
1553
|
inputSchema: {
|
|
1407
|
-
milestoneId:
|
|
1408
|
-
workItemId:
|
|
1554
|
+
milestoneId: z39.string().describe("milestoneId from list_milestones or create_milestone"),
|
|
1555
|
+
workItemId: z39.string().describe("workItemId from list_work_items")
|
|
1409
1556
|
},
|
|
1410
1557
|
annotations: {
|
|
1558
|
+
title: "Add an item to a milestone",
|
|
1411
1559
|
readOnlyHint: false,
|
|
1412
1560
|
destructiveHint: false,
|
|
1413
1561
|
idempotentHint: true,
|
|
1414
1562
|
openWorldHint: false
|
|
1415
1563
|
}
|
|
1416
1564
|
},
|
|
1417
|
-
async (args) => {
|
|
1565
|
+
async (args, extra) => {
|
|
1418
1566
|
const result = await getConvexClient().action(
|
|
1419
1567
|
api.mcp.setWorkItemMilestone,
|
|
1420
1568
|
{
|
|
1421
|
-
token: getToken(),
|
|
1569
|
+
token: getToken(extra),
|
|
1422
1570
|
...args,
|
|
1423
1571
|
attached: true
|
|
1424
1572
|
}
|
|
@@ -1431,28 +1579,29 @@ function registerAddItemToMilestone(server) {
|
|
|
1431
1579
|
}
|
|
1432
1580
|
|
|
1433
1581
|
// src/tools/remove-item-from-milestone.ts
|
|
1434
|
-
import { z as
|
|
1582
|
+
import { z as z40 } from "zod";
|
|
1435
1583
|
function registerRemoveItemFromMilestone(server) {
|
|
1436
1584
|
server.registerTool(
|
|
1437
1585
|
"remove_item_from_milestone",
|
|
1438
1586
|
{
|
|
1439
1587
|
description: "Remove a work item's association with a milestone (idempotent \u2014 a no-op if it wasn't associated).",
|
|
1440
1588
|
inputSchema: {
|
|
1441
|
-
milestoneId:
|
|
1442
|
-
workItemId:
|
|
1589
|
+
milestoneId: z40.string().describe("milestoneId from list_milestones"),
|
|
1590
|
+
workItemId: z40.string().describe("workItemId from list_work_items")
|
|
1443
1591
|
},
|
|
1444
1592
|
annotations: {
|
|
1593
|
+
title: "Remove an item from a milestone",
|
|
1445
1594
|
readOnlyHint: false,
|
|
1446
1595
|
destructiveHint: true,
|
|
1447
1596
|
idempotentHint: true,
|
|
1448
1597
|
openWorldHint: false
|
|
1449
1598
|
}
|
|
1450
1599
|
},
|
|
1451
|
-
async (args) => {
|
|
1600
|
+
async (args, extra) => {
|
|
1452
1601
|
const result = await getConvexClient().action(
|
|
1453
1602
|
api.mcp.setWorkItemMilestone,
|
|
1454
1603
|
{
|
|
1455
|
-
token: getToken(),
|
|
1604
|
+
token: getToken(extra),
|
|
1456
1605
|
...args,
|
|
1457
1606
|
attached: false
|
|
1458
1607
|
}
|
|
@@ -1491,6 +1640,8 @@ function createServer() {
|
|
|
1491
1640
|
registerListDocuments(server);
|
|
1492
1641
|
registerCreateProjectLink(server);
|
|
1493
1642
|
registerListProjectLinks(server);
|
|
1643
|
+
registerListProjectMembers(server);
|
|
1644
|
+
registerUnlinkPr(server);
|
|
1494
1645
|
registerListScratchNotes(server);
|
|
1495
1646
|
registerResolveScratchNote(server);
|
|
1496
1647
|
registerHeartbeatWorkItem(server);
|