@nimara-app/mcp 0.2.0 → 0.3.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 -0
- package/dist/index.js +211 -112
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -230,7 +230,7 @@ function registerListAiReviewQueue(server) {
|
|
|
230
230
|
server.registerTool(
|
|
231
231
|
"list_ai_review_queue",
|
|
232
232
|
{
|
|
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.",
|
|
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. 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
234
|
inputSchema: {
|
|
235
235
|
projectId: z5.string().describe("projectId from list_projects"),
|
|
236
236
|
limit: z5.number().int().min(1).max(200).optional().describe("Items per page (default 50, max 200)."),
|
|
@@ -257,8 +257,35 @@ function registerListAiReviewQueue(server) {
|
|
|
257
257
|
);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
// src/tools/
|
|
260
|
+
// src/tools/get-active-block.ts
|
|
261
261
|
import { z as z6 } from "zod";
|
|
262
|
+
function registerGetActiveBlock(server) {
|
|
263
|
+
server.registerTool(
|
|
264
|
+
"get_active_block",
|
|
265
|
+
{
|
|
266
|
+
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.",
|
|
267
|
+
inputSchema: {
|
|
268
|
+
projectId: z6.string().describe("projectId from list_projects")
|
|
269
|
+
},
|
|
270
|
+
annotations: {
|
|
271
|
+
readOnlyHint: true,
|
|
272
|
+
openWorldHint: false
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
async ({ projectId }) => {
|
|
276
|
+
const block = await getConvexClient().action(api.mcp.getActiveBlock, {
|
|
277
|
+
token: getToken(),
|
|
278
|
+
projectId
|
|
279
|
+
});
|
|
280
|
+
return {
|
|
281
|
+
content: [{ type: "text", text: JSON.stringify(block, null, 2) }]
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/tools/create-work-item.ts
|
|
288
|
+
import { z as z7 } from "zod";
|
|
262
289
|
|
|
263
290
|
// src/tools/titleGuidance.ts
|
|
264
291
|
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 +293,14 @@ var TITLE_GUIDANCE = 'A title a teammate can understand at a glance, without ope
|
|
|
266
293
|
// src/tools/statusGuidance.ts
|
|
267
294
|
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
295
|
|
|
296
|
+
// ../convex/convex/lib/descriptionLimits.ts
|
|
297
|
+
var DESCRIPTION_MAX_CHARS = 32e3;
|
|
298
|
+
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.`;
|
|
299
|
+
|
|
300
|
+
// src/tools/descriptionGuidance.ts
|
|
301
|
+
var DESCRIPTION_MAX = DESCRIPTION_MAX_CHARS;
|
|
302
|
+
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.`;
|
|
303
|
+
|
|
269
304
|
// src/tools/create-work-item.ts
|
|
270
305
|
function registerCreateWorkItem(server) {
|
|
271
306
|
server.registerTool(
|
|
@@ -273,17 +308,17 @@ function registerCreateWorkItem(server) {
|
|
|
273
308
|
{
|
|
274
309
|
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
310
|
inputSchema: {
|
|
276
|
-
projectId:
|
|
277
|
-
type:
|
|
278
|
-
title:
|
|
279
|
-
description:
|
|
280
|
-
parentId:
|
|
311
|
+
projectId: z7.string().describe("projectId from list_projects"),
|
|
312
|
+
type: z7.enum(["epic", "feature", "task", "subtask"]).describe("Item type \u2014 drives hierarchy validation"),
|
|
313
|
+
title: z7.string().min(1).describe(TITLE_GUIDANCE),
|
|
314
|
+
description: z7.string().max(DESCRIPTION_MAX).optional().describe(DESCRIPTION_GUIDANCE),
|
|
315
|
+
parentId: z7.string().optional().describe(
|
|
281
316
|
"workItemId of the parent. Required for subtask, optional otherwise."
|
|
282
317
|
),
|
|
283
|
-
status:
|
|
318
|
+
status: z7.string().optional().describe(
|
|
284
319
|
STATUS_GUIDANCE
|
|
285
320
|
),
|
|
286
|
-
priority:
|
|
321
|
+
priority: z7.enum(["p0", "p1", "p2", "p3"]).optional().describe("p0 highest, p3 lowest. Defaults to p2.")
|
|
287
322
|
},
|
|
288
323
|
annotations: {
|
|
289
324
|
readOnlyHint: false,
|
|
@@ -306,23 +341,23 @@ function registerCreateWorkItem(server) {
|
|
|
306
341
|
}
|
|
307
342
|
|
|
308
343
|
// src/tools/create-project.ts
|
|
309
|
-
import { z as
|
|
344
|
+
import { z as z8 } from "zod";
|
|
310
345
|
function registerCreateProject(server) {
|
|
311
346
|
server.registerTool(
|
|
312
347
|
"create_project",
|
|
313
348
|
{
|
|
314
349
|
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
350
|
inputSchema: {
|
|
316
|
-
orgId:
|
|
317
|
-
name:
|
|
318
|
-
prefix:
|
|
351
|
+
orgId: z8.string().describe("orgId from list_orgs"),
|
|
352
|
+
name: z8.string().min(1).describe("Project name"),
|
|
353
|
+
prefix: z8.string().min(1).max(8).describe(
|
|
319
354
|
"Short uppercase prefix for work item IDs (e.g. 'NIM'). Unique per org."
|
|
320
355
|
),
|
|
321
|
-
description:
|
|
322
|
-
isPrivate:
|
|
356
|
+
description: z8.string().optional().describe("Optional markdown description"),
|
|
357
|
+
isPrivate: z8.boolean().optional().describe(
|
|
323
358
|
"If true, only explicitly added members see it. Defaults to false."
|
|
324
359
|
),
|
|
325
|
-
workflowTemplate:
|
|
360
|
+
workflowTemplate: z8.enum(["default", "simple", "kanban", "bug"]).optional().describe(
|
|
326
361
|
"Status workflow. Defaults to 'default' (Backlog/Todo/In Progress/Review/Done)."
|
|
327
362
|
)
|
|
328
363
|
},
|
|
@@ -347,7 +382,7 @@ function registerCreateProject(server) {
|
|
|
347
382
|
}
|
|
348
383
|
|
|
349
384
|
// src/tools/update-work-item.ts
|
|
350
|
-
import { z as
|
|
385
|
+
import { z as z9 } from "zod";
|
|
351
386
|
|
|
352
387
|
// src/tools/researchFormat.ts
|
|
353
388
|
var RESEARCH_FORMAT = `When appending automated research to a description, use EXACTLY this format, after the human-written text:
|
|
@@ -383,22 +418,25 @@ function registerUpdateWorkItem(server) {
|
|
|
383
418
|
{
|
|
384
419
|
description: "Update an existing work item: title, description, status, priority, parent, or review flags. Any update also stamps the item as AI-touched.",
|
|
385
420
|
inputSchema: {
|
|
386
|
-
workItemId:
|
|
387
|
-
title:
|
|
388
|
-
description:
|
|
389
|
-
"Full markdown body, replacing what is there. " + RESEARCH_FORMAT
|
|
421
|
+
workItemId: z9.string().describe("workItemId from list_work_items"),
|
|
422
|
+
title: z9.string().min(1).optional().describe(TITLE_GUIDANCE),
|
|
423
|
+
description: z9.string().max(DESCRIPTION_MAX).optional().describe(
|
|
424
|
+
"Full markdown body, replacing what is there. " + DESCRIPTION_GUIDANCE + " " + RESEARCH_FORMAT
|
|
390
425
|
),
|
|
391
|
-
status:
|
|
392
|
-
priority:
|
|
393
|
-
parentId:
|
|
394
|
-
needsReview:
|
|
426
|
+
status: z9.string().optional().describe(STATUS_GUIDANCE),
|
|
427
|
+
priority: z9.enum(["p0", "p1", "p2", "p3"]).optional(),
|
|
428
|
+
parentId: z9.string().nullable().optional().describe("New parent workItemId, null to move to root."),
|
|
429
|
+
needsReview: z9.boolean().optional().describe(
|
|
395
430
|
"Flag the item for manual human review \u2014 set true when your enrichment was thin or uncertain."
|
|
396
431
|
),
|
|
397
|
-
reviewReason:
|
|
432
|
+
reviewReason: z9.string().optional().describe(
|
|
398
433
|
"Short reason the item needs manual review (shown to the human)."
|
|
399
434
|
),
|
|
400
|
-
aiReviewRequested:
|
|
435
|
+
aiReviewRequested: z9.boolean().optional().describe(
|
|
401
436
|
"The human\u2192AI review flag. Set false to clear it after you have reviewed/enriched an item from list_ai_review_queue."
|
|
437
|
+
),
|
|
438
|
+
agentWorking: z9.boolean().optional().describe(
|
|
439
|
+
"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 that every later MCP write on the item (updates, comments) renews, so you only need to set it once at the start; set false when you stop without finishing, and moving the item to the last status clears it for you."
|
|
402
440
|
)
|
|
403
441
|
},
|
|
404
442
|
annotations: {
|
|
@@ -421,7 +459,7 @@ function registerUpdateWorkItem(server) {
|
|
|
421
459
|
}
|
|
422
460
|
|
|
423
461
|
// src/tools/move-work-item.ts
|
|
424
|
-
import { z as
|
|
462
|
+
import { z as z10 } from "zod";
|
|
425
463
|
function registerMoveWorkItem(server) {
|
|
426
464
|
server.registerTool(
|
|
427
465
|
"move_work_item",
|
|
@@ -432,8 +470,8 @@ function registerMoveWorkItem(server) {
|
|
|
432
470
|
// changes, and labels/milestones do not survive.
|
|
433
471
|
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
472
|
inputSchema: {
|
|
435
|
-
workItemId:
|
|
436
|
-
targetProjectId:
|
|
473
|
+
workItemId: z10.string().describe("workItemId from list_work_items \u2014 the item to move."),
|
|
474
|
+
targetProjectId: z10.string().describe(
|
|
437
475
|
"projectId from list_projects \u2014 the destination. Must be in the same org as the item's current project."
|
|
438
476
|
)
|
|
439
477
|
},
|
|
@@ -462,20 +500,20 @@ function registerMoveWorkItem(server) {
|
|
|
462
500
|
}
|
|
463
501
|
|
|
464
502
|
// src/tools/create-document.ts
|
|
465
|
-
import { z as
|
|
503
|
+
import { z as z11 } from "zod";
|
|
466
504
|
function registerCreateDocument(server) {
|
|
467
505
|
server.registerTool(
|
|
468
506
|
"create_document",
|
|
469
507
|
{
|
|
470
508
|
description: "Create a markdown project document. Use type 'prd' for project docs or 'fd' for feature/work-item documents.",
|
|
471
509
|
inputSchema: {
|
|
472
|
-
projectId:
|
|
473
|
-
title:
|
|
474
|
-
type:
|
|
510
|
+
projectId: z11.string().describe("projectId from list_projects"),
|
|
511
|
+
title: z11.string().min(1),
|
|
512
|
+
type: z11.enum(["prd", "fd"]).describe(
|
|
475
513
|
"Document type. 'fd' is stored as the app's feature document type."
|
|
476
514
|
),
|
|
477
|
-
workItemId:
|
|
478
|
-
content:
|
|
515
|
+
workItemId: z11.string().optional().describe("Required for fd documents; omitted for prd."),
|
|
516
|
+
content: z11.string().optional().describe("Optional initial markdown content.")
|
|
479
517
|
},
|
|
480
518
|
annotations: {
|
|
481
519
|
readOnlyHint: false,
|
|
@@ -497,18 +535,18 @@ function registerCreateDocument(server) {
|
|
|
497
535
|
}
|
|
498
536
|
|
|
499
537
|
// src/tools/update-document.ts
|
|
500
|
-
import { z as
|
|
538
|
+
import { z as z12 } from "zod";
|
|
501
539
|
function registerUpdateDocument(server) {
|
|
502
540
|
server.registerTool(
|
|
503
541
|
"update_document",
|
|
504
542
|
{
|
|
505
543
|
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
544
|
inputSchema: {
|
|
507
|
-
documentId:
|
|
508
|
-
content:
|
|
545
|
+
documentId: z12.string().describe("documentId from list_documents"),
|
|
546
|
+
content: z12.string().optional().describe(
|
|
509
547
|
"New markdown content. Replaces the document's existing content."
|
|
510
548
|
),
|
|
511
|
-
title:
|
|
549
|
+
title: z12.string().min(1).optional().describe("New title (optional).")
|
|
512
550
|
},
|
|
513
551
|
annotations: {
|
|
514
552
|
readOnlyHint: false,
|
|
@@ -530,15 +568,15 @@ function registerUpdateDocument(server) {
|
|
|
530
568
|
}
|
|
531
569
|
|
|
532
570
|
// src/tools/list-documents.ts
|
|
533
|
-
import { z as
|
|
571
|
+
import { z as z13 } from "zod";
|
|
534
572
|
function registerListDocuments(server) {
|
|
535
573
|
server.registerTool(
|
|
536
574
|
"list_documents",
|
|
537
575
|
{
|
|
538
576
|
description: "List PRD/FD markdown documents for a project, optionally filtered to one work item.",
|
|
539
577
|
inputSchema: {
|
|
540
|
-
projectId:
|
|
541
|
-
workItemId:
|
|
578
|
+
projectId: z13.string().describe("projectId from list_projects"),
|
|
579
|
+
workItemId: z13.string().optional().describe("Optional workItemId to list linked FD documents.")
|
|
542
580
|
},
|
|
543
581
|
annotations: {
|
|
544
582
|
readOnlyHint: true,
|
|
@@ -558,8 +596,8 @@ function registerListDocuments(server) {
|
|
|
558
596
|
}
|
|
559
597
|
|
|
560
598
|
// src/tools/create-project-link.ts
|
|
561
|
-
import { z as
|
|
562
|
-
var projectLinkCategory =
|
|
599
|
+
import { z as z14 } from "zod";
|
|
600
|
+
var projectLinkCategory = z14.enum([
|
|
563
601
|
"api",
|
|
564
602
|
"dashboard",
|
|
565
603
|
"documentation",
|
|
@@ -573,10 +611,10 @@ function registerCreateProjectLink(server) {
|
|
|
573
611
|
{
|
|
574
612
|
description: "Save a link on a project for resources like APIs, dashboards, documentation, repositories, or services.",
|
|
575
613
|
inputSchema: {
|
|
576
|
-
projectId:
|
|
577
|
-
title:
|
|
578
|
-
url:
|
|
579
|
-
description:
|
|
614
|
+
projectId: z14.string().describe("projectId from list_projects"),
|
|
615
|
+
title: z14.string().min(1).max(120),
|
|
616
|
+
url: z14.string().url().describe("http(s) URL to save"),
|
|
617
|
+
description: z14.string().max(500).optional(),
|
|
580
618
|
category: projectLinkCategory.optional().describe("Defaults to other.")
|
|
581
619
|
},
|
|
582
620
|
annotations: {
|
|
@@ -599,14 +637,14 @@ function registerCreateProjectLink(server) {
|
|
|
599
637
|
}
|
|
600
638
|
|
|
601
639
|
// src/tools/list-project-links.ts
|
|
602
|
-
import { z as
|
|
640
|
+
import { z as z15 } from "zod";
|
|
603
641
|
function registerListProjectLinks(server) {
|
|
604
642
|
server.registerTool(
|
|
605
643
|
"list_project_links",
|
|
606
644
|
{
|
|
607
645
|
description: "List saved links for a project, such as APIs, dashboards, docs, repositories, and services.",
|
|
608
646
|
inputSchema: {
|
|
609
|
-
projectId:
|
|
647
|
+
projectId: z15.string().describe("projectId from list_projects")
|
|
610
648
|
},
|
|
611
649
|
annotations: {
|
|
612
650
|
readOnlyHint: true,
|
|
@@ -625,18 +663,76 @@ function registerListProjectLinks(server) {
|
|
|
625
663
|
);
|
|
626
664
|
}
|
|
627
665
|
|
|
666
|
+
// src/tools/list-scratch-notes.ts
|
|
667
|
+
import { z as z16 } from "zod";
|
|
668
|
+
function registerListScratchNotes(server) {
|
|
669
|
+
server.registerTool(
|
|
670
|
+
"list_scratch_notes",
|
|
671
|
+
{
|
|
672
|
+
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.",
|
|
673
|
+
inputSchema: {
|
|
674
|
+
projectId: z16.string().describe("projectId from list_projects")
|
|
675
|
+
},
|
|
676
|
+
annotations: {
|
|
677
|
+
readOnlyHint: true,
|
|
678
|
+
openWorldHint: false
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
async ({ projectId }) => {
|
|
682
|
+
const notes = await getConvexClient().action(api.mcp.listScratchNotes, {
|
|
683
|
+
token: getToken(),
|
|
684
|
+
projectId
|
|
685
|
+
});
|
|
686
|
+
return {
|
|
687
|
+
content: [{ type: "text", text: JSON.stringify(notes, null, 2) }]
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// src/tools/resolve-scratch-note.ts
|
|
694
|
+
import { z as z17 } from "zod";
|
|
695
|
+
function registerResolveScratchNote(server) {
|
|
696
|
+
server.registerTool(
|
|
697
|
+
"resolve_scratch_note",
|
|
698
|
+
{
|
|
699
|
+
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.",
|
|
700
|
+
inputSchema: {
|
|
701
|
+
noteId: z17.string().describe("noteId from list_scratch_notes"),
|
|
702
|
+
workItemId: z17.string().optional().describe("The workItemId the note became, from create_work_item or list_work_items.")
|
|
703
|
+
},
|
|
704
|
+
annotations: {
|
|
705
|
+
readOnlyHint: false,
|
|
706
|
+
destructiveHint: false,
|
|
707
|
+
idempotentHint: true,
|
|
708
|
+
openWorldHint: false
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
async ({ noteId, workItemId }) => {
|
|
712
|
+
const result = await getConvexClient().action(api.mcp.resolveScratchNote, {
|
|
713
|
+
token: getToken(),
|
|
714
|
+
noteId,
|
|
715
|
+
workItemId
|
|
716
|
+
});
|
|
717
|
+
return {
|
|
718
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
628
724
|
// src/tools/add-work-item-image.ts
|
|
629
|
-
import { z as
|
|
725
|
+
import { z as z18 } from "zod";
|
|
630
726
|
function registerAddWorkItemImage(server) {
|
|
631
727
|
server.registerTool(
|
|
632
728
|
"add_work_item_image",
|
|
633
729
|
{
|
|
634
730
|
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
731
|
inputSchema: {
|
|
636
|
-
workItemId:
|
|
637
|
-
imageUrl:
|
|
638
|
-
filename:
|
|
639
|
-
altText:
|
|
732
|
+
workItemId: z18.string().describe("workItemId from list_work_items"),
|
|
733
|
+
imageUrl: z18.string().url().describe("Public or otherwise fetchable http(s) image URL"),
|
|
734
|
+
filename: z18.string().optional().describe("Optional display filename"),
|
|
735
|
+
altText: z18.string().optional().describe("Optional description/caption for the image")
|
|
640
736
|
},
|
|
641
737
|
annotations: {
|
|
642
738
|
readOnlyHint: false,
|
|
@@ -658,14 +754,14 @@ function registerAddWorkItemImage(server) {
|
|
|
658
754
|
}
|
|
659
755
|
|
|
660
756
|
// src/tools/list-work-item-images.ts
|
|
661
|
-
import { z as
|
|
757
|
+
import { z as z19 } from "zod";
|
|
662
758
|
function registerListWorkItemImages(server) {
|
|
663
759
|
server.registerTool(
|
|
664
760
|
"list_work_item_images",
|
|
665
761
|
{
|
|
666
762
|
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
763
|
inputSchema: {
|
|
668
|
-
workItemId:
|
|
764
|
+
workItemId: z19.string().describe("workItemId from list_work_items")
|
|
669
765
|
},
|
|
670
766
|
annotations: {
|
|
671
767
|
readOnlyHint: true,
|
|
@@ -688,16 +784,16 @@ function registerListWorkItemImages(server) {
|
|
|
688
784
|
}
|
|
689
785
|
|
|
690
786
|
// src/tools/add-work-item-comment.ts
|
|
691
|
-
import { z as
|
|
787
|
+
import { z as z20 } from "zod";
|
|
692
788
|
function registerAddWorkItemComment(server) {
|
|
693
789
|
server.registerTool(
|
|
694
790
|
"add_work_item_comment",
|
|
695
791
|
{
|
|
696
792
|
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
793
|
inputSchema: {
|
|
698
|
-
workItemId:
|
|
699
|
-
body:
|
|
700
|
-
source:
|
|
794
|
+
workItemId: z20.string().describe("workItemId from list_work_items"),
|
|
795
|
+
body: z20.string().min(1).describe("Comment text (markdown supported)"),
|
|
796
|
+
source: z20.enum(["user", "ai"]).optional().describe('Who authored it; defaults to "ai" for MCP callers')
|
|
701
797
|
},
|
|
702
798
|
annotations: {
|
|
703
799
|
readOnlyHint: false,
|
|
@@ -722,14 +818,14 @@ function registerAddWorkItemComment(server) {
|
|
|
722
818
|
}
|
|
723
819
|
|
|
724
820
|
// src/tools/list-work-item-comments.ts
|
|
725
|
-
import { z as
|
|
821
|
+
import { z as z21 } from "zod";
|
|
726
822
|
function registerListWorkItemComments(server) {
|
|
727
823
|
server.registerTool(
|
|
728
824
|
"list_work_item_comments",
|
|
729
825
|
{
|
|
730
826
|
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
827
|
inputSchema: {
|
|
732
|
-
workItemId:
|
|
828
|
+
workItemId: z21.string().describe("workItemId from list_work_items")
|
|
733
829
|
},
|
|
734
830
|
annotations: {
|
|
735
831
|
readOnlyHint: true,
|
|
@@ -752,14 +848,14 @@ function registerListWorkItemComments(server) {
|
|
|
752
848
|
}
|
|
753
849
|
|
|
754
850
|
// src/tools/list-validations.ts
|
|
755
|
-
import { z as
|
|
851
|
+
import { z as z22 } from "zod";
|
|
756
852
|
function registerListValidations(server) {
|
|
757
853
|
server.registerTool(
|
|
758
854
|
"list_validations",
|
|
759
855
|
{
|
|
760
856
|
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
857
|
inputSchema: {
|
|
762
|
-
projectId:
|
|
858
|
+
projectId: z22.string().describe("projectId from list_projects")
|
|
763
859
|
},
|
|
764
860
|
annotations: {
|
|
765
861
|
readOnlyHint: true,
|
|
@@ -780,15 +876,15 @@ function registerListValidations(server) {
|
|
|
780
876
|
}
|
|
781
877
|
|
|
782
878
|
// src/tools/record-validation.ts
|
|
783
|
-
import { z as
|
|
879
|
+
import { z as z23 } from "zod";
|
|
784
880
|
function registerRecordValidation(server) {
|
|
785
881
|
server.registerTool(
|
|
786
882
|
"record_validation",
|
|
787
883
|
{
|
|
788
884
|
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
885
|
inputSchema: {
|
|
790
|
-
validationTaskId:
|
|
791
|
-
result:
|
|
886
|
+
validationTaskId: z23.string().describe("validationTaskId from list_validations"),
|
|
887
|
+
result: z23.enum(["pass", "fail"]).describe("Outcome of running the test")
|
|
792
888
|
},
|
|
793
889
|
annotations: {
|
|
794
890
|
readOnlyHint: false,
|
|
@@ -812,14 +908,14 @@ function registerRecordValidation(server) {
|
|
|
812
908
|
}
|
|
813
909
|
|
|
814
910
|
// src/tools/mark-system-changed.ts
|
|
815
|
-
import { z as
|
|
911
|
+
import { z as z24 } from "zod";
|
|
816
912
|
function registerMarkSystemChanged(server) {
|
|
817
913
|
server.registerTool(
|
|
818
914
|
"mark_system_changed",
|
|
819
915
|
{
|
|
820
916
|
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
917
|
inputSchema: {
|
|
822
|
-
systemId:
|
|
918
|
+
systemId: z24.string().describe("systemId from list_validations")
|
|
823
919
|
},
|
|
824
920
|
annotations: {
|
|
825
921
|
readOnlyHint: false,
|
|
@@ -842,16 +938,16 @@ function registerMarkSystemChanged(server) {
|
|
|
842
938
|
}
|
|
843
939
|
|
|
844
940
|
// src/tools/create-system.ts
|
|
845
|
-
import { z as
|
|
941
|
+
import { z as z25 } from "zod";
|
|
846
942
|
function registerCreateSystem(server) {
|
|
847
943
|
server.registerTool(
|
|
848
944
|
"create_system",
|
|
849
945
|
{
|
|
850
946
|
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
947
|
inputSchema: {
|
|
852
|
-
projectId:
|
|
853
|
-
name:
|
|
854
|
-
description:
|
|
948
|
+
projectId: z25.string().describe("projectId from list_projects"),
|
|
949
|
+
name: z25.string().min(1).describe("Short system name, e.g. 'Auth'"),
|
|
950
|
+
description: z25.string().optional().describe("Optional details")
|
|
855
951
|
},
|
|
856
952
|
annotations: {
|
|
857
953
|
readOnlyHint: false,
|
|
@@ -874,20 +970,20 @@ function registerCreateSystem(server) {
|
|
|
874
970
|
}
|
|
875
971
|
|
|
876
972
|
// src/tools/create-validation-task.ts
|
|
877
|
-
import { z as
|
|
973
|
+
import { z as z26 } from "zod";
|
|
878
974
|
function registerCreateValidationTask(server) {
|
|
879
975
|
server.registerTool(
|
|
880
976
|
"create_validation_task",
|
|
881
977
|
{
|
|
882
978
|
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
979
|
inputSchema: {
|
|
884
|
-
projectId:
|
|
885
|
-
title:
|
|
886
|
-
description:
|
|
887
|
-
kind:
|
|
980
|
+
projectId: z26.string().describe("projectId from list_projects"),
|
|
981
|
+
title: z26.string().min(1).describe("What this test validates"),
|
|
982
|
+
description: z26.string().optional().describe("Optional steps / details"),
|
|
983
|
+
kind: z26.enum(["auto", "manual"]).optional().describe(
|
|
888
984
|
"'auto' if runnable by a test runner, 'manual' if a human checks it. Defaults to manual."
|
|
889
985
|
),
|
|
890
|
-
workItemId:
|
|
986
|
+
workItemId: z26.string().optional().describe(
|
|
891
987
|
"Optional workItemId to link this test to a feature for traceability"
|
|
892
988
|
)
|
|
893
989
|
},
|
|
@@ -912,15 +1008,15 @@ function registerCreateValidationTask(server) {
|
|
|
912
1008
|
}
|
|
913
1009
|
|
|
914
1010
|
// src/tools/add-system-to-task.ts
|
|
915
|
-
import { z as
|
|
1011
|
+
import { z as z27 } from "zod";
|
|
916
1012
|
function registerAddSystemToTask(server) {
|
|
917
1013
|
server.registerTool(
|
|
918
1014
|
"add_system_to_task",
|
|
919
1015
|
{
|
|
920
1016
|
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
1017
|
inputSchema: {
|
|
922
|
-
validationTaskId:
|
|
923
|
-
systemId:
|
|
1018
|
+
validationTaskId: z27.string().describe("validationTaskId from list_validations"),
|
|
1019
|
+
systemId: z27.string().describe("systemId from list_validations")
|
|
924
1020
|
},
|
|
925
1021
|
annotations: {
|
|
926
1022
|
readOnlyHint: false,
|
|
@@ -944,15 +1040,15 @@ function registerAddSystemToTask(server) {
|
|
|
944
1040
|
}
|
|
945
1041
|
|
|
946
1042
|
// src/tools/add-validation-dependency.ts
|
|
947
|
-
import { z as
|
|
1043
|
+
import { z as z28 } from "zod";
|
|
948
1044
|
function registerAddValidationDependency(server) {
|
|
949
1045
|
server.registerTool(
|
|
950
1046
|
"add_validation_dependency",
|
|
951
1047
|
{
|
|
952
1048
|
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
1049
|
inputSchema: {
|
|
954
|
-
validationTaskId:
|
|
955
|
-
dependsOnTaskId:
|
|
1050
|
+
validationTaskId: z28.string().describe("The dependent (downstream) task"),
|
|
1051
|
+
dependsOnTaskId: z28.string().describe("The dependency (upstream) task it relies on")
|
|
956
1052
|
},
|
|
957
1053
|
annotations: {
|
|
958
1054
|
readOnlyHint: false,
|
|
@@ -976,14 +1072,14 @@ function registerAddValidationDependency(server) {
|
|
|
976
1072
|
}
|
|
977
1073
|
|
|
978
1074
|
// src/tools/list-labels.ts
|
|
979
|
-
import { z as
|
|
1075
|
+
import { z as z29 } from "zod";
|
|
980
1076
|
function registerListLabels(server) {
|
|
981
1077
|
server.registerTool(
|
|
982
1078
|
"list_labels",
|
|
983
1079
|
{
|
|
984
1080
|
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
1081
|
inputSchema: {
|
|
986
|
-
projectId:
|
|
1082
|
+
projectId: z29.string().describe("projectId from list_projects")
|
|
987
1083
|
},
|
|
988
1084
|
annotations: {
|
|
989
1085
|
readOnlyHint: true,
|
|
@@ -1003,16 +1099,16 @@ function registerListLabels(server) {
|
|
|
1003
1099
|
}
|
|
1004
1100
|
|
|
1005
1101
|
// src/tools/create-label.ts
|
|
1006
|
-
import { z as
|
|
1102
|
+
import { z as z30 } from "zod";
|
|
1007
1103
|
function registerCreateLabel(server) {
|
|
1008
1104
|
server.registerTool(
|
|
1009
1105
|
"create_label",
|
|
1010
1106
|
{
|
|
1011
1107
|
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
1108
|
inputSchema: {
|
|
1013
|
-
projectId:
|
|
1014
|
-
name:
|
|
1015
|
-
color:
|
|
1109
|
+
projectId: z30.string().describe("projectId from list_projects"),
|
|
1110
|
+
name: z30.string().min(1).describe("Label name, unique within the project"),
|
|
1111
|
+
color: z30.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Hex color like #2563eb. Defaults to a neutral slate.")
|
|
1016
1112
|
},
|
|
1017
1113
|
annotations: {
|
|
1018
1114
|
readOnlyHint: false,
|
|
@@ -1034,15 +1130,15 @@ function registerCreateLabel(server) {
|
|
|
1034
1130
|
}
|
|
1035
1131
|
|
|
1036
1132
|
// src/tools/add-label-to-work-item.ts
|
|
1037
|
-
import { z as
|
|
1133
|
+
import { z as z31 } from "zod";
|
|
1038
1134
|
function registerAddLabelToWorkItem(server) {
|
|
1039
1135
|
server.registerTool(
|
|
1040
1136
|
"add_label_to_work_item",
|
|
1041
1137
|
{
|
|
1042
1138
|
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
1139
|
inputSchema: {
|
|
1044
|
-
workItemId:
|
|
1045
|
-
labelId:
|
|
1140
|
+
workItemId: z31.string().describe("workItemId from list_work_items"),
|
|
1141
|
+
labelId: z31.string().describe("labelId from list_labels or create_label")
|
|
1046
1142
|
},
|
|
1047
1143
|
annotations: {
|
|
1048
1144
|
readOnlyHint: false,
|
|
@@ -1065,15 +1161,15 @@ function registerAddLabelToWorkItem(server) {
|
|
|
1065
1161
|
}
|
|
1066
1162
|
|
|
1067
1163
|
// src/tools/remove-label-from-work-item.ts
|
|
1068
|
-
import { z as
|
|
1164
|
+
import { z as z32 } from "zod";
|
|
1069
1165
|
function registerRemoveLabelFromWorkItem(server) {
|
|
1070
1166
|
server.registerTool(
|
|
1071
1167
|
"remove_label_from_work_item",
|
|
1072
1168
|
{
|
|
1073
1169
|
description: "Remove a label from a work item (idempotent \u2014 a no-op if it wasn't attached).",
|
|
1074
1170
|
inputSchema: {
|
|
1075
|
-
workItemId:
|
|
1076
|
-
labelId:
|
|
1171
|
+
workItemId: z32.string().describe("workItemId from list_work_items"),
|
|
1172
|
+
labelId: z32.string().describe("labelId from list_labels")
|
|
1077
1173
|
},
|
|
1078
1174
|
annotations: {
|
|
1079
1175
|
readOnlyHint: false,
|
|
@@ -1096,14 +1192,14 @@ function registerRemoveLabelFromWorkItem(server) {
|
|
|
1096
1192
|
}
|
|
1097
1193
|
|
|
1098
1194
|
// src/tools/list-milestones.ts
|
|
1099
|
-
import { z as
|
|
1195
|
+
import { z as z33 } from "zod";
|
|
1100
1196
|
function registerListMilestones(server) {
|
|
1101
1197
|
server.registerTool(
|
|
1102
1198
|
"list_milestones",
|
|
1103
1199
|
{
|
|
1104
1200
|
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
1201
|
inputSchema: {
|
|
1106
|
-
projectId:
|
|
1202
|
+
projectId: z33.string().describe("projectId from list_projects")
|
|
1107
1203
|
},
|
|
1108
1204
|
annotations: {
|
|
1109
1205
|
readOnlyHint: true,
|
|
@@ -1123,17 +1219,17 @@ function registerListMilestones(server) {
|
|
|
1123
1219
|
}
|
|
1124
1220
|
|
|
1125
1221
|
// src/tools/create-milestone.ts
|
|
1126
|
-
import { z as
|
|
1222
|
+
import { z as z34 } from "zod";
|
|
1127
1223
|
function registerCreateMilestone(server) {
|
|
1128
1224
|
server.registerTool(
|
|
1129
1225
|
"create_milestone",
|
|
1130
1226
|
{
|
|
1131
1227
|
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
1228
|
inputSchema: {
|
|
1133
|
-
projectId:
|
|
1134
|
-
name:
|
|
1135
|
-
description:
|
|
1136
|
-
dueDate:
|
|
1229
|
+
projectId: z34.string().describe("projectId from list_projects"),
|
|
1230
|
+
name: z34.string().min(1).describe("Milestone name"),
|
|
1231
|
+
description: z34.string().optional().describe("Optional markdown description"),
|
|
1232
|
+
dueDate: z34.number().optional().describe("Optional due date as a unix timestamp in milliseconds")
|
|
1137
1233
|
},
|
|
1138
1234
|
annotations: {
|
|
1139
1235
|
readOnlyHint: false,
|
|
@@ -1155,15 +1251,15 @@ function registerCreateMilestone(server) {
|
|
|
1155
1251
|
}
|
|
1156
1252
|
|
|
1157
1253
|
// src/tools/add-item-to-milestone.ts
|
|
1158
|
-
import { z as
|
|
1254
|
+
import { z as z35 } from "zod";
|
|
1159
1255
|
function registerAddItemToMilestone(server) {
|
|
1160
1256
|
server.registerTool(
|
|
1161
1257
|
"add_item_to_milestone",
|
|
1162
1258
|
{
|
|
1163
1259
|
description: "Associate a work item with a milestone (idempotent). Both must be in the same project.",
|
|
1164
1260
|
inputSchema: {
|
|
1165
|
-
milestoneId:
|
|
1166
|
-
workItemId:
|
|
1261
|
+
milestoneId: z35.string().describe("milestoneId from list_milestones or create_milestone"),
|
|
1262
|
+
workItemId: z35.string().describe("workItemId from list_work_items")
|
|
1167
1263
|
},
|
|
1168
1264
|
annotations: {
|
|
1169
1265
|
readOnlyHint: false,
|
|
@@ -1189,15 +1285,15 @@ function registerAddItemToMilestone(server) {
|
|
|
1189
1285
|
}
|
|
1190
1286
|
|
|
1191
1287
|
// src/tools/remove-item-from-milestone.ts
|
|
1192
|
-
import { z as
|
|
1288
|
+
import { z as z36 } from "zod";
|
|
1193
1289
|
function registerRemoveItemFromMilestone(server) {
|
|
1194
1290
|
server.registerTool(
|
|
1195
1291
|
"remove_item_from_milestone",
|
|
1196
1292
|
{
|
|
1197
1293
|
description: "Remove a work item's association with a milestone (idempotent \u2014 a no-op if it wasn't associated).",
|
|
1198
1294
|
inputSchema: {
|
|
1199
|
-
milestoneId:
|
|
1200
|
-
workItemId:
|
|
1295
|
+
milestoneId: z36.string().describe("milestoneId from list_milestones"),
|
|
1296
|
+
workItemId: z36.string().describe("workItemId from list_work_items")
|
|
1201
1297
|
},
|
|
1202
1298
|
annotations: {
|
|
1203
1299
|
readOnlyHint: false,
|
|
@@ -1234,6 +1330,7 @@ function createServer() {
|
|
|
1234
1330
|
registerGetWorkItems(server);
|
|
1235
1331
|
registerSearchWorkItems(server);
|
|
1236
1332
|
registerListAiReviewQueue(server);
|
|
1333
|
+
registerGetActiveBlock(server);
|
|
1237
1334
|
registerCreateProject(server);
|
|
1238
1335
|
registerCreateWorkItem(server);
|
|
1239
1336
|
registerUpdateWorkItem(server);
|
|
@@ -1243,6 +1340,8 @@ function createServer() {
|
|
|
1243
1340
|
registerListDocuments(server);
|
|
1244
1341
|
registerCreateProjectLink(server);
|
|
1245
1342
|
registerListProjectLinks(server);
|
|
1343
|
+
registerListScratchNotes(server);
|
|
1344
|
+
registerResolveScratchNote(server);
|
|
1246
1345
|
registerAddWorkItemImage(server);
|
|
1247
1346
|
registerListWorkItemImages(server);
|
|
1248
1347
|
registerAddWorkItemComment(server);
|