@nimara-app/mcp 0.2.0 → 0.4.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 +4 -0
- package/dist/index.js +321 -116
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6,6 +6,76 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
6
6
|
// src/server.ts
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
|
|
9
|
+
// package.json
|
|
10
|
+
var package_default = {
|
|
11
|
+
name: "@nimara-app/mcp",
|
|
12
|
+
version: "0.4.0",
|
|
13
|
+
private: false,
|
|
14
|
+
mcpName: "io.github.langskip-studios/nimara",
|
|
15
|
+
description: "Nimara MCP server \u2014 AI tool integration for Claude, Codex, and others",
|
|
16
|
+
license: "MIT",
|
|
17
|
+
type: "module",
|
|
18
|
+
main: "dist/index.js",
|
|
19
|
+
bin: {
|
|
20
|
+
"nimara-mcp": "dist/index.js"
|
|
21
|
+
},
|
|
22
|
+
files: [
|
|
23
|
+
"dist",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
keywords: [
|
|
27
|
+
"mcp",
|
|
28
|
+
"model-context-protocol",
|
|
29
|
+
"nimara",
|
|
30
|
+
"claude",
|
|
31
|
+
"ai",
|
|
32
|
+
"convex"
|
|
33
|
+
],
|
|
34
|
+
repository: {
|
|
35
|
+
type: "git",
|
|
36
|
+
url: "git+https://github.com/Langskip-Studios/Nimara.git",
|
|
37
|
+
directory: "packages/mcp"
|
|
38
|
+
},
|
|
39
|
+
homepage: "https://github.com/Langskip-Studios/Nimara/tree/master/packages/mcp#readme",
|
|
40
|
+
publishConfig: {
|
|
41
|
+
access: "public"
|
|
42
|
+
},
|
|
43
|
+
scripts: {
|
|
44
|
+
build: "node build.mjs",
|
|
45
|
+
dev: "node build.mjs",
|
|
46
|
+
start: "node dist/index.js",
|
|
47
|
+
typecheck: "tsc --noEmit",
|
|
48
|
+
prepublishOnly: "npm run typecheck && npm run build"
|
|
49
|
+
},
|
|
50
|
+
dependencies: {
|
|
51
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
52
|
+
convex: "^1.18.0",
|
|
53
|
+
zod: "^3.23.0"
|
|
54
|
+
},
|
|
55
|
+
devDependencies: {
|
|
56
|
+
"@nimara/convex": "workspace:*",
|
|
57
|
+
"@types/node": "^20.0.0",
|
|
58
|
+
esbuild: "^0.23.0",
|
|
59
|
+
typescript: "^5.0.0"
|
|
60
|
+
},
|
|
61
|
+
engines: {
|
|
62
|
+
node: ">=20"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/instructions.ts
|
|
67
|
+
var INSTRUCTIONS = `Nimara is the team's work tracker. Work items have a type (epic > feature > task > subtask), a status from the project's own workflow, and a displayId like NIM-T42 that people read and type.
|
|
68
|
+
|
|
69
|
+
Start with list_orgs \u2192 list_projects \u2192 get_active_block (the committed work; position 0 is next) and list_ai_review_queue (what a person has asked an agent to do; read \`answer\` first). Use get_work_items / search_work_items for items you already know of; list_work_items only to survey.
|
|
70
|
+
|
|
71
|
+
Titles are plain language a teammate understands at a glance: what is wrong or what changes for a person, not the file, function or mechanism. No em dashes, no "Area: thing, thing" shapes. Descriptions carry the why, acceptance criteria and file paths; a spec, design or long report goes in a document (create_document) linked from the item.
|
|
72
|
+
|
|
73
|
+
A status must be one of the project's \`statuses\` from list_projects, spelled exactly; never invent one. Labels come from list_labels; do not invent those either.
|
|
74
|
+
|
|
75
|
+
When you pick up an item: update_work_item { status: <in-progress status>, agentWorking: true }. That lights its row for 15 minutes; 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
|
+
|
|
77
|
+
Mention a person in a comment as [@Name](user:<userId>) and they are notified. 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.`;
|
|
78
|
+
|
|
9
79
|
// ../convex/convex/_generated/api.js
|
|
10
80
|
import { anyApi, componentsGeneric } from "convex/server";
|
|
11
81
|
var api = anyApi;
|
|
@@ -230,7 +300,7 @@ function registerListAiReviewQueue(server) {
|
|
|
230
300
|
server.registerTool(
|
|
231
301
|
"list_ai_review_queue",
|
|
232
302
|
{
|
|
233
|
-
description: "List work items in a project that a human has flagged for AI review (aiReviewRequested). Descriptions come back in full here, since enrichment needs the whole text. Returns { items, nextCursor, hasMore } \u2014 when hasMore is true, call again with cursor set to nextCursor. Process each item, then clear its flag by calling update_work_item with aiReviewRequested: false. Call list_projects first to find the projectId.",
|
|
303
|
+
description: "List work items in a project that a human has flagged for AI review (aiReviewRequested). Descriptions come back in full here, since enrichment needs the whole text. Each item also carries recentComments (the last few, oldest first) and, when a person has written one, `answer`: the newest human comment. READ `answer` FIRST. When you flagged an item with a question and it is back in this queue, `answer` is the person's reply to that question; act on it rather than asking again. Returns { items, nextCursor, hasMore } \u2014 when hasMore is true, call again with cursor set to nextCursor. Process each item, then clear its flag by calling update_work_item with aiReviewRequested: false. Call list_projects first to find the projectId.",
|
|
234
304
|
inputSchema: {
|
|
235
305
|
projectId: z5.string().describe("projectId from list_projects"),
|
|
236
306
|
limit: z5.number().int().min(1).max(200).optional().describe("Items per page (default 50, max 200)."),
|
|
@@ -257,8 +327,35 @@ function registerListAiReviewQueue(server) {
|
|
|
257
327
|
);
|
|
258
328
|
}
|
|
259
329
|
|
|
260
|
-
// src/tools/
|
|
330
|
+
// src/tools/get-active-block.ts
|
|
261
331
|
import { z as z6 } from "zod";
|
|
332
|
+
function registerGetActiveBlock(server) {
|
|
333
|
+
server.registerTool(
|
|
334
|
+
"get_active_block",
|
|
335
|
+
{
|
|
336
|
+
description: "The project's ACTIVE BLOCK: the small, ordered set of work a person has committed to right now. Call this BEFORE list_work_items when deciding what to work on \u2014 position 0 is the next thing to do, and the wider backlog is not on the table until the block is empty. Each item carries a lane: 'person' means a human owes it (flagged for review, or parked in a Review status) and you should not touch it; 'agent' means a human asked an agent to act on it, so it is yours first, and it carries recentComments and `answer` (the newest human comment: read it before anything else); 'open' is uncommitted-to-anyone work in priority order; 'done' is finished. Returns null when the project has no active block, in which case fall back to list_work_items. Descriptions are previewed; use get_work_items for the full text of an item you pick up. Call list_projects first for the projectId.",
|
|
337
|
+
inputSchema: {
|
|
338
|
+
projectId: z6.string().describe("projectId from list_projects")
|
|
339
|
+
},
|
|
340
|
+
annotations: {
|
|
341
|
+
readOnlyHint: true,
|
|
342
|
+
openWorldHint: false
|
|
343
|
+
}
|
|
344
|
+
},
|
|
345
|
+
async ({ projectId }) => {
|
|
346
|
+
const block = await getConvexClient().action(api.mcp.getActiveBlock, {
|
|
347
|
+
token: getToken(),
|
|
348
|
+
projectId
|
|
349
|
+
});
|
|
350
|
+
return {
|
|
351
|
+
content: [{ type: "text", text: JSON.stringify(block, null, 2) }]
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/tools/create-work-item.ts
|
|
358
|
+
import { z as z7 } from "zod";
|
|
262
359
|
|
|
263
360
|
// src/tools/titleGuidance.ts
|
|
264
361
|
var TITLE_GUIDANCE = 'A title a teammate can understand at a glance, without opening the item or knowing the codebase. Say what is wrong or what changes FOR A PERSON, in plain words \u2014 not the mechanism, the file, or the symbol. Good: "Sorted list shows an expand arrow that does nothing". Bad: "Fix hasChildren computation in list.tsx". Good: "Long titles wrap to three lines and are hard to read". Bad: "Adjust COLUMNS.title min-width constant". Prefer ordinary words over internal abbreviations, and only name a file, function or identifier when it genuinely is the clearest way to say it (a tool or endpoint the user calls by name, for instance). No ticket prefixes or status markers \u2014 the board renders those already. Root cause, file paths, and reproduction steps belong in the description, which has room for them. Keep it to one plain clause. Do NOT use an em dash, and do not use the "Area: thing, other thing" shape \u2014 both are ways of stapling a second title on and they are what makes these hard to read. Bad: "Gantt: tighten row density, match app typography, stop the chart falling short of its container". Good: "Gantt chart looks nothing like the rest of the app". If the title needs a comma-spliced list to be accurate, that is a sign the item is really several items, not a sign it needs a longer title.';
|
|
@@ -266,6 +363,14 @@ var TITLE_GUIDANCE = 'A title a teammate can understand at a glance, without ope
|
|
|
266
363
|
// src/tools/statusGuidance.ts
|
|
267
364
|
var STATUS_GUIDANCE = 'Must be one of the names in the project\'s `statuses`, returned by list_projects. Read it rather than guessing \u2014 a status is the board column, so a name the workflow does not have is rejected, and before that check existed such items rendered in the first column while claiming otherwise. Match the existing name exactly, including its wording: if the workflow says "Review", do not write "In Review". Do NOT invent a status to capture something about an item. Statuses are shared across the whole project and aggregated on the dashboard, so each new one is a permanent extra band there. Anything that describes an item rather than locating it in the flow belongs in a LABEL \u2014 see create_label and add_label_to_work_item. Defaults to the first status when omitted.';
|
|
268
365
|
|
|
366
|
+
// ../convex/convex/lib/descriptionLimits.ts
|
|
367
|
+
var DESCRIPTION_MAX_CHARS = 32e3;
|
|
368
|
+
var DESCRIPTION_TOO_LONG = `A description can be at most ${DESCRIPTION_MAX_CHARS.toLocaleString("en-US")} characters. Put specs, research and long reports in a document and link it to the item.`;
|
|
369
|
+
|
|
370
|
+
// src/tools/descriptionGuidance.ts
|
|
371
|
+
var DESCRIPTION_MAX = DESCRIPTION_MAX_CHARS;
|
|
372
|
+
var DESCRIPTION_GUIDANCE = `Markdown. The what and the why: the problem, acceptance criteria, reproduction steps, file paths, root cause. Keep it to what someone picking the item up needs. A spec, a design, research, or a long report goes in a document (create_document) linked from here, not inline. At most ${DESCRIPTION_MAX_CHARS.toLocaleString("en-US")} characters.`;
|
|
373
|
+
|
|
269
374
|
// src/tools/create-work-item.ts
|
|
270
375
|
function registerCreateWorkItem(server) {
|
|
271
376
|
server.registerTool(
|
|
@@ -273,17 +378,17 @@ function registerCreateWorkItem(server) {
|
|
|
273
378
|
{
|
|
274
379
|
description: "Create a new work item in a project. Hierarchy rules: epics live at the root; features go under epics; tasks go under epics or features (or root); subtasks must go under a task. Returns the new workItemId and its display ID (e.g. NIM-42).",
|
|
275
380
|
inputSchema: {
|
|
276
|
-
projectId:
|
|
277
|
-
type:
|
|
278
|
-
title:
|
|
279
|
-
description:
|
|
280
|
-
parentId:
|
|
381
|
+
projectId: z7.string().describe("projectId from list_projects"),
|
|
382
|
+
type: z7.enum(["epic", "feature", "task", "subtask"]).describe("Item type \u2014 drives hierarchy validation"),
|
|
383
|
+
title: z7.string().min(1).describe(TITLE_GUIDANCE),
|
|
384
|
+
description: z7.string().max(DESCRIPTION_MAX).optional().describe(DESCRIPTION_GUIDANCE),
|
|
385
|
+
parentId: z7.string().optional().describe(
|
|
281
386
|
"workItemId of the parent. Required for subtask, optional otherwise."
|
|
282
387
|
),
|
|
283
|
-
status:
|
|
388
|
+
status: z7.string().optional().describe(
|
|
284
389
|
STATUS_GUIDANCE
|
|
285
390
|
),
|
|
286
|
-
priority:
|
|
391
|
+
priority: z7.enum(["p0", "p1", "p2", "p3"]).optional().describe("p0 highest, p3 lowest. Defaults to p2.")
|
|
287
392
|
},
|
|
288
393
|
annotations: {
|
|
289
394
|
readOnlyHint: false,
|
|
@@ -306,23 +411,23 @@ function registerCreateWorkItem(server) {
|
|
|
306
411
|
}
|
|
307
412
|
|
|
308
413
|
// src/tools/create-project.ts
|
|
309
|
-
import { z as
|
|
414
|
+
import { z as z8 } from "zod";
|
|
310
415
|
function registerCreateProject(server) {
|
|
311
416
|
server.registerTool(
|
|
312
417
|
"create_project",
|
|
313
418
|
{
|
|
314
419
|
description: "Create a new project in an org. Prefix must be unique within the org and is uppercased (e.g. 'NIM' yields work items NIM-1, NIM-2\u2026). Caller becomes the project admin. Returns the new projectId.",
|
|
315
420
|
inputSchema: {
|
|
316
|
-
orgId:
|
|
317
|
-
name:
|
|
318
|
-
prefix:
|
|
421
|
+
orgId: z8.string().describe("orgId from list_orgs"),
|
|
422
|
+
name: z8.string().min(1).describe("Project name"),
|
|
423
|
+
prefix: z8.string().min(1).max(8).describe(
|
|
319
424
|
"Short uppercase prefix for work item IDs (e.g. 'NIM'). Unique per org."
|
|
320
425
|
),
|
|
321
|
-
description:
|
|
322
|
-
isPrivate:
|
|
426
|
+
description: z8.string().optional().describe("Optional markdown description"),
|
|
427
|
+
isPrivate: z8.boolean().optional().describe(
|
|
323
428
|
"If true, only explicitly added members see it. Defaults to false."
|
|
324
429
|
),
|
|
325
|
-
workflowTemplate:
|
|
430
|
+
workflowTemplate: z8.enum(["default", "simple", "kanban", "bug"]).optional().describe(
|
|
326
431
|
"Status workflow. Defaults to 'default' (Backlog/Todo/In Progress/Review/Done)."
|
|
327
432
|
)
|
|
328
433
|
},
|
|
@@ -347,7 +452,7 @@ function registerCreateProject(server) {
|
|
|
347
452
|
}
|
|
348
453
|
|
|
349
454
|
// src/tools/update-work-item.ts
|
|
350
|
-
import { z as
|
|
455
|
+
import { z as z9 } from "zod";
|
|
351
456
|
|
|
352
457
|
// src/tools/researchFormat.ts
|
|
353
458
|
var RESEARCH_FORMAT = `When appending automated research to a description, use EXACTLY this format, after the human-written text:
|
|
@@ -383,22 +488,25 @@ function registerUpdateWorkItem(server) {
|
|
|
383
488
|
{
|
|
384
489
|
description: "Update an existing work item: title, description, status, priority, parent, or review flags. Any update also stamps the item as AI-touched.",
|
|
385
490
|
inputSchema: {
|
|
386
|
-
workItemId:
|
|
387
|
-
title:
|
|
388
|
-
description:
|
|
389
|
-
"Full markdown body, replacing what is there. " + RESEARCH_FORMAT
|
|
491
|
+
workItemId: z9.string().describe("workItemId from list_work_items"),
|
|
492
|
+
title: z9.string().min(1).optional().describe(TITLE_GUIDANCE),
|
|
493
|
+
description: z9.string().max(DESCRIPTION_MAX).optional().describe(
|
|
494
|
+
"Full markdown body, replacing what is there. " + DESCRIPTION_GUIDANCE + " " + RESEARCH_FORMAT
|
|
390
495
|
),
|
|
391
|
-
status:
|
|
392
|
-
priority:
|
|
393
|
-
parentId:
|
|
394
|
-
needsReview:
|
|
496
|
+
status: z9.string().optional().describe(STATUS_GUIDANCE),
|
|
497
|
+
priority: z9.enum(["p0", "p1", "p2", "p3"]).optional(),
|
|
498
|
+
parentId: z9.string().nullable().optional().describe("New parent workItemId, null to move to root."),
|
|
499
|
+
needsReview: z9.boolean().optional().describe(
|
|
395
500
|
"Flag the item for manual human review \u2014 set true when your enrichment was thin or uncertain."
|
|
396
501
|
),
|
|
397
|
-
reviewReason:
|
|
502
|
+
reviewReason: z9.string().optional().describe(
|
|
398
503
|
"Short reason the item needs manual review (shown to the human)."
|
|
399
504
|
),
|
|
400
|
-
aiReviewRequested:
|
|
505
|
+
aiReviewRequested: z9.boolean().optional().describe(
|
|
401
506
|
"The human\u2192AI review flag. Set false to clear it after you have reviewed/enriched an item from list_ai_review_queue."
|
|
507
|
+
),
|
|
508
|
+
agentWorking: z9.boolean().optional().describe(
|
|
509
|
+
"Set true when you start working on this item: the app lights its row so the person can see it is in flight. It is a 15-minute lease renewed by any later MCP write on the item (updates, comments), by reading it (get_work_items, list_work_item_comments), or by heartbeat_work_item \u2014 call that every few minutes on a long task with no other Nimara calls, or the row goes quiet while you are still at it. Set false when you stop without finishing; moving the item to the last status clears it for you."
|
|
402
510
|
)
|
|
403
511
|
},
|
|
404
512
|
annotations: {
|
|
@@ -421,7 +529,7 @@ function registerUpdateWorkItem(server) {
|
|
|
421
529
|
}
|
|
422
530
|
|
|
423
531
|
// src/tools/move-work-item.ts
|
|
424
|
-
import { z as
|
|
532
|
+
import { z as z10 } from "zod";
|
|
425
533
|
function registerMoveWorkItem(server) {
|
|
426
534
|
server.registerTool(
|
|
427
535
|
"move_work_item",
|
|
@@ -432,8 +540,8 @@ function registerMoveWorkItem(server) {
|
|
|
432
540
|
// changes, and labels/milestones do not survive.
|
|
433
541
|
description: "Move a work item to a different project, taking its whole subtree with it \u2014 children and grandchildren move too, and the hierarchy is preserved. Both projects must be in the same organization, and you need write access to both. Not reversible by calling this again: the item is renumbered in the destination, so NIM-T44 might become SKA-T18 and the old ID stops resolving. Labels and milestone assignments are dropped, because both belong to the project being left; a status the destination doesn't define falls back to its first status. The response reports every old \u2192 new ID plus everything that was dropped or remapped. Comments, images, attached documents, PR links and history all follow the item. Call list_projects first for the target projectId.",
|
|
434
542
|
inputSchema: {
|
|
435
|
-
workItemId:
|
|
436
|
-
targetProjectId:
|
|
543
|
+
workItemId: z10.string().describe("workItemId from list_work_items \u2014 the item to move."),
|
|
544
|
+
targetProjectId: z10.string().describe(
|
|
437
545
|
"projectId from list_projects \u2014 the destination. Must be in the same org as the item's current project."
|
|
438
546
|
)
|
|
439
547
|
},
|
|
@@ -462,20 +570,20 @@ function registerMoveWorkItem(server) {
|
|
|
462
570
|
}
|
|
463
571
|
|
|
464
572
|
// src/tools/create-document.ts
|
|
465
|
-
import { z as
|
|
573
|
+
import { z as z11 } from "zod";
|
|
466
574
|
function registerCreateDocument(server) {
|
|
467
575
|
server.registerTool(
|
|
468
576
|
"create_document",
|
|
469
577
|
{
|
|
470
578
|
description: "Create a markdown project document. Use type 'prd' for project docs or 'fd' for feature/work-item documents.",
|
|
471
579
|
inputSchema: {
|
|
472
|
-
projectId:
|
|
473
|
-
title:
|
|
474
|
-
type:
|
|
580
|
+
projectId: z11.string().describe("projectId from list_projects"),
|
|
581
|
+
title: z11.string().min(1),
|
|
582
|
+
type: z11.enum(["prd", "fd"]).describe(
|
|
475
583
|
"Document type. 'fd' is stored as the app's feature document type."
|
|
476
584
|
),
|
|
477
|
-
workItemId:
|
|
478
|
-
content:
|
|
585
|
+
workItemId: z11.string().optional().describe("Required for fd documents; omitted for prd."),
|
|
586
|
+
content: z11.string().optional().describe("Optional initial markdown content.")
|
|
479
587
|
},
|
|
480
588
|
annotations: {
|
|
481
589
|
readOnlyHint: false,
|
|
@@ -497,18 +605,18 @@ function registerCreateDocument(server) {
|
|
|
497
605
|
}
|
|
498
606
|
|
|
499
607
|
// src/tools/update-document.ts
|
|
500
|
-
import { z as
|
|
608
|
+
import { z as z12 } from "zod";
|
|
501
609
|
function registerUpdateDocument(server) {
|
|
502
610
|
server.registerTool(
|
|
503
611
|
"update_document",
|
|
504
612
|
{
|
|
505
613
|
description: "Update an existing document's markdown content and/or title. Use this to fill in or revise a document created earlier (e.g. backfill content into an empty doc). documentId comes from list_documents. Provided content fully replaces the existing content.",
|
|
506
614
|
inputSchema: {
|
|
507
|
-
documentId:
|
|
508
|
-
content:
|
|
615
|
+
documentId: z12.string().describe("documentId from list_documents"),
|
|
616
|
+
content: z12.string().optional().describe(
|
|
509
617
|
"New markdown content. Replaces the document's existing content."
|
|
510
618
|
),
|
|
511
|
-
title:
|
|
619
|
+
title: z12.string().min(1).optional().describe("New title (optional).")
|
|
512
620
|
},
|
|
513
621
|
annotations: {
|
|
514
622
|
readOnlyHint: false,
|
|
@@ -530,15 +638,15 @@ function registerUpdateDocument(server) {
|
|
|
530
638
|
}
|
|
531
639
|
|
|
532
640
|
// src/tools/list-documents.ts
|
|
533
|
-
import { z as
|
|
641
|
+
import { z as z13 } from "zod";
|
|
534
642
|
function registerListDocuments(server) {
|
|
535
643
|
server.registerTool(
|
|
536
644
|
"list_documents",
|
|
537
645
|
{
|
|
538
646
|
description: "List PRD/FD markdown documents for a project, optionally filtered to one work item.",
|
|
539
647
|
inputSchema: {
|
|
540
|
-
projectId:
|
|
541
|
-
workItemId:
|
|
648
|
+
projectId: z13.string().describe("projectId from list_projects"),
|
|
649
|
+
workItemId: z13.string().optional().describe("Optional workItemId to list linked FD documents.")
|
|
542
650
|
},
|
|
543
651
|
annotations: {
|
|
544
652
|
readOnlyHint: true,
|
|
@@ -558,8 +666,8 @@ function registerListDocuments(server) {
|
|
|
558
666
|
}
|
|
559
667
|
|
|
560
668
|
// src/tools/create-project-link.ts
|
|
561
|
-
import { z as
|
|
562
|
-
var projectLinkCategory =
|
|
669
|
+
import { z as z14 } from "zod";
|
|
670
|
+
var projectLinkCategory = z14.enum([
|
|
563
671
|
"api",
|
|
564
672
|
"dashboard",
|
|
565
673
|
"documentation",
|
|
@@ -573,10 +681,10 @@ function registerCreateProjectLink(server) {
|
|
|
573
681
|
{
|
|
574
682
|
description: "Save a link on a project for resources like APIs, dashboards, documentation, repositories, or services.",
|
|
575
683
|
inputSchema: {
|
|
576
|
-
projectId:
|
|
577
|
-
title:
|
|
578
|
-
url:
|
|
579
|
-
description:
|
|
684
|
+
projectId: z14.string().describe("projectId from list_projects"),
|
|
685
|
+
title: z14.string().min(1).max(120),
|
|
686
|
+
url: z14.string().url().describe("http(s) URL to save"),
|
|
687
|
+
description: z14.string().max(500).optional(),
|
|
580
688
|
category: projectLinkCategory.optional().describe("Defaults to other.")
|
|
581
689
|
},
|
|
582
690
|
annotations: {
|
|
@@ -599,14 +707,14 @@ function registerCreateProjectLink(server) {
|
|
|
599
707
|
}
|
|
600
708
|
|
|
601
709
|
// src/tools/list-project-links.ts
|
|
602
|
-
import { z as
|
|
710
|
+
import { z as z15 } from "zod";
|
|
603
711
|
function registerListProjectLinks(server) {
|
|
604
712
|
server.registerTool(
|
|
605
713
|
"list_project_links",
|
|
606
714
|
{
|
|
607
715
|
description: "List saved links for a project, such as APIs, dashboards, docs, repositories, and services.",
|
|
608
716
|
inputSchema: {
|
|
609
|
-
projectId:
|
|
717
|
+
projectId: z15.string().describe("projectId from list_projects")
|
|
610
718
|
},
|
|
611
719
|
annotations: {
|
|
612
720
|
readOnlyHint: true,
|
|
@@ -625,18 +733,105 @@ function registerListProjectLinks(server) {
|
|
|
625
733
|
);
|
|
626
734
|
}
|
|
627
735
|
|
|
736
|
+
// src/tools/list-scratch-notes.ts
|
|
737
|
+
import { z as z16 } from "zod";
|
|
738
|
+
function registerListScratchNotes(server) {
|
|
739
|
+
server.registerTool(
|
|
740
|
+
"list_scratch_notes",
|
|
741
|
+
{
|
|
742
|
+
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.",
|
|
743
|
+
inputSchema: {
|
|
744
|
+
projectId: z16.string().describe("projectId from list_projects")
|
|
745
|
+
},
|
|
746
|
+
annotations: {
|
|
747
|
+
readOnlyHint: true,
|
|
748
|
+
openWorldHint: false
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
async ({ projectId }) => {
|
|
752
|
+
const notes = await getConvexClient().action(api.mcp.listScratchNotes, {
|
|
753
|
+
token: getToken(),
|
|
754
|
+
projectId
|
|
755
|
+
});
|
|
756
|
+
return {
|
|
757
|
+
content: [{ type: "text", text: JSON.stringify(notes, null, 2) }]
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// src/tools/resolve-scratch-note.ts
|
|
764
|
+
import { z as z17 } from "zod";
|
|
765
|
+
function registerResolveScratchNote(server) {
|
|
766
|
+
server.registerTool(
|
|
767
|
+
"resolve_scratch_note",
|
|
768
|
+
{
|
|
769
|
+
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.",
|
|
770
|
+
inputSchema: {
|
|
771
|
+
noteId: z17.string().describe("noteId from list_scratch_notes"),
|
|
772
|
+
workItemId: z17.string().optional().describe("The workItemId the note became, from create_work_item or list_work_items.")
|
|
773
|
+
},
|
|
774
|
+
annotations: {
|
|
775
|
+
readOnlyHint: false,
|
|
776
|
+
destructiveHint: false,
|
|
777
|
+
idempotentHint: true,
|
|
778
|
+
openWorldHint: false
|
|
779
|
+
}
|
|
780
|
+
},
|
|
781
|
+
async ({ noteId, workItemId }) => {
|
|
782
|
+
const result = await getConvexClient().action(api.mcp.resolveScratchNote, {
|
|
783
|
+
token: getToken(),
|
|
784
|
+
noteId,
|
|
785
|
+
workItemId
|
|
786
|
+
});
|
|
787
|
+
return {
|
|
788
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
789
|
+
};
|
|
790
|
+
}
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// src/tools/heartbeat-work-item.ts
|
|
795
|
+
import { z as z18 } from "zod";
|
|
796
|
+
function registerHeartbeatWorkItem(server) {
|
|
797
|
+
server.registerTool(
|
|
798
|
+
"heartbeat_work_item",
|
|
799
|
+
{
|
|
800
|
+
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.',
|
|
801
|
+
inputSchema: {
|
|
802
|
+
workItemId: z18.string().describe("workItemId of the item you are working on")
|
|
803
|
+
},
|
|
804
|
+
annotations: {
|
|
805
|
+
readOnlyHint: false,
|
|
806
|
+
destructiveHint: false,
|
|
807
|
+
idempotentHint: true,
|
|
808
|
+
openWorldHint: false
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
async ({ workItemId }) => {
|
|
812
|
+
const result = await getConvexClient().action(api.mcp.heartbeatWorkItem, {
|
|
813
|
+
token: getToken(),
|
|
814
|
+
workItemId
|
|
815
|
+
});
|
|
816
|
+
return {
|
|
817
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
|
|
628
823
|
// src/tools/add-work-item-image.ts
|
|
629
|
-
import { z as
|
|
824
|
+
import { z as z19 } from "zod";
|
|
630
825
|
function registerAddWorkItemImage(server) {
|
|
631
826
|
server.registerTool(
|
|
632
827
|
"add_work_item_image",
|
|
633
828
|
{
|
|
634
829
|
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.",
|
|
635
830
|
inputSchema: {
|
|
636
|
-
workItemId:
|
|
637
|
-
imageUrl:
|
|
638
|
-
filename:
|
|
639
|
-
altText:
|
|
831
|
+
workItemId: z19.string().describe("workItemId from list_work_items"),
|
|
832
|
+
imageUrl: z19.string().url().describe("Public or otherwise fetchable http(s) image URL"),
|
|
833
|
+
filename: z19.string().optional().describe("Optional display filename"),
|
|
834
|
+
altText: z19.string().optional().describe("Optional description/caption for the image")
|
|
640
835
|
},
|
|
641
836
|
annotations: {
|
|
642
837
|
readOnlyHint: false,
|
|
@@ -658,14 +853,14 @@ function registerAddWorkItemImage(server) {
|
|
|
658
853
|
}
|
|
659
854
|
|
|
660
855
|
// src/tools/list-work-item-images.ts
|
|
661
|
-
import { z as
|
|
856
|
+
import { z as z20 } from "zod";
|
|
662
857
|
function registerListWorkItemImages(server) {
|
|
663
858
|
server.registerTool(
|
|
664
859
|
"list_work_item_images",
|
|
665
860
|
{
|
|
666
861
|
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.",
|
|
667
862
|
inputSchema: {
|
|
668
|
-
workItemId:
|
|
863
|
+
workItemId: z20.string().describe("workItemId from list_work_items")
|
|
669
864
|
},
|
|
670
865
|
annotations: {
|
|
671
866
|
readOnlyHint: true,
|
|
@@ -688,16 +883,18 @@ function registerListWorkItemImages(server) {
|
|
|
688
883
|
}
|
|
689
884
|
|
|
690
885
|
// src/tools/add-work-item-comment.ts
|
|
691
|
-
import { z as
|
|
886
|
+
import { z as z21 } from "zod";
|
|
692
887
|
function registerAddWorkItemComment(server) {
|
|
693
888
|
server.registerTool(
|
|
694
889
|
"add_work_item_comment",
|
|
695
890
|
{
|
|
696
891
|
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.",
|
|
697
892
|
inputSchema: {
|
|
698
|
-
workItemId:
|
|
699
|
-
body:
|
|
700
|
-
|
|
893
|
+
workItemId: z21.string().describe("workItemId from list_work_items"),
|
|
894
|
+
body: z21.string().min(1).describe(
|
|
895
|
+
"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."
|
|
896
|
+
),
|
|
897
|
+
source: z21.enum(["user", "ai"]).optional().describe('Who authored it; defaults to "ai" for MCP callers')
|
|
701
898
|
},
|
|
702
899
|
annotations: {
|
|
703
900
|
readOnlyHint: false,
|
|
@@ -722,14 +919,14 @@ function registerAddWorkItemComment(server) {
|
|
|
722
919
|
}
|
|
723
920
|
|
|
724
921
|
// src/tools/list-work-item-comments.ts
|
|
725
|
-
import { z as
|
|
922
|
+
import { z as z22 } from "zod";
|
|
726
923
|
function registerListWorkItemComments(server) {
|
|
727
924
|
server.registerTool(
|
|
728
925
|
"list_work_item_comments",
|
|
729
926
|
{
|
|
730
927
|
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.",
|
|
731
928
|
inputSchema: {
|
|
732
|
-
workItemId:
|
|
929
|
+
workItemId: z22.string().describe("workItemId from list_work_items")
|
|
733
930
|
},
|
|
734
931
|
annotations: {
|
|
735
932
|
readOnlyHint: true,
|
|
@@ -752,14 +949,14 @@ function registerListWorkItemComments(server) {
|
|
|
752
949
|
}
|
|
753
950
|
|
|
754
951
|
// src/tools/list-validations.ts
|
|
755
|
-
import { z as
|
|
952
|
+
import { z as z23 } from "zod";
|
|
756
953
|
function registerListValidations(server) {
|
|
757
954
|
server.registerTool(
|
|
758
955
|
"list_validations",
|
|
759
956
|
{
|
|
760
957
|
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.",
|
|
761
958
|
inputSchema: {
|
|
762
|
-
projectId:
|
|
959
|
+
projectId: z23.string().describe("projectId from list_projects")
|
|
763
960
|
},
|
|
764
961
|
annotations: {
|
|
765
962
|
readOnlyHint: true,
|
|
@@ -780,15 +977,15 @@ function registerListValidations(server) {
|
|
|
780
977
|
}
|
|
781
978
|
|
|
782
979
|
// src/tools/record-validation.ts
|
|
783
|
-
import { z as
|
|
980
|
+
import { z as z24 } from "zod";
|
|
784
981
|
function registerRecordValidation(server) {
|
|
785
982
|
server.registerTool(
|
|
786
983
|
"record_validation",
|
|
787
984
|
{
|
|
788
985
|
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.",
|
|
789
986
|
inputSchema: {
|
|
790
|
-
validationTaskId:
|
|
791
|
-
result:
|
|
987
|
+
validationTaskId: z24.string().describe("validationTaskId from list_validations"),
|
|
988
|
+
result: z24.enum(["pass", "fail"]).describe("Outcome of running the test")
|
|
792
989
|
},
|
|
793
990
|
annotations: {
|
|
794
991
|
readOnlyHint: false,
|
|
@@ -812,14 +1009,14 @@ function registerRecordValidation(server) {
|
|
|
812
1009
|
}
|
|
813
1010
|
|
|
814
1011
|
// src/tools/mark-system-changed.ts
|
|
815
|
-
import { z as
|
|
1012
|
+
import { z as z25 } from "zod";
|
|
816
1013
|
function registerMarkSystemChanged(server) {
|
|
817
1014
|
server.registerTool(
|
|
818
1015
|
"mark_system_changed",
|
|
819
1016
|
{
|
|
820
1017
|
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.",
|
|
821
1018
|
inputSchema: {
|
|
822
|
-
systemId:
|
|
1019
|
+
systemId: z25.string().describe("systemId from list_validations")
|
|
823
1020
|
},
|
|
824
1021
|
annotations: {
|
|
825
1022
|
readOnlyHint: false,
|
|
@@ -842,16 +1039,16 @@ function registerMarkSystemChanged(server) {
|
|
|
842
1039
|
}
|
|
843
1040
|
|
|
844
1041
|
// src/tools/create-system.ts
|
|
845
|
-
import { z as
|
|
1042
|
+
import { z as z26 } from "zod";
|
|
846
1043
|
function registerCreateSystem(server) {
|
|
847
1044
|
server.registerTool(
|
|
848
1045
|
"create_system",
|
|
849
1046
|
{
|
|
850
1047
|
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.",
|
|
851
1048
|
inputSchema: {
|
|
852
|
-
projectId:
|
|
853
|
-
name:
|
|
854
|
-
description:
|
|
1049
|
+
projectId: z26.string().describe("projectId from list_projects"),
|
|
1050
|
+
name: z26.string().min(1).describe("Short system name, e.g. 'Auth'"),
|
|
1051
|
+
description: z26.string().optional().describe("Optional details")
|
|
855
1052
|
},
|
|
856
1053
|
annotations: {
|
|
857
1054
|
readOnlyHint: false,
|
|
@@ -874,20 +1071,20 @@ function registerCreateSystem(server) {
|
|
|
874
1071
|
}
|
|
875
1072
|
|
|
876
1073
|
// src/tools/create-validation-task.ts
|
|
877
|
-
import { z as
|
|
1074
|
+
import { z as z27 } from "zod";
|
|
878
1075
|
function registerCreateValidationTask(server) {
|
|
879
1076
|
server.registerTool(
|
|
880
1077
|
"create_validation_task",
|
|
881
1078
|
{
|
|
882
1079
|
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.",
|
|
883
1080
|
inputSchema: {
|
|
884
|
-
projectId:
|
|
885
|
-
title:
|
|
886
|
-
description:
|
|
887
|
-
kind:
|
|
1081
|
+
projectId: z27.string().describe("projectId from list_projects"),
|
|
1082
|
+
title: z27.string().min(1).describe("What this test validates"),
|
|
1083
|
+
description: z27.string().optional().describe("Optional steps / details"),
|
|
1084
|
+
kind: z27.enum(["auto", "manual"]).optional().describe(
|
|
888
1085
|
"'auto' if runnable by a test runner, 'manual' if a human checks it. Defaults to manual."
|
|
889
1086
|
),
|
|
890
|
-
workItemId:
|
|
1087
|
+
workItemId: z27.string().optional().describe(
|
|
891
1088
|
"Optional workItemId to link this test to a feature for traceability"
|
|
892
1089
|
)
|
|
893
1090
|
},
|
|
@@ -912,15 +1109,15 @@ function registerCreateValidationTask(server) {
|
|
|
912
1109
|
}
|
|
913
1110
|
|
|
914
1111
|
// src/tools/add-system-to-task.ts
|
|
915
|
-
import { z as
|
|
1112
|
+
import { z as z28 } from "zod";
|
|
916
1113
|
function registerAddSystemToTask(server) {
|
|
917
1114
|
server.registerTool(
|
|
918
1115
|
"add_system_to_task",
|
|
919
1116
|
{
|
|
920
1117
|
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.",
|
|
921
1118
|
inputSchema: {
|
|
922
|
-
validationTaskId:
|
|
923
|
-
systemId:
|
|
1119
|
+
validationTaskId: z28.string().describe("validationTaskId from list_validations"),
|
|
1120
|
+
systemId: z28.string().describe("systemId from list_validations")
|
|
924
1121
|
},
|
|
925
1122
|
annotations: {
|
|
926
1123
|
readOnlyHint: false,
|
|
@@ -944,15 +1141,15 @@ function registerAddSystemToTask(server) {
|
|
|
944
1141
|
}
|
|
945
1142
|
|
|
946
1143
|
// src/tools/add-validation-dependency.ts
|
|
947
|
-
import { z as
|
|
1144
|
+
import { z as z29 } from "zod";
|
|
948
1145
|
function registerAddValidationDependency(server) {
|
|
949
1146
|
server.registerTool(
|
|
950
1147
|
"add_validation_dependency",
|
|
951
1148
|
{
|
|
952
1149
|
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.",
|
|
953
1150
|
inputSchema: {
|
|
954
|
-
validationTaskId:
|
|
955
|
-
dependsOnTaskId:
|
|
1151
|
+
validationTaskId: z29.string().describe("The dependent (downstream) task"),
|
|
1152
|
+
dependsOnTaskId: z29.string().describe("The dependency (upstream) task it relies on")
|
|
956
1153
|
},
|
|
957
1154
|
annotations: {
|
|
958
1155
|
readOnlyHint: false,
|
|
@@ -976,14 +1173,14 @@ function registerAddValidationDependency(server) {
|
|
|
976
1173
|
}
|
|
977
1174
|
|
|
978
1175
|
// src/tools/list-labels.ts
|
|
979
|
-
import { z as
|
|
1176
|
+
import { z as z30 } from "zod";
|
|
980
1177
|
function registerListLabels(server) {
|
|
981
1178
|
server.registerTool(
|
|
982
1179
|
"list_labels",
|
|
983
1180
|
{
|
|
984
1181
|
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.",
|
|
985
1182
|
inputSchema: {
|
|
986
|
-
projectId:
|
|
1183
|
+
projectId: z30.string().describe("projectId from list_projects")
|
|
987
1184
|
},
|
|
988
1185
|
annotations: {
|
|
989
1186
|
readOnlyHint: true,
|
|
@@ -1003,16 +1200,16 @@ function registerListLabels(server) {
|
|
|
1003
1200
|
}
|
|
1004
1201
|
|
|
1005
1202
|
// src/tools/create-label.ts
|
|
1006
|
-
import { z as
|
|
1203
|
+
import { z as z31 } from "zod";
|
|
1007
1204
|
function registerCreateLabel(server) {
|
|
1008
1205
|
server.registerTool(
|
|
1009
1206
|
"create_label",
|
|
1010
1207
|
{
|
|
1011
1208
|
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.",
|
|
1012
1209
|
inputSchema: {
|
|
1013
|
-
projectId:
|
|
1014
|
-
name:
|
|
1015
|
-
color:
|
|
1210
|
+
projectId: z31.string().describe("projectId from list_projects"),
|
|
1211
|
+
name: z31.string().min(1).describe("Label name, unique within the project"),
|
|
1212
|
+
color: z31.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Hex color like #2563eb. Defaults to a neutral slate.")
|
|
1016
1213
|
},
|
|
1017
1214
|
annotations: {
|
|
1018
1215
|
readOnlyHint: false,
|
|
@@ -1034,15 +1231,15 @@ function registerCreateLabel(server) {
|
|
|
1034
1231
|
}
|
|
1035
1232
|
|
|
1036
1233
|
// src/tools/add-label-to-work-item.ts
|
|
1037
|
-
import { z as
|
|
1234
|
+
import { z as z32 } from "zod";
|
|
1038
1235
|
function registerAddLabelToWorkItem(server) {
|
|
1039
1236
|
server.registerTool(
|
|
1040
1237
|
"add_label_to_work_item",
|
|
1041
1238
|
{
|
|
1042
1239
|
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.",
|
|
1043
1240
|
inputSchema: {
|
|
1044
|
-
workItemId:
|
|
1045
|
-
labelId:
|
|
1241
|
+
workItemId: z32.string().describe("workItemId from list_work_items"),
|
|
1242
|
+
labelId: z32.string().describe("labelId from list_labels or create_label")
|
|
1046
1243
|
},
|
|
1047
1244
|
annotations: {
|
|
1048
1245
|
readOnlyHint: false,
|
|
@@ -1065,15 +1262,15 @@ function registerAddLabelToWorkItem(server) {
|
|
|
1065
1262
|
}
|
|
1066
1263
|
|
|
1067
1264
|
// src/tools/remove-label-from-work-item.ts
|
|
1068
|
-
import { z as
|
|
1265
|
+
import { z as z33 } from "zod";
|
|
1069
1266
|
function registerRemoveLabelFromWorkItem(server) {
|
|
1070
1267
|
server.registerTool(
|
|
1071
1268
|
"remove_label_from_work_item",
|
|
1072
1269
|
{
|
|
1073
1270
|
description: "Remove a label from a work item (idempotent \u2014 a no-op if it wasn't attached).",
|
|
1074
1271
|
inputSchema: {
|
|
1075
|
-
workItemId:
|
|
1076
|
-
labelId:
|
|
1272
|
+
workItemId: z33.string().describe("workItemId from list_work_items"),
|
|
1273
|
+
labelId: z33.string().describe("labelId from list_labels")
|
|
1077
1274
|
},
|
|
1078
1275
|
annotations: {
|
|
1079
1276
|
readOnlyHint: false,
|
|
@@ -1096,14 +1293,14 @@ function registerRemoveLabelFromWorkItem(server) {
|
|
|
1096
1293
|
}
|
|
1097
1294
|
|
|
1098
1295
|
// src/tools/list-milestones.ts
|
|
1099
|
-
import { z as
|
|
1296
|
+
import { z as z34 } from "zod";
|
|
1100
1297
|
function registerListMilestones(server) {
|
|
1101
1298
|
server.registerTool(
|
|
1102
1299
|
"list_milestones",
|
|
1103
1300
|
{
|
|
1104
1301
|
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.",
|
|
1105
1302
|
inputSchema: {
|
|
1106
|
-
projectId:
|
|
1303
|
+
projectId: z34.string().describe("projectId from list_projects")
|
|
1107
1304
|
},
|
|
1108
1305
|
annotations: {
|
|
1109
1306
|
readOnlyHint: true,
|
|
@@ -1123,17 +1320,17 @@ function registerListMilestones(server) {
|
|
|
1123
1320
|
}
|
|
1124
1321
|
|
|
1125
1322
|
// src/tools/create-milestone.ts
|
|
1126
|
-
import { z as
|
|
1323
|
+
import { z as z35 } from "zod";
|
|
1127
1324
|
function registerCreateMilestone(server) {
|
|
1128
1325
|
server.registerTool(
|
|
1129
1326
|
"create_milestone",
|
|
1130
1327
|
{
|
|
1131
1328
|
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').",
|
|
1132
1329
|
inputSchema: {
|
|
1133
|
-
projectId:
|
|
1134
|
-
name:
|
|
1135
|
-
description:
|
|
1136
|
-
dueDate:
|
|
1330
|
+
projectId: z35.string().describe("projectId from list_projects"),
|
|
1331
|
+
name: z35.string().min(1).describe("Milestone name"),
|
|
1332
|
+
description: z35.string().optional().describe("Optional markdown description"),
|
|
1333
|
+
dueDate: z35.number().optional().describe("Optional due date as a unix timestamp in milliseconds")
|
|
1137
1334
|
},
|
|
1138
1335
|
annotations: {
|
|
1139
1336
|
readOnlyHint: false,
|
|
@@ -1155,15 +1352,15 @@ function registerCreateMilestone(server) {
|
|
|
1155
1352
|
}
|
|
1156
1353
|
|
|
1157
1354
|
// src/tools/add-item-to-milestone.ts
|
|
1158
|
-
import { z as
|
|
1355
|
+
import { z as z36 } from "zod";
|
|
1159
1356
|
function registerAddItemToMilestone(server) {
|
|
1160
1357
|
server.registerTool(
|
|
1161
1358
|
"add_item_to_milestone",
|
|
1162
1359
|
{
|
|
1163
1360
|
description: "Associate a work item with a milestone (idempotent). Both must be in the same project.",
|
|
1164
1361
|
inputSchema: {
|
|
1165
|
-
milestoneId:
|
|
1166
|
-
workItemId:
|
|
1362
|
+
milestoneId: z36.string().describe("milestoneId from list_milestones or create_milestone"),
|
|
1363
|
+
workItemId: z36.string().describe("workItemId from list_work_items")
|
|
1167
1364
|
},
|
|
1168
1365
|
annotations: {
|
|
1169
1366
|
readOnlyHint: false,
|
|
@@ -1189,15 +1386,15 @@ function registerAddItemToMilestone(server) {
|
|
|
1189
1386
|
}
|
|
1190
1387
|
|
|
1191
1388
|
// src/tools/remove-item-from-milestone.ts
|
|
1192
|
-
import { z as
|
|
1389
|
+
import { z as z37 } from "zod";
|
|
1193
1390
|
function registerRemoveItemFromMilestone(server) {
|
|
1194
1391
|
server.registerTool(
|
|
1195
1392
|
"remove_item_from_milestone",
|
|
1196
1393
|
{
|
|
1197
1394
|
description: "Remove a work item's association with a milestone (idempotent \u2014 a no-op if it wasn't associated).",
|
|
1198
1395
|
inputSchema: {
|
|
1199
|
-
milestoneId:
|
|
1200
|
-
workItemId:
|
|
1396
|
+
milestoneId: z37.string().describe("milestoneId from list_milestones"),
|
|
1397
|
+
workItemId: z37.string().describe("workItemId from list_work_items")
|
|
1201
1398
|
},
|
|
1202
1399
|
annotations: {
|
|
1203
1400
|
readOnlyHint: false,
|
|
@@ -1224,16 +1421,21 @@ function registerRemoveItemFromMilestone(server) {
|
|
|
1224
1421
|
|
|
1225
1422
|
// src/server.ts
|
|
1226
1423
|
function createServer() {
|
|
1227
|
-
const server = new McpServer(
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1424
|
+
const server = new McpServer(
|
|
1425
|
+
{
|
|
1426
|
+
name: "nimara",
|
|
1427
|
+
version: package_default.version
|
|
1428
|
+
},
|
|
1429
|
+
// Sent at connect time, before any tool is read (NIM-T189).
|
|
1430
|
+
{ instructions: INSTRUCTIONS }
|
|
1431
|
+
);
|
|
1231
1432
|
registerListOrgs(server);
|
|
1232
1433
|
registerListProjects(server);
|
|
1233
1434
|
registerListWorkItems(server);
|
|
1234
1435
|
registerGetWorkItems(server);
|
|
1235
1436
|
registerSearchWorkItems(server);
|
|
1236
1437
|
registerListAiReviewQueue(server);
|
|
1438
|
+
registerGetActiveBlock(server);
|
|
1237
1439
|
registerCreateProject(server);
|
|
1238
1440
|
registerCreateWorkItem(server);
|
|
1239
1441
|
registerUpdateWorkItem(server);
|
|
@@ -1243,6 +1445,9 @@ function createServer() {
|
|
|
1243
1445
|
registerListDocuments(server);
|
|
1244
1446
|
registerCreateProjectLink(server);
|
|
1245
1447
|
registerListProjectLinks(server);
|
|
1448
|
+
registerListScratchNotes(server);
|
|
1449
|
+
registerResolveScratchNote(server);
|
|
1450
|
+
registerHeartbeatWorkItem(server);
|
|
1246
1451
|
registerAddWorkItemImage(server);
|
|
1247
1452
|
registerListWorkItemImages(server);
|
|
1248
1453
|
registerAddWorkItemComment(server);
|