@nimara-app/mcp 0.1.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 +15 -3
- package/dist/index.js +402 -118
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
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,
|
|
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 ({
|
|
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/
|
|
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
|
-
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.",
|
|
132
234
|
inputSchema: {
|
|
133
|
-
projectId:
|
|
134
|
-
limit:
|
|
135
|
-
cursor:
|
|
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
|
},
|
|
@@ -155,25 +257,68 @@ function registerListAiReviewQueue(server) {
|
|
|
155
257
|
);
|
|
156
258
|
}
|
|
157
259
|
|
|
260
|
+
// src/tools/get-active-block.ts
|
|
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";
|
|
289
|
+
|
|
290
|
+
// src/tools/titleGuidance.ts
|
|
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.';
|
|
292
|
+
|
|
293
|
+
// src/tools/statusGuidance.ts
|
|
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.';
|
|
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
|
+
|
|
158
304
|
// src/tools/create-work-item.ts
|
|
159
|
-
import { z as z4 } from "zod";
|
|
160
305
|
function registerCreateWorkItem(server) {
|
|
161
306
|
server.registerTool(
|
|
162
307
|
"create_work_item",
|
|
163
308
|
{
|
|
164
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).",
|
|
165
310
|
inputSchema: {
|
|
166
|
-
projectId:
|
|
167
|
-
type:
|
|
168
|
-
title:
|
|
169
|
-
description:
|
|
170
|
-
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(
|
|
171
316
|
"workItemId of the parent. Required for subtask, optional otherwise."
|
|
172
317
|
),
|
|
173
|
-
status:
|
|
174
|
-
|
|
318
|
+
status: z7.string().optional().describe(
|
|
319
|
+
STATUS_GUIDANCE
|
|
175
320
|
),
|
|
176
|
-
priority:
|
|
321
|
+
priority: z7.enum(["p0", "p1", "p2", "p3"]).optional().describe("p0 highest, p3 lowest. Defaults to p2.")
|
|
177
322
|
},
|
|
178
323
|
annotations: {
|
|
179
324
|
readOnlyHint: false,
|
|
@@ -196,23 +341,23 @@ function registerCreateWorkItem(server) {
|
|
|
196
341
|
}
|
|
197
342
|
|
|
198
343
|
// src/tools/create-project.ts
|
|
199
|
-
import { z as
|
|
344
|
+
import { z as z8 } from "zod";
|
|
200
345
|
function registerCreateProject(server) {
|
|
201
346
|
server.registerTool(
|
|
202
347
|
"create_project",
|
|
203
348
|
{
|
|
204
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.",
|
|
205
350
|
inputSchema: {
|
|
206
|
-
orgId:
|
|
207
|
-
name:
|
|
208
|
-
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(
|
|
209
354
|
"Short uppercase prefix for work item IDs (e.g. 'NIM'). Unique per org."
|
|
210
355
|
),
|
|
211
|
-
description:
|
|
212
|
-
isPrivate:
|
|
356
|
+
description: z8.string().optional().describe("Optional markdown description"),
|
|
357
|
+
isPrivate: z8.boolean().optional().describe(
|
|
213
358
|
"If true, only explicitly added members see it. Defaults to false."
|
|
214
359
|
),
|
|
215
|
-
workflowTemplate:
|
|
360
|
+
workflowTemplate: z8.enum(["default", "simple", "kanban", "bug"]).optional().describe(
|
|
216
361
|
"Status workflow. Defaults to 'default' (Backlog/Todo/In Progress/Review/Done)."
|
|
217
362
|
)
|
|
218
363
|
},
|
|
@@ -237,27 +382,61 @@ function registerCreateProject(server) {
|
|
|
237
382
|
}
|
|
238
383
|
|
|
239
384
|
// src/tools/update-work-item.ts
|
|
240
|
-
import { z as
|
|
385
|
+
import { z as z9 } from "zod";
|
|
386
|
+
|
|
387
|
+
// src/tools/researchFormat.ts
|
|
388
|
+
var RESEARCH_FORMAT = `When appending automated research to a description, use EXACTLY this format, after the human-written text:
|
|
389
|
+
|
|
390
|
+
---
|
|
391
|
+
## Research (auto)
|
|
392
|
+
|
|
393
|
+
### Findings
|
|
394
|
+
- One bullet per verified fact, each naming what you checked.
|
|
395
|
+
|
|
396
|
+
### Dependencies
|
|
397
|
+
- Items this blocks or is blocked by, by displayId. Omit the section if none.
|
|
398
|
+
|
|
399
|
+
### Duplicates
|
|
400
|
+
- Existing items covering the same work, by displayId. Omit if none.
|
|
401
|
+
|
|
402
|
+
### Suggested priority
|
|
403
|
+
One line. Say "No change" when it is already right.
|
|
404
|
+
|
|
405
|
+
Rules:
|
|
406
|
+
- 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.
|
|
407
|
+
- NO EMOJI anywhere in the block, in headings or in bullets. Older items carry emoji headings; do not copy them.
|
|
408
|
+
- Omit a section rather than writing "None found" in it. An empty section is noise the reader still has to read.
|
|
409
|
+
- Never edit or delete the human-written text above the rule. Append only.
|
|
410
|
+
- 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.
|
|
411
|
+
- For a workflow, sequence or state machine, use a \\\`\\\`\\\`mermaid fenced block. That is the supported way to draw one.
|
|
412
|
+
- Say what you verified and how. "Confirmed X still returns Y (file:line)" is worth more than a conclusion with no trail.`;
|
|
413
|
+
|
|
414
|
+
// src/tools/update-work-item.ts
|
|
241
415
|
function registerUpdateWorkItem(server) {
|
|
242
416
|
server.registerTool(
|
|
243
417
|
"update_work_item",
|
|
244
418
|
{
|
|
245
419
|
description: "Update an existing work item: title, description, status, priority, parent, or review flags. Any update also stamps the item as AI-touched.",
|
|
246
420
|
inputSchema: {
|
|
247
|
-
workItemId:
|
|
248
|
-
title:
|
|
249
|
-
description:
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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
|
|
425
|
+
),
|
|
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(
|
|
254
430
|
"Flag the item for manual human review \u2014 set true when your enrichment was thin or uncertain."
|
|
255
431
|
),
|
|
256
|
-
reviewReason:
|
|
432
|
+
reviewReason: z9.string().optional().describe(
|
|
257
433
|
"Short reason the item needs manual review (shown to the human)."
|
|
258
434
|
),
|
|
259
|
-
aiReviewRequested:
|
|
435
|
+
aiReviewRequested: z9.boolean().optional().describe(
|
|
260
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."
|
|
261
440
|
)
|
|
262
441
|
},
|
|
263
442
|
annotations: {
|
|
@@ -279,21 +458,62 @@ function registerUpdateWorkItem(server) {
|
|
|
279
458
|
);
|
|
280
459
|
}
|
|
281
460
|
|
|
461
|
+
// src/tools/move-work-item.ts
|
|
462
|
+
import { z as z10 } from "zod";
|
|
463
|
+
function registerMoveWorkItem(server) {
|
|
464
|
+
server.registerTool(
|
|
465
|
+
"move_work_item",
|
|
466
|
+
{
|
|
467
|
+
// Everything a model needs to predict the damage has to be stated here —
|
|
468
|
+
// the description is the only channel it has. The consequences that are
|
|
469
|
+
// NOT obvious from the name are the ones worth the tokens: the display ID
|
|
470
|
+
// changes, and labels/milestones do not survive.
|
|
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.",
|
|
472
|
+
inputSchema: {
|
|
473
|
+
workItemId: z10.string().describe("workItemId from list_work_items \u2014 the item to move."),
|
|
474
|
+
targetProjectId: z10.string().describe(
|
|
475
|
+
"projectId from list_projects \u2014 the destination. Must be in the same org as the item's current project."
|
|
476
|
+
)
|
|
477
|
+
},
|
|
478
|
+
annotations: {
|
|
479
|
+
readOnlyHint: false,
|
|
480
|
+
// Renumbering display IDs and deleting label/milestone links are
|
|
481
|
+
// overwrites, not additions — this belongs with the tools a client
|
|
482
|
+
// should prompt on rather than auto-approve.
|
|
483
|
+
destructiveHint: true,
|
|
484
|
+
// Calling it twice does not land in the same place: the second call
|
|
485
|
+
// either errors as already-there or moves the item on again.
|
|
486
|
+
idempotentHint: false,
|
|
487
|
+
openWorldHint: false
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
async ({ workItemId, targetProjectId }) => {
|
|
491
|
+
const result = await getConvexClient().action(
|
|
492
|
+
api.mcp.moveWorkItemToProject,
|
|
493
|
+
{ token: getToken(), workItemId, targetProjectId }
|
|
494
|
+
);
|
|
495
|
+
return {
|
|
496
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
);
|
|
500
|
+
}
|
|
501
|
+
|
|
282
502
|
// src/tools/create-document.ts
|
|
283
|
-
import { z as
|
|
503
|
+
import { z as z11 } from "zod";
|
|
284
504
|
function registerCreateDocument(server) {
|
|
285
505
|
server.registerTool(
|
|
286
506
|
"create_document",
|
|
287
507
|
{
|
|
288
508
|
description: "Create a markdown project document. Use type 'prd' for project docs or 'fd' for feature/work-item documents.",
|
|
289
509
|
inputSchema: {
|
|
290
|
-
projectId:
|
|
291
|
-
title:
|
|
292
|
-
type:
|
|
510
|
+
projectId: z11.string().describe("projectId from list_projects"),
|
|
511
|
+
title: z11.string().min(1),
|
|
512
|
+
type: z11.enum(["prd", "fd"]).describe(
|
|
293
513
|
"Document type. 'fd' is stored as the app's feature document type."
|
|
294
514
|
),
|
|
295
|
-
workItemId:
|
|
296
|
-
content:
|
|
515
|
+
workItemId: z11.string().optional().describe("Required for fd documents; omitted for prd."),
|
|
516
|
+
content: z11.string().optional().describe("Optional initial markdown content.")
|
|
297
517
|
},
|
|
298
518
|
annotations: {
|
|
299
519
|
readOnlyHint: false,
|
|
@@ -315,18 +535,18 @@ function registerCreateDocument(server) {
|
|
|
315
535
|
}
|
|
316
536
|
|
|
317
537
|
// src/tools/update-document.ts
|
|
318
|
-
import { z as
|
|
538
|
+
import { z as z12 } from "zod";
|
|
319
539
|
function registerUpdateDocument(server) {
|
|
320
540
|
server.registerTool(
|
|
321
541
|
"update_document",
|
|
322
542
|
{
|
|
323
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.",
|
|
324
544
|
inputSchema: {
|
|
325
|
-
documentId:
|
|
326
|
-
content:
|
|
545
|
+
documentId: z12.string().describe("documentId from list_documents"),
|
|
546
|
+
content: z12.string().optional().describe(
|
|
327
547
|
"New markdown content. Replaces the document's existing content."
|
|
328
548
|
),
|
|
329
|
-
title:
|
|
549
|
+
title: z12.string().min(1).optional().describe("New title (optional).")
|
|
330
550
|
},
|
|
331
551
|
annotations: {
|
|
332
552
|
readOnlyHint: false,
|
|
@@ -348,15 +568,15 @@ function registerUpdateDocument(server) {
|
|
|
348
568
|
}
|
|
349
569
|
|
|
350
570
|
// src/tools/list-documents.ts
|
|
351
|
-
import { z as
|
|
571
|
+
import { z as z13 } from "zod";
|
|
352
572
|
function registerListDocuments(server) {
|
|
353
573
|
server.registerTool(
|
|
354
574
|
"list_documents",
|
|
355
575
|
{
|
|
356
576
|
description: "List PRD/FD markdown documents for a project, optionally filtered to one work item.",
|
|
357
577
|
inputSchema: {
|
|
358
|
-
projectId:
|
|
359
|
-
workItemId:
|
|
578
|
+
projectId: z13.string().describe("projectId from list_projects"),
|
|
579
|
+
workItemId: z13.string().optional().describe("Optional workItemId to list linked FD documents.")
|
|
360
580
|
},
|
|
361
581
|
annotations: {
|
|
362
582
|
readOnlyHint: true,
|
|
@@ -376,8 +596,8 @@ function registerListDocuments(server) {
|
|
|
376
596
|
}
|
|
377
597
|
|
|
378
598
|
// src/tools/create-project-link.ts
|
|
379
|
-
import { z as
|
|
380
|
-
var projectLinkCategory =
|
|
599
|
+
import { z as z14 } from "zod";
|
|
600
|
+
var projectLinkCategory = z14.enum([
|
|
381
601
|
"api",
|
|
382
602
|
"dashboard",
|
|
383
603
|
"documentation",
|
|
@@ -391,10 +611,10 @@ function registerCreateProjectLink(server) {
|
|
|
391
611
|
{
|
|
392
612
|
description: "Save a link on a project for resources like APIs, dashboards, documentation, repositories, or services.",
|
|
393
613
|
inputSchema: {
|
|
394
|
-
projectId:
|
|
395
|
-
title:
|
|
396
|
-
url:
|
|
397
|
-
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(),
|
|
398
618
|
category: projectLinkCategory.optional().describe("Defaults to other.")
|
|
399
619
|
},
|
|
400
620
|
annotations: {
|
|
@@ -417,14 +637,14 @@ function registerCreateProjectLink(server) {
|
|
|
417
637
|
}
|
|
418
638
|
|
|
419
639
|
// src/tools/list-project-links.ts
|
|
420
|
-
import { z as
|
|
640
|
+
import { z as z15 } from "zod";
|
|
421
641
|
function registerListProjectLinks(server) {
|
|
422
642
|
server.registerTool(
|
|
423
643
|
"list_project_links",
|
|
424
644
|
{
|
|
425
645
|
description: "List saved links for a project, such as APIs, dashboards, docs, repositories, and services.",
|
|
426
646
|
inputSchema: {
|
|
427
|
-
projectId:
|
|
647
|
+
projectId: z15.string().describe("projectId from list_projects")
|
|
428
648
|
},
|
|
429
649
|
annotations: {
|
|
430
650
|
readOnlyHint: true,
|
|
@@ -443,18 +663,76 @@ function registerListProjectLinks(server) {
|
|
|
443
663
|
);
|
|
444
664
|
}
|
|
445
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
|
+
|
|
446
724
|
// src/tools/add-work-item-image.ts
|
|
447
|
-
import { z as
|
|
725
|
+
import { z as z18 } from "zod";
|
|
448
726
|
function registerAddWorkItemImage(server) {
|
|
449
727
|
server.registerTool(
|
|
450
728
|
"add_work_item_image",
|
|
451
729
|
{
|
|
452
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.",
|
|
453
731
|
inputSchema: {
|
|
454
|
-
workItemId:
|
|
455
|
-
imageUrl:
|
|
456
|
-
filename:
|
|
457
|
-
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")
|
|
458
736
|
},
|
|
459
737
|
annotations: {
|
|
460
738
|
readOnlyHint: false,
|
|
@@ -476,14 +754,14 @@ function registerAddWorkItemImage(server) {
|
|
|
476
754
|
}
|
|
477
755
|
|
|
478
756
|
// src/tools/list-work-item-images.ts
|
|
479
|
-
import { z as
|
|
757
|
+
import { z as z19 } from "zod";
|
|
480
758
|
function registerListWorkItemImages(server) {
|
|
481
759
|
server.registerTool(
|
|
482
760
|
"list_work_item_images",
|
|
483
761
|
{
|
|
484
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.",
|
|
485
763
|
inputSchema: {
|
|
486
|
-
workItemId:
|
|
764
|
+
workItemId: z19.string().describe("workItemId from list_work_items")
|
|
487
765
|
},
|
|
488
766
|
annotations: {
|
|
489
767
|
readOnlyHint: true,
|
|
@@ -506,16 +784,16 @@ function registerListWorkItemImages(server) {
|
|
|
506
784
|
}
|
|
507
785
|
|
|
508
786
|
// src/tools/add-work-item-comment.ts
|
|
509
|
-
import { z as
|
|
787
|
+
import { z as z20 } from "zod";
|
|
510
788
|
function registerAddWorkItemComment(server) {
|
|
511
789
|
server.registerTool(
|
|
512
790
|
"add_work_item_comment",
|
|
513
791
|
{
|
|
514
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.",
|
|
515
793
|
inputSchema: {
|
|
516
|
-
workItemId:
|
|
517
|
-
body:
|
|
518
|
-
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')
|
|
519
797
|
},
|
|
520
798
|
annotations: {
|
|
521
799
|
readOnlyHint: false,
|
|
@@ -540,14 +818,14 @@ function registerAddWorkItemComment(server) {
|
|
|
540
818
|
}
|
|
541
819
|
|
|
542
820
|
// src/tools/list-work-item-comments.ts
|
|
543
|
-
import { z as
|
|
821
|
+
import { z as z21 } from "zod";
|
|
544
822
|
function registerListWorkItemComments(server) {
|
|
545
823
|
server.registerTool(
|
|
546
824
|
"list_work_item_comments",
|
|
547
825
|
{
|
|
548
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.",
|
|
549
827
|
inputSchema: {
|
|
550
|
-
workItemId:
|
|
828
|
+
workItemId: z21.string().describe("workItemId from list_work_items")
|
|
551
829
|
},
|
|
552
830
|
annotations: {
|
|
553
831
|
readOnlyHint: true,
|
|
@@ -570,14 +848,14 @@ function registerListWorkItemComments(server) {
|
|
|
570
848
|
}
|
|
571
849
|
|
|
572
850
|
// src/tools/list-validations.ts
|
|
573
|
-
import { z as
|
|
851
|
+
import { z as z22 } from "zod";
|
|
574
852
|
function registerListValidations(server) {
|
|
575
853
|
server.registerTool(
|
|
576
854
|
"list_validations",
|
|
577
855
|
{
|
|
578
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.",
|
|
579
857
|
inputSchema: {
|
|
580
|
-
projectId:
|
|
858
|
+
projectId: z22.string().describe("projectId from list_projects")
|
|
581
859
|
},
|
|
582
860
|
annotations: {
|
|
583
861
|
readOnlyHint: true,
|
|
@@ -598,15 +876,15 @@ function registerListValidations(server) {
|
|
|
598
876
|
}
|
|
599
877
|
|
|
600
878
|
// src/tools/record-validation.ts
|
|
601
|
-
import { z as
|
|
879
|
+
import { z as z23 } from "zod";
|
|
602
880
|
function registerRecordValidation(server) {
|
|
603
881
|
server.registerTool(
|
|
604
882
|
"record_validation",
|
|
605
883
|
{
|
|
606
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.",
|
|
607
885
|
inputSchema: {
|
|
608
|
-
validationTaskId:
|
|
609
|
-
result:
|
|
886
|
+
validationTaskId: z23.string().describe("validationTaskId from list_validations"),
|
|
887
|
+
result: z23.enum(["pass", "fail"]).describe("Outcome of running the test")
|
|
610
888
|
},
|
|
611
889
|
annotations: {
|
|
612
890
|
readOnlyHint: false,
|
|
@@ -630,14 +908,14 @@ function registerRecordValidation(server) {
|
|
|
630
908
|
}
|
|
631
909
|
|
|
632
910
|
// src/tools/mark-system-changed.ts
|
|
633
|
-
import { z as
|
|
911
|
+
import { z as z24 } from "zod";
|
|
634
912
|
function registerMarkSystemChanged(server) {
|
|
635
913
|
server.registerTool(
|
|
636
914
|
"mark_system_changed",
|
|
637
915
|
{
|
|
638
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.",
|
|
639
917
|
inputSchema: {
|
|
640
|
-
systemId:
|
|
918
|
+
systemId: z24.string().describe("systemId from list_validations")
|
|
641
919
|
},
|
|
642
920
|
annotations: {
|
|
643
921
|
readOnlyHint: false,
|
|
@@ -660,16 +938,16 @@ function registerMarkSystemChanged(server) {
|
|
|
660
938
|
}
|
|
661
939
|
|
|
662
940
|
// src/tools/create-system.ts
|
|
663
|
-
import { z as
|
|
941
|
+
import { z as z25 } from "zod";
|
|
664
942
|
function registerCreateSystem(server) {
|
|
665
943
|
server.registerTool(
|
|
666
944
|
"create_system",
|
|
667
945
|
{
|
|
668
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.",
|
|
669
947
|
inputSchema: {
|
|
670
|
-
projectId:
|
|
671
|
-
name:
|
|
672
|
-
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")
|
|
673
951
|
},
|
|
674
952
|
annotations: {
|
|
675
953
|
readOnlyHint: false,
|
|
@@ -692,20 +970,20 @@ function registerCreateSystem(server) {
|
|
|
692
970
|
}
|
|
693
971
|
|
|
694
972
|
// src/tools/create-validation-task.ts
|
|
695
|
-
import { z as
|
|
973
|
+
import { z as z26 } from "zod";
|
|
696
974
|
function registerCreateValidationTask(server) {
|
|
697
975
|
server.registerTool(
|
|
698
976
|
"create_validation_task",
|
|
699
977
|
{
|
|
700
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.",
|
|
701
979
|
inputSchema: {
|
|
702
|
-
projectId:
|
|
703
|
-
title:
|
|
704
|
-
description:
|
|
705
|
-
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(
|
|
706
984
|
"'auto' if runnable by a test runner, 'manual' if a human checks it. Defaults to manual."
|
|
707
985
|
),
|
|
708
|
-
workItemId:
|
|
986
|
+
workItemId: z26.string().optional().describe(
|
|
709
987
|
"Optional workItemId to link this test to a feature for traceability"
|
|
710
988
|
)
|
|
711
989
|
},
|
|
@@ -730,15 +1008,15 @@ function registerCreateValidationTask(server) {
|
|
|
730
1008
|
}
|
|
731
1009
|
|
|
732
1010
|
// src/tools/add-system-to-task.ts
|
|
733
|
-
import { z as
|
|
1011
|
+
import { z as z27 } from "zod";
|
|
734
1012
|
function registerAddSystemToTask(server) {
|
|
735
1013
|
server.registerTool(
|
|
736
1014
|
"add_system_to_task",
|
|
737
1015
|
{
|
|
738
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.",
|
|
739
1017
|
inputSchema: {
|
|
740
|
-
validationTaskId:
|
|
741
|
-
systemId:
|
|
1018
|
+
validationTaskId: z27.string().describe("validationTaskId from list_validations"),
|
|
1019
|
+
systemId: z27.string().describe("systemId from list_validations")
|
|
742
1020
|
},
|
|
743
1021
|
annotations: {
|
|
744
1022
|
readOnlyHint: false,
|
|
@@ -762,15 +1040,15 @@ function registerAddSystemToTask(server) {
|
|
|
762
1040
|
}
|
|
763
1041
|
|
|
764
1042
|
// src/tools/add-validation-dependency.ts
|
|
765
|
-
import { z as
|
|
1043
|
+
import { z as z28 } from "zod";
|
|
766
1044
|
function registerAddValidationDependency(server) {
|
|
767
1045
|
server.registerTool(
|
|
768
1046
|
"add_validation_dependency",
|
|
769
1047
|
{
|
|
770
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.",
|
|
771
1049
|
inputSchema: {
|
|
772
|
-
validationTaskId:
|
|
773
|
-
dependsOnTaskId:
|
|
1050
|
+
validationTaskId: z28.string().describe("The dependent (downstream) task"),
|
|
1051
|
+
dependsOnTaskId: z28.string().describe("The dependency (upstream) task it relies on")
|
|
774
1052
|
},
|
|
775
1053
|
annotations: {
|
|
776
1054
|
readOnlyHint: false,
|
|
@@ -794,14 +1072,14 @@ function registerAddValidationDependency(server) {
|
|
|
794
1072
|
}
|
|
795
1073
|
|
|
796
1074
|
// src/tools/list-labels.ts
|
|
797
|
-
import { z as
|
|
1075
|
+
import { z as z29 } from "zod";
|
|
798
1076
|
function registerListLabels(server) {
|
|
799
1077
|
server.registerTool(
|
|
800
1078
|
"list_labels",
|
|
801
1079
|
{
|
|
802
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.",
|
|
803
1081
|
inputSchema: {
|
|
804
|
-
projectId:
|
|
1082
|
+
projectId: z29.string().describe("projectId from list_projects")
|
|
805
1083
|
},
|
|
806
1084
|
annotations: {
|
|
807
1085
|
readOnlyHint: true,
|
|
@@ -821,16 +1099,16 @@ function registerListLabels(server) {
|
|
|
821
1099
|
}
|
|
822
1100
|
|
|
823
1101
|
// src/tools/create-label.ts
|
|
824
|
-
import { z as
|
|
1102
|
+
import { z as z30 } from "zod";
|
|
825
1103
|
function registerCreateLabel(server) {
|
|
826
1104
|
server.registerTool(
|
|
827
1105
|
"create_label",
|
|
828
1106
|
{
|
|
829
|
-
description: "
|
|
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.",
|
|
830
1108
|
inputSchema: {
|
|
831
|
-
projectId:
|
|
832
|
-
name:
|
|
833
|
-
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.")
|
|
834
1112
|
},
|
|
835
1113
|
annotations: {
|
|
836
1114
|
readOnlyHint: false,
|
|
@@ -852,15 +1130,15 @@ function registerCreateLabel(server) {
|
|
|
852
1130
|
}
|
|
853
1131
|
|
|
854
1132
|
// src/tools/add-label-to-work-item.ts
|
|
855
|
-
import { z as
|
|
1133
|
+
import { z as z31 } from "zod";
|
|
856
1134
|
function registerAddLabelToWorkItem(server) {
|
|
857
1135
|
server.registerTool(
|
|
858
1136
|
"add_label_to_work_item",
|
|
859
1137
|
{
|
|
860
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.",
|
|
861
1139
|
inputSchema: {
|
|
862
|
-
workItemId:
|
|
863
|
-
labelId:
|
|
1140
|
+
workItemId: z31.string().describe("workItemId from list_work_items"),
|
|
1141
|
+
labelId: z31.string().describe("labelId from list_labels or create_label")
|
|
864
1142
|
},
|
|
865
1143
|
annotations: {
|
|
866
1144
|
readOnlyHint: false,
|
|
@@ -883,15 +1161,15 @@ function registerAddLabelToWorkItem(server) {
|
|
|
883
1161
|
}
|
|
884
1162
|
|
|
885
1163
|
// src/tools/remove-label-from-work-item.ts
|
|
886
|
-
import { z as
|
|
1164
|
+
import { z as z32 } from "zod";
|
|
887
1165
|
function registerRemoveLabelFromWorkItem(server) {
|
|
888
1166
|
server.registerTool(
|
|
889
1167
|
"remove_label_from_work_item",
|
|
890
1168
|
{
|
|
891
1169
|
description: "Remove a label from a work item (idempotent \u2014 a no-op if it wasn't attached).",
|
|
892
1170
|
inputSchema: {
|
|
893
|
-
workItemId:
|
|
894
|
-
labelId:
|
|
1171
|
+
workItemId: z32.string().describe("workItemId from list_work_items"),
|
|
1172
|
+
labelId: z32.string().describe("labelId from list_labels")
|
|
895
1173
|
},
|
|
896
1174
|
annotations: {
|
|
897
1175
|
readOnlyHint: false,
|
|
@@ -914,14 +1192,14 @@ function registerRemoveLabelFromWorkItem(server) {
|
|
|
914
1192
|
}
|
|
915
1193
|
|
|
916
1194
|
// src/tools/list-milestones.ts
|
|
917
|
-
import { z as
|
|
1195
|
+
import { z as z33 } from "zod";
|
|
918
1196
|
function registerListMilestones(server) {
|
|
919
1197
|
server.registerTool(
|
|
920
1198
|
"list_milestones",
|
|
921
1199
|
{
|
|
922
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.",
|
|
923
1201
|
inputSchema: {
|
|
924
|
-
projectId:
|
|
1202
|
+
projectId: z33.string().describe("projectId from list_projects")
|
|
925
1203
|
},
|
|
926
1204
|
annotations: {
|
|
927
1205
|
readOnlyHint: true,
|
|
@@ -941,17 +1219,17 @@ function registerListMilestones(server) {
|
|
|
941
1219
|
}
|
|
942
1220
|
|
|
943
1221
|
// src/tools/create-milestone.ts
|
|
944
|
-
import { z as
|
|
1222
|
+
import { z as z34 } from "zod";
|
|
945
1223
|
function registerCreateMilestone(server) {
|
|
946
1224
|
server.registerTool(
|
|
947
1225
|
"create_milestone",
|
|
948
1226
|
{
|
|
949
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').",
|
|
950
1228
|
inputSchema: {
|
|
951
|
-
projectId:
|
|
952
|
-
name:
|
|
953
|
-
description:
|
|
954
|
-
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")
|
|
955
1233
|
},
|
|
956
1234
|
annotations: {
|
|
957
1235
|
readOnlyHint: false,
|
|
@@ -973,15 +1251,15 @@ function registerCreateMilestone(server) {
|
|
|
973
1251
|
}
|
|
974
1252
|
|
|
975
1253
|
// src/tools/add-item-to-milestone.ts
|
|
976
|
-
import { z as
|
|
1254
|
+
import { z as z35 } from "zod";
|
|
977
1255
|
function registerAddItemToMilestone(server) {
|
|
978
1256
|
server.registerTool(
|
|
979
1257
|
"add_item_to_milestone",
|
|
980
1258
|
{
|
|
981
1259
|
description: "Associate a work item with a milestone (idempotent). Both must be in the same project.",
|
|
982
1260
|
inputSchema: {
|
|
983
|
-
milestoneId:
|
|
984
|
-
workItemId:
|
|
1261
|
+
milestoneId: z35.string().describe("milestoneId from list_milestones or create_milestone"),
|
|
1262
|
+
workItemId: z35.string().describe("workItemId from list_work_items")
|
|
985
1263
|
},
|
|
986
1264
|
annotations: {
|
|
987
1265
|
readOnlyHint: false,
|
|
@@ -1007,15 +1285,15 @@ function registerAddItemToMilestone(server) {
|
|
|
1007
1285
|
}
|
|
1008
1286
|
|
|
1009
1287
|
// src/tools/remove-item-from-milestone.ts
|
|
1010
|
-
import { z as
|
|
1288
|
+
import { z as z36 } from "zod";
|
|
1011
1289
|
function registerRemoveItemFromMilestone(server) {
|
|
1012
1290
|
server.registerTool(
|
|
1013
1291
|
"remove_item_from_milestone",
|
|
1014
1292
|
{
|
|
1015
1293
|
description: "Remove a work item's association with a milestone (idempotent \u2014 a no-op if it wasn't associated).",
|
|
1016
1294
|
inputSchema: {
|
|
1017
|
-
milestoneId:
|
|
1018
|
-
workItemId:
|
|
1295
|
+
milestoneId: z36.string().describe("milestoneId from list_milestones"),
|
|
1296
|
+
workItemId: z36.string().describe("workItemId from list_work_items")
|
|
1019
1297
|
},
|
|
1020
1298
|
annotations: {
|
|
1021
1299
|
readOnlyHint: false,
|
|
@@ -1049,15 +1327,21 @@ function createServer() {
|
|
|
1049
1327
|
registerListOrgs(server);
|
|
1050
1328
|
registerListProjects(server);
|
|
1051
1329
|
registerListWorkItems(server);
|
|
1330
|
+
registerGetWorkItems(server);
|
|
1331
|
+
registerSearchWorkItems(server);
|
|
1052
1332
|
registerListAiReviewQueue(server);
|
|
1333
|
+
registerGetActiveBlock(server);
|
|
1053
1334
|
registerCreateProject(server);
|
|
1054
1335
|
registerCreateWorkItem(server);
|
|
1055
1336
|
registerUpdateWorkItem(server);
|
|
1337
|
+
registerMoveWorkItem(server);
|
|
1056
1338
|
registerCreateDocument(server);
|
|
1057
1339
|
registerUpdateDocument(server);
|
|
1058
1340
|
registerListDocuments(server);
|
|
1059
1341
|
registerCreateProjectLink(server);
|
|
1060
1342
|
registerListProjectLinks(server);
|
|
1343
|
+
registerListScratchNotes(server);
|
|
1344
|
+
registerResolveScratchNote(server);
|
|
1061
1345
|
registerAddWorkItemImage(server);
|
|
1062
1346
|
registerListWorkItemImages(server);
|
|
1063
1347
|
registerAddWorkItemComment(server);
|