@nimara-app/mcp 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -63,7 +63,7 @@ function registerListProjects(server) {
63
63
  server.registerTool(
64
64
  "list_projects",
65
65
  {
66
- description: "List projects in a given organization. Call list_orgs first to find the orgId.",
66
+ 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.",
67
67
  inputSchema: { orgId: z.string().describe("orgId from list_orgs") },
68
68
  annotations: {
69
69
  readOnlyHint: true,
@@ -91,9 +91,22 @@ function registerListWorkItems(server) {
91
91
  {
92
92
  // The description is the only place a model learns paging exists, so it
93
93
  // states the shape of the response and what to do when hasMore is true.
94
- description: "List non-archived work items in a project, newest-created last. Returns { items, nextCursor, hasMore }. Long descriptions are previewed \u2014 an item with descriptionTruncated: true has more text, readable via fullDescriptions. When hasMore is true, call again with cursor set to nextCursor to get the next page. Call list_projects first for the projectId.",
94
+ description: "List non-archived work items in a project, oldest-created first. Returns { items, nextCursor, hasMore }. Long descriptions are previewed \u2014 an item with descriptionTruncated: true has more text, readable via fullDescriptions. When hasMore is true, call again with cursor set to nextCursor to get the next page \u2014 the FIRST page is the OLDEST items, so recently created ones are on the LAST page and you have not seen the project until hasMore is false. Narrow with status/type/parentId/needsReview rather than paging and filtering yourself. If you already know which items you want, use get_work_items instead \u2014 it takes displayIds like NIM-T75 and costs a fraction of a list. Call list_projects first for the projectId.",
95
95
  inputSchema: {
96
96
  projectId: z2.string().describe("projectId from list_projects"),
97
+ status: z2.union([z2.string(), z2.array(z2.string())]).optional().describe(
98
+ "Only items in these statuses. Names come from the project's statusOrder (e.g. 'In Progress'). One name or a list."
99
+ ),
100
+ type: z2.union([
101
+ z2.enum(["epic", "feature", "task", "subtask"]),
102
+ z2.array(z2.enum(["epic", "feature", "task", "subtask"]))
103
+ ]).optional().describe("Only items of these types. One type or a list."),
104
+ parentId: z2.string().optional().describe(
105
+ "Only direct children of this workItemId. One level, not the whole subtree."
106
+ ),
107
+ needsReview: z2.boolean().optional().describe(
108
+ "true returns only items flagged needsReview; false only unflagged ones. Omit for both."
109
+ ),
97
110
  limit: z2.number().int().min(1).max(200).optional().describe("Items per page (default 50, max 200)."),
98
111
  cursor: z2.number().optional().describe(
99
112
  "nextCursor from the previous page. Omit for the first page."
@@ -107,13 +120,28 @@ function registerListWorkItems(server) {
107
120
  openWorldHint: false
108
121
  }
109
122
  },
110
- async ({ projectId, limit, cursor, fullDescriptions }) => {
123
+ async ({
124
+ projectId,
125
+ limit,
126
+ cursor,
127
+ fullDescriptions,
128
+ status,
129
+ type,
130
+ parentId,
131
+ needsReview
132
+ }) => {
111
133
  const page = await getConvexClient().action(api.mcp.listWorkItems, {
112
134
  token: getToken(),
113
135
  projectId,
114
136
  limit,
115
137
  cursor,
116
- fullDescriptions
138
+ fullDescriptions,
139
+ // Accepting a bare string as well as a list is what keeps a model from
140
+ // failing validation on the overwhelmingly common single-value case.
141
+ status: status === void 0 ? void 0 : Array.isArray(status) ? status : [status],
142
+ type: type === void 0 ? void 0 : Array.isArray(type) ? type : [type],
143
+ parentId,
144
+ needsReview
117
145
  });
118
146
  return {
119
147
  content: [{ type: "text", text: JSON.stringify(page, null, 2) }]
@@ -122,17 +150,91 @@ function registerListWorkItems(server) {
122
150
  );
123
151
  }
124
152
 
125
- // src/tools/list-ai-review-queue.ts
153
+ // src/tools/get-work-items.ts
126
154
  import { z as z3 } from "zod";
155
+ function registerGetWorkItems(server) {
156
+ server.registerTool(
157
+ "get_work_items",
158
+ {
159
+ // Written to be chosen *over* list_work_items whenever the caller already
160
+ // knows which items it wants — that is the whole point of the tool, and
161
+ // the description is the only place a model learns it.
162
+ description: "Read specific work items by id \u2014 the cheap way to re-read items you already know about. Prefer this over list_work_items whenever you have a displayId (e.g. NIM-T75) or a workItemId: listing a project returns every item and its description, which is orders of magnitude more data. Accepts up to 50 ids per call and always returns descriptions in full. Returns { items, notFound }; ids in notFound either do not exist or are not readable by this token. displayIds require projectId (they are unique per project, not globally); workItemIds do not.",
163
+ inputSchema: {
164
+ displayIds: z3.array(z3.string()).optional().describe(
165
+ "Human-facing ids like ['NIM-T75', 'NIM-F27']. Case-insensitive. Requires projectId."
166
+ ),
167
+ workItemIds: z3.array(z3.string()).optional().describe(
168
+ "Raw workItemIds, as returned by create_work_item or list_work_items."
169
+ ),
170
+ projectId: z3.string().optional().describe(
171
+ "projectId from list_projects. Required only when passing displayIds."
172
+ )
173
+ },
174
+ annotations: {
175
+ readOnlyHint: true,
176
+ openWorldHint: false
177
+ }
178
+ },
179
+ async ({ displayIds, workItemIds, projectId }) => {
180
+ const result = await getConvexClient().action(api.mcp.getWorkItems, {
181
+ token: getToken(),
182
+ displayIds,
183
+ workItemIds,
184
+ projectId
185
+ });
186
+ return {
187
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
188
+ };
189
+ }
190
+ );
191
+ }
192
+
193
+ // src/tools/search-work-items.ts
194
+ import { z as z4 } from "zod";
195
+ function registerSearchWorkItems(server) {
196
+ server.registerTool(
197
+ "search_work_items",
198
+ {
199
+ // The point of this description is to stop a model reaching for
200
+ // list_work_items when it is looking for something by name. Listing a
201
+ // project to find one item is the expensive mistake this tool exists to
202
+ // prevent.
203
+ description: "Find work items in a project by title. Use this to answer 'does an item for X already exist?' \u2014 listing the whole project and filtering yourself is far more expensive and, because the first page is the OLDEST items, easy to get wrong. Matches TITLES only, not descriptions. Returns { items, query } with descriptions previewed; once you know which item you want, get_work_items returns it in full. Capped at 25 results \u2014 narrow the query rather than paging. Call list_projects first for the projectId.",
204
+ inputSchema: {
205
+ projectId: z4.string().describe("projectId from list_projects"),
206
+ q: z4.string().min(1).describe(
207
+ "Words to match against item titles, e.g. 'mobile modal' or 'shortcut'. Matching is full-text, so word order does not matter."
208
+ ),
209
+ limit: z4.number().int().min(1).max(25).optional().describe("Maximum results (default and maximum 25).")
210
+ },
211
+ annotations: { readOnlyHint: true }
212
+ },
213
+ async ({ projectId, q, limit }) => {
214
+ const result = await getConvexClient().action(api.mcp.searchWorkItems, {
215
+ token: getToken(),
216
+ projectId,
217
+ q,
218
+ limit
219
+ });
220
+ return {
221
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
222
+ };
223
+ }
224
+ );
225
+ }
226
+
227
+ // src/tools/list-ai-review-queue.ts
228
+ import { z as z5 } from "zod";
127
229
  function registerListAiReviewQueue(server) {
128
230
  server.registerTool(
129
231
  "list_ai_review_queue",
130
232
  {
131
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.",
132
234
  inputSchema: {
133
- projectId: z3.string().describe("projectId from list_projects"),
134
- limit: z3.number().int().min(1).max(200).optional().describe("Items per page (default 50, max 200)."),
135
- cursor: z3.number().optional().describe(
235
+ projectId: z5.string().describe("projectId from list_projects"),
236
+ limit: z5.number().int().min(1).max(200).optional().describe("Items per page (default 50, max 200)."),
237
+ cursor: z5.number().optional().describe(
136
238
  "nextCursor from the previous page. Omit for the first page."
137
239
  )
138
240
  },
@@ -156,24 +258,32 @@ function registerListAiReviewQueue(server) {
156
258
  }
157
259
 
158
260
  // src/tools/create-work-item.ts
159
- import { z as z4 } from "zod";
261
+ import { z as z6 } from "zod";
262
+
263
+ // src/tools/titleGuidance.ts
264
+ 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.';
265
+
266
+ // src/tools/statusGuidance.ts
267
+ 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
+
269
+ // src/tools/create-work-item.ts
160
270
  function registerCreateWorkItem(server) {
161
271
  server.registerTool(
162
272
  "create_work_item",
163
273
  {
164
274
  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).",
165
275
  inputSchema: {
166
- projectId: z4.string().describe("projectId from list_projects"),
167
- type: z4.enum(["epic", "feature", "task", "subtask"]).describe("Item type \u2014 drives hierarchy validation"),
168
- title: z4.string().min(1).describe("Short, descriptive title"),
169
- description: z4.string().optional().describe("Optional markdown body"),
170
- parentId: z4.string().optional().describe(
276
+ projectId: z6.string().describe("projectId from list_projects"),
277
+ type: z6.enum(["epic", "feature", "task", "subtask"]).describe("Item type \u2014 drives hierarchy validation"),
278
+ title: z6.string().min(1).describe(TITLE_GUIDANCE),
279
+ description: z6.string().optional().describe("Optional markdown body"),
280
+ parentId: z6.string().optional().describe(
171
281
  "workItemId of the parent. Required for subtask, optional otherwise."
172
282
  ),
173
- status: z4.string().optional().describe(
174
- "Status name from the project's statusOrder. Defaults to first status (e.g. 'Backlog')."
283
+ status: z6.string().optional().describe(
284
+ STATUS_GUIDANCE
175
285
  ),
176
- priority: z4.enum(["p0", "p1", "p2", "p3"]).optional().describe("p0 highest, p3 lowest. Defaults to p2.")
286
+ priority: z6.enum(["p0", "p1", "p2", "p3"]).optional().describe("p0 highest, p3 lowest. Defaults to p2.")
177
287
  },
178
288
  annotations: {
179
289
  readOnlyHint: false,
@@ -196,23 +306,23 @@ function registerCreateWorkItem(server) {
196
306
  }
197
307
 
198
308
  // src/tools/create-project.ts
199
- import { z as z5 } from "zod";
309
+ import { z as z7 } from "zod";
200
310
  function registerCreateProject(server) {
201
311
  server.registerTool(
202
312
  "create_project",
203
313
  {
204
314
  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.",
205
315
  inputSchema: {
206
- orgId: z5.string().describe("orgId from list_orgs"),
207
- name: z5.string().min(1).describe("Project name"),
208
- prefix: z5.string().min(1).max(8).describe(
316
+ orgId: z7.string().describe("orgId from list_orgs"),
317
+ name: z7.string().min(1).describe("Project name"),
318
+ prefix: z7.string().min(1).max(8).describe(
209
319
  "Short uppercase prefix for work item IDs (e.g. 'NIM'). Unique per org."
210
320
  ),
211
- description: z5.string().optional().describe("Optional markdown description"),
212
- isPrivate: z5.boolean().optional().describe(
321
+ description: z7.string().optional().describe("Optional markdown description"),
322
+ isPrivate: z7.boolean().optional().describe(
213
323
  "If true, only explicitly added members see it. Defaults to false."
214
324
  ),
215
- workflowTemplate: z5.enum(["default", "simple", "kanban", "bug"]).optional().describe(
325
+ workflowTemplate: z7.enum(["default", "simple", "kanban", "bug"]).optional().describe(
216
326
  "Status workflow. Defaults to 'default' (Backlog/Todo/In Progress/Review/Done)."
217
327
  )
218
328
  },
@@ -237,26 +347,57 @@ function registerCreateProject(server) {
237
347
  }
238
348
 
239
349
  // src/tools/update-work-item.ts
240
- import { z as z6 } from "zod";
350
+ import { z as z8 } from "zod";
351
+
352
+ // src/tools/researchFormat.ts
353
+ var RESEARCH_FORMAT = `When appending automated research to a description, use EXACTLY this format, after the human-written text:
354
+
355
+ ---
356
+ ## Research (auto)
357
+
358
+ ### Findings
359
+ - One bullet per verified fact, each naming what you checked.
360
+
361
+ ### Dependencies
362
+ - Items this blocks or is blocked by, by displayId. Omit the section if none.
363
+
364
+ ### Duplicates
365
+ - Existing items covering the same work, by displayId. Omit if none.
366
+
367
+ ### Suggested priority
368
+ One line. Say "No change" when it is already right.
369
+
370
+ Rules:
371
+ - The "## Research (auto)" heading is EXACT and is what the app keys on to show this in its own tab. Do not reword it, renumber its level, or translate it.
372
+ - NO EMOJI anywhere in the block, in headings or in bullets. Older items carry emoji headings; do not copy them.
373
+ - Omit a section rather than writing "None found" in it. An empty section is noise the reader still has to read.
374
+ - Never edit or delete the human-written text above the rule. Append only.
375
+ - Markdown only. GFM tables are supported and are the right choice for comparisons. Raw HTML is NOT rendered by the app and must not be used.
376
+ - For a workflow, sequence or state machine, use a \\\`\\\`\\\`mermaid fenced block. That is the supported way to draw one.
377
+ - Say what you verified and how. "Confirmed X still returns Y (file:line)" is worth more than a conclusion with no trail.`;
378
+
379
+ // src/tools/update-work-item.ts
241
380
  function registerUpdateWorkItem(server) {
242
381
  server.registerTool(
243
382
  "update_work_item",
244
383
  {
245
384
  description: "Update an existing work item: title, description, status, priority, parent, or review flags. Any update also stamps the item as AI-touched.",
246
385
  inputSchema: {
247
- workItemId: z6.string().describe("workItemId from list_work_items"),
248
- title: z6.string().min(1).optional(),
249
- description: z6.string().optional(),
250
- status: z6.string().optional(),
251
- priority: z6.enum(["p0", "p1", "p2", "p3"]).optional(),
252
- parentId: z6.string().nullable().optional().describe("New parent workItemId, null to move to root."),
253
- needsReview: z6.boolean().optional().describe(
386
+ workItemId: z8.string().describe("workItemId from list_work_items"),
387
+ title: z8.string().min(1).optional().describe(TITLE_GUIDANCE),
388
+ description: z8.string().optional().describe(
389
+ "Full markdown body, replacing what is there. " + RESEARCH_FORMAT
390
+ ),
391
+ status: z8.string().optional().describe(STATUS_GUIDANCE),
392
+ priority: z8.enum(["p0", "p1", "p2", "p3"]).optional(),
393
+ parentId: z8.string().nullable().optional().describe("New parent workItemId, null to move to root."),
394
+ needsReview: z8.boolean().optional().describe(
254
395
  "Flag the item for manual human review \u2014 set true when your enrichment was thin or uncertain."
255
396
  ),
256
- reviewReason: z6.string().optional().describe(
397
+ reviewReason: z8.string().optional().describe(
257
398
  "Short reason the item needs manual review (shown to the human)."
258
399
  ),
259
- aiReviewRequested: z6.boolean().optional().describe(
400
+ aiReviewRequested: z8.boolean().optional().describe(
260
401
  "The human\u2192AI review flag. Set false to clear it after you have reviewed/enriched an item from list_ai_review_queue."
261
402
  )
262
403
  },
@@ -279,21 +420,62 @@ function registerUpdateWorkItem(server) {
279
420
  );
280
421
  }
281
422
 
423
+ // src/tools/move-work-item.ts
424
+ import { z as z9 } from "zod";
425
+ function registerMoveWorkItem(server) {
426
+ server.registerTool(
427
+ "move_work_item",
428
+ {
429
+ // Everything a model needs to predict the damage has to be stated here —
430
+ // the description is the only channel it has. The consequences that are
431
+ // NOT obvious from the name are the ones worth the tokens: the display ID
432
+ // changes, and labels/milestones do not survive.
433
+ 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
+ inputSchema: {
435
+ workItemId: z9.string().describe("workItemId from list_work_items \u2014 the item to move."),
436
+ targetProjectId: z9.string().describe(
437
+ "projectId from list_projects \u2014 the destination. Must be in the same org as the item's current project."
438
+ )
439
+ },
440
+ annotations: {
441
+ readOnlyHint: false,
442
+ // Renumbering display IDs and deleting label/milestone links are
443
+ // overwrites, not additions — this belongs with the tools a client
444
+ // should prompt on rather than auto-approve.
445
+ destructiveHint: true,
446
+ // Calling it twice does not land in the same place: the second call
447
+ // either errors as already-there or moves the item on again.
448
+ idempotentHint: false,
449
+ openWorldHint: false
450
+ }
451
+ },
452
+ async ({ workItemId, targetProjectId }) => {
453
+ const result = await getConvexClient().action(
454
+ api.mcp.moveWorkItemToProject,
455
+ { token: getToken(), workItemId, targetProjectId }
456
+ );
457
+ return {
458
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
459
+ };
460
+ }
461
+ );
462
+ }
463
+
282
464
  // src/tools/create-document.ts
283
- import { z as z7 } from "zod";
465
+ import { z as z10 } from "zod";
284
466
  function registerCreateDocument(server) {
285
467
  server.registerTool(
286
468
  "create_document",
287
469
  {
288
470
  description: "Create a markdown project document. Use type 'prd' for project docs or 'fd' for feature/work-item documents.",
289
471
  inputSchema: {
290
- projectId: z7.string().describe("projectId from list_projects"),
291
- title: z7.string().min(1),
292
- type: z7.enum(["prd", "fd"]).describe(
472
+ projectId: z10.string().describe("projectId from list_projects"),
473
+ title: z10.string().min(1),
474
+ type: z10.enum(["prd", "fd"]).describe(
293
475
  "Document type. 'fd' is stored as the app's feature document type."
294
476
  ),
295
- workItemId: z7.string().optional().describe("Required for fd documents; omitted for prd."),
296
- content: z7.string().optional().describe("Optional initial markdown content.")
477
+ workItemId: z10.string().optional().describe("Required for fd documents; omitted for prd."),
478
+ content: z10.string().optional().describe("Optional initial markdown content.")
297
479
  },
298
480
  annotations: {
299
481
  readOnlyHint: false,
@@ -315,18 +497,18 @@ function registerCreateDocument(server) {
315
497
  }
316
498
 
317
499
  // src/tools/update-document.ts
318
- import { z as z8 } from "zod";
500
+ import { z as z11 } from "zod";
319
501
  function registerUpdateDocument(server) {
320
502
  server.registerTool(
321
503
  "update_document",
322
504
  {
323
505
  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.",
324
506
  inputSchema: {
325
- documentId: z8.string().describe("documentId from list_documents"),
326
- content: z8.string().optional().describe(
507
+ documentId: z11.string().describe("documentId from list_documents"),
508
+ content: z11.string().optional().describe(
327
509
  "New markdown content. Replaces the document's existing content."
328
510
  ),
329
- title: z8.string().min(1).optional().describe("New title (optional).")
511
+ title: z11.string().min(1).optional().describe("New title (optional).")
330
512
  },
331
513
  annotations: {
332
514
  readOnlyHint: false,
@@ -348,15 +530,15 @@ function registerUpdateDocument(server) {
348
530
  }
349
531
 
350
532
  // src/tools/list-documents.ts
351
- import { z as z9 } from "zod";
533
+ import { z as z12 } from "zod";
352
534
  function registerListDocuments(server) {
353
535
  server.registerTool(
354
536
  "list_documents",
355
537
  {
356
538
  description: "List PRD/FD markdown documents for a project, optionally filtered to one work item.",
357
539
  inputSchema: {
358
- projectId: z9.string().describe("projectId from list_projects"),
359
- workItemId: z9.string().optional().describe("Optional workItemId to list linked FD documents.")
540
+ projectId: z12.string().describe("projectId from list_projects"),
541
+ workItemId: z12.string().optional().describe("Optional workItemId to list linked FD documents.")
360
542
  },
361
543
  annotations: {
362
544
  readOnlyHint: true,
@@ -376,8 +558,8 @@ function registerListDocuments(server) {
376
558
  }
377
559
 
378
560
  // src/tools/create-project-link.ts
379
- import { z as z10 } from "zod";
380
- var projectLinkCategory = z10.enum([
561
+ import { z as z13 } from "zod";
562
+ var projectLinkCategory = z13.enum([
381
563
  "api",
382
564
  "dashboard",
383
565
  "documentation",
@@ -391,10 +573,10 @@ function registerCreateProjectLink(server) {
391
573
  {
392
574
  description: "Save a link on a project for resources like APIs, dashboards, documentation, repositories, or services.",
393
575
  inputSchema: {
394
- projectId: z10.string().describe("projectId from list_projects"),
395
- title: z10.string().min(1).max(120),
396
- url: z10.string().url().describe("http(s) URL to save"),
397
- description: z10.string().max(500).optional(),
576
+ projectId: z13.string().describe("projectId from list_projects"),
577
+ title: z13.string().min(1).max(120),
578
+ url: z13.string().url().describe("http(s) URL to save"),
579
+ description: z13.string().max(500).optional(),
398
580
  category: projectLinkCategory.optional().describe("Defaults to other.")
399
581
  },
400
582
  annotations: {
@@ -417,14 +599,14 @@ function registerCreateProjectLink(server) {
417
599
  }
418
600
 
419
601
  // src/tools/list-project-links.ts
420
- import { z as z11 } from "zod";
602
+ import { z as z14 } from "zod";
421
603
  function registerListProjectLinks(server) {
422
604
  server.registerTool(
423
605
  "list_project_links",
424
606
  {
425
607
  description: "List saved links for a project, such as APIs, dashboards, docs, repositories, and services.",
426
608
  inputSchema: {
427
- projectId: z11.string().describe("projectId from list_projects")
609
+ projectId: z14.string().describe("projectId from list_projects")
428
610
  },
429
611
  annotations: {
430
612
  readOnlyHint: true,
@@ -444,17 +626,17 @@ function registerListProjectLinks(server) {
444
626
  }
445
627
 
446
628
  // src/tools/add-work-item-image.ts
447
- import { z as z12 } from "zod";
629
+ import { z as z15 } from "zod";
448
630
  function registerAddWorkItemImage(server) {
449
631
  server.registerTool(
450
632
  "add_work_item_image",
451
633
  {
452
634
  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.",
453
635
  inputSchema: {
454
- workItemId: z12.string().describe("workItemId from list_work_items"),
455
- imageUrl: z12.string().url().describe("Public or otherwise fetchable http(s) image URL"),
456
- filename: z12.string().optional().describe("Optional display filename"),
457
- altText: z12.string().optional().describe("Optional description/caption for the image")
636
+ workItemId: z15.string().describe("workItemId from list_work_items"),
637
+ imageUrl: z15.string().url().describe("Public or otherwise fetchable http(s) image URL"),
638
+ filename: z15.string().optional().describe("Optional display filename"),
639
+ altText: z15.string().optional().describe("Optional description/caption for the image")
458
640
  },
459
641
  annotations: {
460
642
  readOnlyHint: false,
@@ -476,14 +658,14 @@ function registerAddWorkItemImage(server) {
476
658
  }
477
659
 
478
660
  // src/tools/list-work-item-images.ts
479
- import { z as z13 } from "zod";
661
+ import { z as z16 } from "zod";
480
662
  function registerListWorkItemImages(server) {
481
663
  server.registerTool(
482
664
  "list_work_item_images",
483
665
  {
484
666
  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.",
485
667
  inputSchema: {
486
- workItemId: z13.string().describe("workItemId from list_work_items")
668
+ workItemId: z16.string().describe("workItemId from list_work_items")
487
669
  },
488
670
  annotations: {
489
671
  readOnlyHint: true,
@@ -506,16 +688,16 @@ function registerListWorkItemImages(server) {
506
688
  }
507
689
 
508
690
  // src/tools/add-work-item-comment.ts
509
- import { z as z14 } from "zod";
691
+ import { z as z17 } from "zod";
510
692
  function registerAddWorkItemComment(server) {
511
693
  server.registerTool(
512
694
  "add_work_item_comment",
513
695
  {
514
696
  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.",
515
697
  inputSchema: {
516
- workItemId: z14.string().describe("workItemId from list_work_items"),
517
- body: z14.string().min(1).describe("Comment text (markdown supported)"),
518
- source: z14.enum(["user", "ai"]).optional().describe('Who authored it; defaults to "ai" for MCP callers')
698
+ workItemId: z17.string().describe("workItemId from list_work_items"),
699
+ body: z17.string().min(1).describe("Comment text (markdown supported)"),
700
+ source: z17.enum(["user", "ai"]).optional().describe('Who authored it; defaults to "ai" for MCP callers')
519
701
  },
520
702
  annotations: {
521
703
  readOnlyHint: false,
@@ -540,14 +722,14 @@ function registerAddWorkItemComment(server) {
540
722
  }
541
723
 
542
724
  // src/tools/list-work-item-comments.ts
543
- import { z as z15 } from "zod";
725
+ import { z as z18 } from "zod";
544
726
  function registerListWorkItemComments(server) {
545
727
  server.registerTool(
546
728
  "list_work_item_comments",
547
729
  {
548
730
  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.",
549
731
  inputSchema: {
550
- workItemId: z15.string().describe("workItemId from list_work_items")
732
+ workItemId: z18.string().describe("workItemId from list_work_items")
551
733
  },
552
734
  annotations: {
553
735
  readOnlyHint: true,
@@ -570,14 +752,14 @@ function registerListWorkItemComments(server) {
570
752
  }
571
753
 
572
754
  // src/tools/list-validations.ts
573
- import { z as z16 } from "zod";
755
+ import { z as z19 } from "zod";
574
756
  function registerListValidations(server) {
575
757
  server.registerTool(
576
758
  "list_validations",
577
759
  {
578
760
  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.",
579
761
  inputSchema: {
580
- projectId: z16.string().describe("projectId from list_projects")
762
+ projectId: z19.string().describe("projectId from list_projects")
581
763
  },
582
764
  annotations: {
583
765
  readOnlyHint: true,
@@ -598,15 +780,15 @@ function registerListValidations(server) {
598
780
  }
599
781
 
600
782
  // src/tools/record-validation.ts
601
- import { z as z17 } from "zod";
783
+ import { z as z20 } from "zod";
602
784
  function registerRecordValidation(server) {
603
785
  server.registerTool(
604
786
  "record_validation",
605
787
  {
606
788
  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.",
607
789
  inputSchema: {
608
- validationTaskId: z17.string().describe("validationTaskId from list_validations"),
609
- result: z17.enum(["pass", "fail"]).describe("Outcome of running the test")
790
+ validationTaskId: z20.string().describe("validationTaskId from list_validations"),
791
+ result: z20.enum(["pass", "fail"]).describe("Outcome of running the test")
610
792
  },
611
793
  annotations: {
612
794
  readOnlyHint: false,
@@ -630,14 +812,14 @@ function registerRecordValidation(server) {
630
812
  }
631
813
 
632
814
  // src/tools/mark-system-changed.ts
633
- import { z as z18 } from "zod";
815
+ import { z as z21 } from "zod";
634
816
  function registerMarkSystemChanged(server) {
635
817
  server.registerTool(
636
818
  "mark_system_changed",
637
819
  {
638
820
  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.",
639
821
  inputSchema: {
640
- systemId: z18.string().describe("systemId from list_validations")
822
+ systemId: z21.string().describe("systemId from list_validations")
641
823
  },
642
824
  annotations: {
643
825
  readOnlyHint: false,
@@ -660,16 +842,16 @@ function registerMarkSystemChanged(server) {
660
842
  }
661
843
 
662
844
  // src/tools/create-system.ts
663
- import { z as z19 } from "zod";
845
+ import { z as z22 } from "zod";
664
846
  function registerCreateSystem(server) {
665
847
  server.registerTool(
666
848
  "create_system",
667
849
  {
668
850
  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.",
669
851
  inputSchema: {
670
- projectId: z19.string().describe("projectId from list_projects"),
671
- name: z19.string().min(1).describe("Short system name, e.g. 'Auth'"),
672
- description: z19.string().optional().describe("Optional details")
852
+ projectId: z22.string().describe("projectId from list_projects"),
853
+ name: z22.string().min(1).describe("Short system name, e.g. 'Auth'"),
854
+ description: z22.string().optional().describe("Optional details")
673
855
  },
674
856
  annotations: {
675
857
  readOnlyHint: false,
@@ -692,20 +874,20 @@ function registerCreateSystem(server) {
692
874
  }
693
875
 
694
876
  // src/tools/create-validation-task.ts
695
- import { z as z20 } from "zod";
877
+ import { z as z23 } from "zod";
696
878
  function registerCreateValidationTask(server) {
697
879
  server.registerTool(
698
880
  "create_validation_task",
699
881
  {
700
882
  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.",
701
883
  inputSchema: {
702
- projectId: z20.string().describe("projectId from list_projects"),
703
- title: z20.string().min(1).describe("What this test validates"),
704
- description: z20.string().optional().describe("Optional steps / details"),
705
- kind: z20.enum(["auto", "manual"]).optional().describe(
884
+ projectId: z23.string().describe("projectId from list_projects"),
885
+ title: z23.string().min(1).describe("What this test validates"),
886
+ description: z23.string().optional().describe("Optional steps / details"),
887
+ kind: z23.enum(["auto", "manual"]).optional().describe(
706
888
  "'auto' if runnable by a test runner, 'manual' if a human checks it. Defaults to manual."
707
889
  ),
708
- workItemId: z20.string().optional().describe(
890
+ workItemId: z23.string().optional().describe(
709
891
  "Optional workItemId to link this test to a feature for traceability"
710
892
  )
711
893
  },
@@ -730,15 +912,15 @@ function registerCreateValidationTask(server) {
730
912
  }
731
913
 
732
914
  // src/tools/add-system-to-task.ts
733
- import { z as z21 } from "zod";
915
+ import { z as z24 } from "zod";
734
916
  function registerAddSystemToTask(server) {
735
917
  server.registerTool(
736
918
  "add_system_to_task",
737
919
  {
738
920
  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.",
739
921
  inputSchema: {
740
- validationTaskId: z21.string().describe("validationTaskId from list_validations"),
741
- systemId: z21.string().describe("systemId from list_validations")
922
+ validationTaskId: z24.string().describe("validationTaskId from list_validations"),
923
+ systemId: z24.string().describe("systemId from list_validations")
742
924
  },
743
925
  annotations: {
744
926
  readOnlyHint: false,
@@ -762,15 +944,15 @@ function registerAddSystemToTask(server) {
762
944
  }
763
945
 
764
946
  // src/tools/add-validation-dependency.ts
765
- import { z as z22 } from "zod";
947
+ import { z as z25 } from "zod";
766
948
  function registerAddValidationDependency(server) {
767
949
  server.registerTool(
768
950
  "add_validation_dependency",
769
951
  {
770
952
  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.",
771
953
  inputSchema: {
772
- validationTaskId: z22.string().describe("The dependent (downstream) task"),
773
- dependsOnTaskId: z22.string().describe("The dependency (upstream) task it relies on")
954
+ validationTaskId: z25.string().describe("The dependent (downstream) task"),
955
+ dependsOnTaskId: z25.string().describe("The dependency (upstream) task it relies on")
774
956
  },
775
957
  annotations: {
776
958
  readOnlyHint: false,
@@ -794,14 +976,14 @@ function registerAddValidationDependency(server) {
794
976
  }
795
977
 
796
978
  // src/tools/list-labels.ts
797
- import { z as z23 } from "zod";
979
+ import { z as z26 } from "zod";
798
980
  function registerListLabels(server) {
799
981
  server.registerTool(
800
982
  "list_labels",
801
983
  {
802
984
  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.",
803
985
  inputSchema: {
804
- projectId: z23.string().describe("projectId from list_projects")
986
+ projectId: z26.string().describe("projectId from list_projects")
805
987
  },
806
988
  annotations: {
807
989
  readOnlyHint: true,
@@ -821,16 +1003,16 @@ function registerListLabels(server) {
821
1003
  }
822
1004
 
823
1005
  // src/tools/create-label.ts
824
- import { z as z24 } from "zod";
1006
+ import { z as z27 } from "zod";
825
1007
  function registerCreateLabel(server) {
826
1008
  server.registerTool(
827
1009
  "create_label",
828
1010
  {
829
- description: "Create a label in a project (or return the existing one if the name is already taken \u2014 idempotent). Returns the labelId. Colors are optional; a neutral default is used when omitted.",
1011
+ 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.",
830
1012
  inputSchema: {
831
- projectId: z24.string().describe("projectId from list_projects"),
832
- name: z24.string().min(1).describe("Label name, unique within the project"),
833
- color: z24.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Hex color like #2563eb. Defaults to a neutral slate.")
1013
+ projectId: z27.string().describe("projectId from list_projects"),
1014
+ name: z27.string().min(1).describe("Label name, unique within the project"),
1015
+ color: z27.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Hex color like #2563eb. Defaults to a neutral slate.")
834
1016
  },
835
1017
  annotations: {
836
1018
  readOnlyHint: false,
@@ -852,15 +1034,15 @@ function registerCreateLabel(server) {
852
1034
  }
853
1035
 
854
1036
  // src/tools/add-label-to-work-item.ts
855
- import { z as z25 } from "zod";
1037
+ import { z as z28 } from "zod";
856
1038
  function registerAddLabelToWorkItem(server) {
857
1039
  server.registerTool(
858
1040
  "add_label_to_work_item",
859
1041
  {
860
1042
  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.",
861
1043
  inputSchema: {
862
- workItemId: z25.string().describe("workItemId from list_work_items"),
863
- labelId: z25.string().describe("labelId from list_labels or create_label")
1044
+ workItemId: z28.string().describe("workItemId from list_work_items"),
1045
+ labelId: z28.string().describe("labelId from list_labels or create_label")
864
1046
  },
865
1047
  annotations: {
866
1048
  readOnlyHint: false,
@@ -883,15 +1065,15 @@ function registerAddLabelToWorkItem(server) {
883
1065
  }
884
1066
 
885
1067
  // src/tools/remove-label-from-work-item.ts
886
- import { z as z26 } from "zod";
1068
+ import { z as z29 } from "zod";
887
1069
  function registerRemoveLabelFromWorkItem(server) {
888
1070
  server.registerTool(
889
1071
  "remove_label_from_work_item",
890
1072
  {
891
1073
  description: "Remove a label from a work item (idempotent \u2014 a no-op if it wasn't attached).",
892
1074
  inputSchema: {
893
- workItemId: z26.string().describe("workItemId from list_work_items"),
894
- labelId: z26.string().describe("labelId from list_labels")
1075
+ workItemId: z29.string().describe("workItemId from list_work_items"),
1076
+ labelId: z29.string().describe("labelId from list_labels")
895
1077
  },
896
1078
  annotations: {
897
1079
  readOnlyHint: false,
@@ -914,14 +1096,14 @@ function registerRemoveLabelFromWorkItem(server) {
914
1096
  }
915
1097
 
916
1098
  // src/tools/list-milestones.ts
917
- import { z as z27 } from "zod";
1099
+ import { z as z30 } from "zod";
918
1100
  function registerListMilestones(server) {
919
1101
  server.registerTool(
920
1102
  "list_milestones",
921
1103
  {
922
1104
  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.",
923
1105
  inputSchema: {
924
- projectId: z27.string().describe("projectId from list_projects")
1106
+ projectId: z30.string().describe("projectId from list_projects")
925
1107
  },
926
1108
  annotations: {
927
1109
  readOnlyHint: true,
@@ -941,17 +1123,17 @@ function registerListMilestones(server) {
941
1123
  }
942
1124
 
943
1125
  // src/tools/create-milestone.ts
944
- import { z as z28 } from "zod";
1126
+ import { z as z31 } from "zod";
945
1127
  function registerCreateMilestone(server) {
946
1128
  server.registerTool(
947
1129
  "create_milestone",
948
1130
  {
949
1131
  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').",
950
1132
  inputSchema: {
951
- projectId: z28.string().describe("projectId from list_projects"),
952
- name: z28.string().min(1).describe("Milestone name"),
953
- description: z28.string().optional().describe("Optional markdown description"),
954
- dueDate: z28.number().optional().describe("Optional due date as a unix timestamp in milliseconds")
1133
+ projectId: z31.string().describe("projectId from list_projects"),
1134
+ name: z31.string().min(1).describe("Milestone name"),
1135
+ description: z31.string().optional().describe("Optional markdown description"),
1136
+ dueDate: z31.number().optional().describe("Optional due date as a unix timestamp in milliseconds")
955
1137
  },
956
1138
  annotations: {
957
1139
  readOnlyHint: false,
@@ -973,15 +1155,15 @@ function registerCreateMilestone(server) {
973
1155
  }
974
1156
 
975
1157
  // src/tools/add-item-to-milestone.ts
976
- import { z as z29 } from "zod";
1158
+ import { z as z32 } from "zod";
977
1159
  function registerAddItemToMilestone(server) {
978
1160
  server.registerTool(
979
1161
  "add_item_to_milestone",
980
1162
  {
981
1163
  description: "Associate a work item with a milestone (idempotent). Both must be in the same project.",
982
1164
  inputSchema: {
983
- milestoneId: z29.string().describe("milestoneId from list_milestones or create_milestone"),
984
- workItemId: z29.string().describe("workItemId from list_work_items")
1165
+ milestoneId: z32.string().describe("milestoneId from list_milestones or create_milestone"),
1166
+ workItemId: z32.string().describe("workItemId from list_work_items")
985
1167
  },
986
1168
  annotations: {
987
1169
  readOnlyHint: false,
@@ -1007,15 +1189,15 @@ function registerAddItemToMilestone(server) {
1007
1189
  }
1008
1190
 
1009
1191
  // src/tools/remove-item-from-milestone.ts
1010
- import { z as z30 } from "zod";
1192
+ import { z as z33 } from "zod";
1011
1193
  function registerRemoveItemFromMilestone(server) {
1012
1194
  server.registerTool(
1013
1195
  "remove_item_from_milestone",
1014
1196
  {
1015
1197
  description: "Remove a work item's association with a milestone (idempotent \u2014 a no-op if it wasn't associated).",
1016
1198
  inputSchema: {
1017
- milestoneId: z30.string().describe("milestoneId from list_milestones"),
1018
- workItemId: z30.string().describe("workItemId from list_work_items")
1199
+ milestoneId: z33.string().describe("milestoneId from list_milestones"),
1200
+ workItemId: z33.string().describe("workItemId from list_work_items")
1019
1201
  },
1020
1202
  annotations: {
1021
1203
  readOnlyHint: false,
@@ -1049,10 +1231,13 @@ function createServer() {
1049
1231
  registerListOrgs(server);
1050
1232
  registerListProjects(server);
1051
1233
  registerListWorkItems(server);
1234
+ registerGetWorkItems(server);
1235
+ registerSearchWorkItems(server);
1052
1236
  registerListAiReviewQueue(server);
1053
1237
  registerCreateProject(server);
1054
1238
  registerCreateWorkItem(server);
1055
1239
  registerUpdateWorkItem(server);
1240
+ registerMoveWorkItem(server);
1056
1241
  registerCreateDocument(server);
1057
1242
  registerUpdateDocument(server);
1058
1243
  registerListDocuments(server);