@nimara-app/mcp 0.1.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 +165 -0
- package/dist/index.js +1096 -0
- package/dist/index.js.map +7 -0
- package/package.json +49 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
|
|
6
|
+
// src/server.ts
|
|
7
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
|
+
|
|
9
|
+
// ../convex/convex/_generated/api.js
|
|
10
|
+
import { anyApi, componentsGeneric } from "convex/server";
|
|
11
|
+
var api = anyApi;
|
|
12
|
+
var components = componentsGeneric();
|
|
13
|
+
|
|
14
|
+
// src/convex.ts
|
|
15
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
16
|
+
var cachedClient = null;
|
|
17
|
+
function getConvexClient() {
|
|
18
|
+
if (cachedClient) return cachedClient;
|
|
19
|
+
const url = process.env.NIMARA_CONVEX_URL;
|
|
20
|
+
if (!url) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"NIMARA_CONVEX_URL is not set. Add it to your MCP client config (e.g. Claude Desktop's mcpServers env block)."
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
cachedClient = new ConvexHttpClient(url);
|
|
26
|
+
return cachedClient;
|
|
27
|
+
}
|
|
28
|
+
function getToken() {
|
|
29
|
+
const token = process.env.NIMARA_TOKEN;
|
|
30
|
+
if (!token) {
|
|
31
|
+
throw new Error(
|
|
32
|
+
"NIMARA_TOKEN is not set. Generate a personal access token from Nimara \u2192 Settings \u2192 Personal Access Tokens, then add it to your MCP client config."
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return token;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/tools/list-orgs.ts
|
|
39
|
+
function registerListOrgs(server) {
|
|
40
|
+
server.registerTool(
|
|
41
|
+
"list_orgs",
|
|
42
|
+
{
|
|
43
|
+
description: "List all organizations (and the personal workspace) the authenticated user belongs to. Use this first to discover org IDs needed by other tools.",
|
|
44
|
+
inputSchema: {},
|
|
45
|
+
annotations: {
|
|
46
|
+
readOnlyHint: true,
|
|
47
|
+
openWorldHint: false
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
async () => {
|
|
51
|
+
const client = getConvexClient();
|
|
52
|
+
const orgs = await client.action(api.mcp.listOrgs, { token: getToken() });
|
|
53
|
+
return {
|
|
54
|
+
content: [{ type: "text", text: JSON.stringify(orgs, null, 2) }]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/tools/list-projects.ts
|
|
61
|
+
import { z } from "zod";
|
|
62
|
+
function registerListProjects(server) {
|
|
63
|
+
server.registerTool(
|
|
64
|
+
"list_projects",
|
|
65
|
+
{
|
|
66
|
+
description: "List projects in a given organization. Call list_orgs first to find the orgId.",
|
|
67
|
+
inputSchema: { orgId: z.string().describe("orgId from list_orgs") },
|
|
68
|
+
annotations: {
|
|
69
|
+
readOnlyHint: true,
|
|
70
|
+
openWorldHint: false
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
async ({ orgId }) => {
|
|
74
|
+
const client = getConvexClient();
|
|
75
|
+
const projects = await client.action(api.mcp.listProjects, {
|
|
76
|
+
token: getToken(),
|
|
77
|
+
orgId
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: JSON.stringify(projects, null, 2) }]
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/tools/list-work-items.ts
|
|
87
|
+
import { z as z2 } from "zod";
|
|
88
|
+
function registerListWorkItems(server) {
|
|
89
|
+
server.registerTool(
|
|
90
|
+
"list_work_items",
|
|
91
|
+
{
|
|
92
|
+
// The description is the only place a model learns paging exists, so it
|
|
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.",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
projectId: z2.string().describe("projectId from list_projects"),
|
|
97
|
+
limit: z2.number().int().min(1).max(200).optional().describe("Items per page (default 50, max 200)."),
|
|
98
|
+
cursor: z2.number().optional().describe(
|
|
99
|
+
"nextCursor from the previous page. Omit for the first page."
|
|
100
|
+
),
|
|
101
|
+
fullDescriptions: z2.boolean().optional().describe(
|
|
102
|
+
"Return descriptions in full instead of previewed. Costly on large projects \u2014 prefer paging, or read one item at a time."
|
|
103
|
+
)
|
|
104
|
+
},
|
|
105
|
+
annotations: {
|
|
106
|
+
readOnlyHint: true,
|
|
107
|
+
openWorldHint: false
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
async ({ projectId, limit, cursor, fullDescriptions }) => {
|
|
111
|
+
const page = await getConvexClient().action(api.mcp.listWorkItems, {
|
|
112
|
+
token: getToken(),
|
|
113
|
+
projectId,
|
|
114
|
+
limit,
|
|
115
|
+
cursor,
|
|
116
|
+
fullDescriptions
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: "text", text: JSON.stringify(page, null, 2) }]
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/tools/list-ai-review-queue.ts
|
|
126
|
+
import { z as z3 } from "zod";
|
|
127
|
+
function registerListAiReviewQueue(server) {
|
|
128
|
+
server.registerTool(
|
|
129
|
+
"list_ai_review_queue",
|
|
130
|
+
{
|
|
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.",
|
|
132
|
+
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(
|
|
136
|
+
"nextCursor from the previous page. Omit for the first page."
|
|
137
|
+
)
|
|
138
|
+
},
|
|
139
|
+
annotations: {
|
|
140
|
+
readOnlyHint: true,
|
|
141
|
+
openWorldHint: false
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
async ({ projectId, limit, cursor }) => {
|
|
145
|
+
const page = await getConvexClient().action(api.mcp.listAiReviewQueue, {
|
|
146
|
+
token: getToken(),
|
|
147
|
+
projectId,
|
|
148
|
+
limit,
|
|
149
|
+
cursor
|
|
150
|
+
});
|
|
151
|
+
return {
|
|
152
|
+
content: [{ type: "text", text: JSON.stringify(page, null, 2) }]
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/tools/create-work-item.ts
|
|
159
|
+
import { z as z4 } from "zod";
|
|
160
|
+
function registerCreateWorkItem(server) {
|
|
161
|
+
server.registerTool(
|
|
162
|
+
"create_work_item",
|
|
163
|
+
{
|
|
164
|
+
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
|
+
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(
|
|
171
|
+
"workItemId of the parent. Required for subtask, optional otherwise."
|
|
172
|
+
),
|
|
173
|
+
status: z4.string().optional().describe(
|
|
174
|
+
"Status name from the project's statusOrder. Defaults to first status (e.g. 'Backlog')."
|
|
175
|
+
),
|
|
176
|
+
priority: z4.enum(["p0", "p1", "p2", "p3"]).optional().describe("p0 highest, p3 lowest. Defaults to p2.")
|
|
177
|
+
},
|
|
178
|
+
annotations: {
|
|
179
|
+
readOnlyHint: false,
|
|
180
|
+
destructiveHint: false,
|
|
181
|
+
idempotentHint: false,
|
|
182
|
+
openWorldHint: false
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
async (args) => {
|
|
186
|
+
const client = getConvexClient();
|
|
187
|
+
const result = await client.action(api.mcp.createWorkItem, {
|
|
188
|
+
token: getToken(),
|
|
189
|
+
...args
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// src/tools/create-project.ts
|
|
199
|
+
import { z as z5 } from "zod";
|
|
200
|
+
function registerCreateProject(server) {
|
|
201
|
+
server.registerTool(
|
|
202
|
+
"create_project",
|
|
203
|
+
{
|
|
204
|
+
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
|
+
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(
|
|
209
|
+
"Short uppercase prefix for work item IDs (e.g. 'NIM'). Unique per org."
|
|
210
|
+
),
|
|
211
|
+
description: z5.string().optional().describe("Optional markdown description"),
|
|
212
|
+
isPrivate: z5.boolean().optional().describe(
|
|
213
|
+
"If true, only explicitly added members see it. Defaults to false."
|
|
214
|
+
),
|
|
215
|
+
workflowTemplate: z5.enum(["default", "simple", "kanban", "bug"]).optional().describe(
|
|
216
|
+
"Status workflow. Defaults to 'default' (Backlog/Todo/In Progress/Review/Done)."
|
|
217
|
+
)
|
|
218
|
+
},
|
|
219
|
+
annotations: {
|
|
220
|
+
readOnlyHint: false,
|
|
221
|
+
destructiveHint: false,
|
|
222
|
+
idempotentHint: false,
|
|
223
|
+
openWorldHint: false
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
async (args) => {
|
|
227
|
+
const client = getConvexClient();
|
|
228
|
+
const result = await client.action(api.mcp.createProject, {
|
|
229
|
+
token: getToken(),
|
|
230
|
+
...args
|
|
231
|
+
});
|
|
232
|
+
return {
|
|
233
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/tools/update-work-item.ts
|
|
240
|
+
import { z as z6 } from "zod";
|
|
241
|
+
function registerUpdateWorkItem(server) {
|
|
242
|
+
server.registerTool(
|
|
243
|
+
"update_work_item",
|
|
244
|
+
{
|
|
245
|
+
description: "Update an existing work item: title, description, status, priority, parent, or review flags. Any update also stamps the item as AI-touched.",
|
|
246
|
+
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(
|
|
254
|
+
"Flag the item for manual human review \u2014 set true when your enrichment was thin or uncertain."
|
|
255
|
+
),
|
|
256
|
+
reviewReason: z6.string().optional().describe(
|
|
257
|
+
"Short reason the item needs manual review (shown to the human)."
|
|
258
|
+
),
|
|
259
|
+
aiReviewRequested: z6.boolean().optional().describe(
|
|
260
|
+
"The human\u2192AI review flag. Set false to clear it after you have reviewed/enriched an item from list_ai_review_queue."
|
|
261
|
+
)
|
|
262
|
+
},
|
|
263
|
+
annotations: {
|
|
264
|
+
readOnlyHint: false,
|
|
265
|
+
destructiveHint: true,
|
|
266
|
+
idempotentHint: true,
|
|
267
|
+
openWorldHint: false
|
|
268
|
+
}
|
|
269
|
+
},
|
|
270
|
+
async (args) => {
|
|
271
|
+
const result = await getConvexClient().action(api.mcp.updateWorkItem, {
|
|
272
|
+
token: getToken(),
|
|
273
|
+
...args
|
|
274
|
+
});
|
|
275
|
+
return {
|
|
276
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/tools/create-document.ts
|
|
283
|
+
import { z as z7 } from "zod";
|
|
284
|
+
function registerCreateDocument(server) {
|
|
285
|
+
server.registerTool(
|
|
286
|
+
"create_document",
|
|
287
|
+
{
|
|
288
|
+
description: "Create a markdown project document. Use type 'prd' for project docs or 'fd' for feature/work-item documents.",
|
|
289
|
+
inputSchema: {
|
|
290
|
+
projectId: z7.string().describe("projectId from list_projects"),
|
|
291
|
+
title: z7.string().min(1),
|
|
292
|
+
type: z7.enum(["prd", "fd"]).describe(
|
|
293
|
+
"Document type. 'fd' is stored as the app's feature document type."
|
|
294
|
+
),
|
|
295
|
+
workItemId: z7.string().optional().describe("Required for fd documents; omitted for prd."),
|
|
296
|
+
content: z7.string().optional().describe("Optional initial markdown content.")
|
|
297
|
+
},
|
|
298
|
+
annotations: {
|
|
299
|
+
readOnlyHint: false,
|
|
300
|
+
destructiveHint: false,
|
|
301
|
+
idempotentHint: false,
|
|
302
|
+
openWorldHint: false
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
async (args) => {
|
|
306
|
+
const result = await getConvexClient().action(api.mcp.createDocument, {
|
|
307
|
+
token: getToken(),
|
|
308
|
+
...args
|
|
309
|
+
});
|
|
310
|
+
return {
|
|
311
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/tools/update-document.ts
|
|
318
|
+
import { z as z8 } from "zod";
|
|
319
|
+
function registerUpdateDocument(server) {
|
|
320
|
+
server.registerTool(
|
|
321
|
+
"update_document",
|
|
322
|
+
{
|
|
323
|
+
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
|
+
inputSchema: {
|
|
325
|
+
documentId: z8.string().describe("documentId from list_documents"),
|
|
326
|
+
content: z8.string().optional().describe(
|
|
327
|
+
"New markdown content. Replaces the document's existing content."
|
|
328
|
+
),
|
|
329
|
+
title: z8.string().min(1).optional().describe("New title (optional).")
|
|
330
|
+
},
|
|
331
|
+
annotations: {
|
|
332
|
+
readOnlyHint: false,
|
|
333
|
+
destructiveHint: true,
|
|
334
|
+
idempotentHint: true,
|
|
335
|
+
openWorldHint: false
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
async (args) => {
|
|
339
|
+
const result = await getConvexClient().action(api.mcp.updateDocument, {
|
|
340
|
+
token: getToken(),
|
|
341
|
+
...args
|
|
342
|
+
});
|
|
343
|
+
return {
|
|
344
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// src/tools/list-documents.ts
|
|
351
|
+
import { z as z9 } from "zod";
|
|
352
|
+
function registerListDocuments(server) {
|
|
353
|
+
server.registerTool(
|
|
354
|
+
"list_documents",
|
|
355
|
+
{
|
|
356
|
+
description: "List PRD/FD markdown documents for a project, optionally filtered to one work item.",
|
|
357
|
+
inputSchema: {
|
|
358
|
+
projectId: z9.string().describe("projectId from list_projects"),
|
|
359
|
+
workItemId: z9.string().optional().describe("Optional workItemId to list linked FD documents.")
|
|
360
|
+
},
|
|
361
|
+
annotations: {
|
|
362
|
+
readOnlyHint: true,
|
|
363
|
+
openWorldHint: false
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
async (args) => {
|
|
367
|
+
const result = await getConvexClient().action(api.mcp.listDocuments, {
|
|
368
|
+
token: getToken(),
|
|
369
|
+
...args
|
|
370
|
+
});
|
|
371
|
+
return {
|
|
372
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// src/tools/create-project-link.ts
|
|
379
|
+
import { z as z10 } from "zod";
|
|
380
|
+
var projectLinkCategory = z10.enum([
|
|
381
|
+
"api",
|
|
382
|
+
"dashboard",
|
|
383
|
+
"documentation",
|
|
384
|
+
"repository",
|
|
385
|
+
"service",
|
|
386
|
+
"other"
|
|
387
|
+
]);
|
|
388
|
+
function registerCreateProjectLink(server) {
|
|
389
|
+
server.registerTool(
|
|
390
|
+
"create_project_link",
|
|
391
|
+
{
|
|
392
|
+
description: "Save a link on a project for resources like APIs, dashboards, documentation, repositories, or services.",
|
|
393
|
+
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(),
|
|
398
|
+
category: projectLinkCategory.optional().describe("Defaults to other.")
|
|
399
|
+
},
|
|
400
|
+
annotations: {
|
|
401
|
+
readOnlyHint: false,
|
|
402
|
+
destructiveHint: false,
|
|
403
|
+
idempotentHint: false,
|
|
404
|
+
openWorldHint: false
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
async (args) => {
|
|
408
|
+
const result = await getConvexClient().action(api.mcp.createProjectLink, {
|
|
409
|
+
token: getToken(),
|
|
410
|
+
...args
|
|
411
|
+
});
|
|
412
|
+
return {
|
|
413
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// src/tools/list-project-links.ts
|
|
420
|
+
import { z as z11 } from "zod";
|
|
421
|
+
function registerListProjectLinks(server) {
|
|
422
|
+
server.registerTool(
|
|
423
|
+
"list_project_links",
|
|
424
|
+
{
|
|
425
|
+
description: "List saved links for a project, such as APIs, dashboards, docs, repositories, and services.",
|
|
426
|
+
inputSchema: {
|
|
427
|
+
projectId: z11.string().describe("projectId from list_projects")
|
|
428
|
+
},
|
|
429
|
+
annotations: {
|
|
430
|
+
readOnlyHint: true,
|
|
431
|
+
openWorldHint: false
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
async ({ projectId }) => {
|
|
435
|
+
const result = await getConvexClient().action(api.mcp.listProjectLinks, {
|
|
436
|
+
token: getToken(),
|
|
437
|
+
projectId
|
|
438
|
+
});
|
|
439
|
+
return {
|
|
440
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/tools/add-work-item-image.ts
|
|
447
|
+
import { z as z12 } from "zod";
|
|
448
|
+
function registerAddWorkItemImage(server) {
|
|
449
|
+
server.registerTool(
|
|
450
|
+
"add_work_item_image",
|
|
451
|
+
{
|
|
452
|
+
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
|
+
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")
|
|
458
|
+
},
|
|
459
|
+
annotations: {
|
|
460
|
+
readOnlyHint: false,
|
|
461
|
+
destructiveHint: false,
|
|
462
|
+
idempotentHint: false,
|
|
463
|
+
openWorldHint: false
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
async (args) => {
|
|
467
|
+
const result = await getConvexClient().action(api.mcp.addWorkItemImage, {
|
|
468
|
+
token: getToken(),
|
|
469
|
+
...args
|
|
470
|
+
});
|
|
471
|
+
return {
|
|
472
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// src/tools/list-work-item-images.ts
|
|
479
|
+
import { z as z13 } from "zod";
|
|
480
|
+
function registerListWorkItemImages(server) {
|
|
481
|
+
server.registerTool(
|
|
482
|
+
"list_work_item_images",
|
|
483
|
+
{
|
|
484
|
+
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
|
+
inputSchema: {
|
|
486
|
+
workItemId: z13.string().describe("workItemId from list_work_items")
|
|
487
|
+
},
|
|
488
|
+
annotations: {
|
|
489
|
+
readOnlyHint: true,
|
|
490
|
+
openWorldHint: false
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
async ({ workItemId }) => {
|
|
494
|
+
const result = await getConvexClient().action(
|
|
495
|
+
api.mcp.listWorkItemImages,
|
|
496
|
+
{
|
|
497
|
+
token: getToken(),
|
|
498
|
+
workItemId
|
|
499
|
+
}
|
|
500
|
+
);
|
|
501
|
+
return {
|
|
502
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
// src/tools/add-work-item-comment.ts
|
|
509
|
+
import { z as z14 } from "zod";
|
|
510
|
+
function registerAddWorkItemComment(server) {
|
|
511
|
+
server.registerTool(
|
|
512
|
+
"add_work_item_comment",
|
|
513
|
+
{
|
|
514
|
+
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
|
+
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')
|
|
519
|
+
},
|
|
520
|
+
annotations: {
|
|
521
|
+
readOnlyHint: false,
|
|
522
|
+
destructiveHint: false,
|
|
523
|
+
idempotentHint: false,
|
|
524
|
+
openWorldHint: false
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
async (args) => {
|
|
528
|
+
const result = await getConvexClient().action(
|
|
529
|
+
api.mcp.addWorkItemComment,
|
|
530
|
+
{
|
|
531
|
+
token: getToken(),
|
|
532
|
+
...args
|
|
533
|
+
}
|
|
534
|
+
);
|
|
535
|
+
return {
|
|
536
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// src/tools/list-work-item-comments.ts
|
|
543
|
+
import { z as z15 } from "zod";
|
|
544
|
+
function registerListWorkItemComments(server) {
|
|
545
|
+
server.registerTool(
|
|
546
|
+
"list_work_item_comments",
|
|
547
|
+
{
|
|
548
|
+
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
|
+
inputSchema: {
|
|
550
|
+
workItemId: z15.string().describe("workItemId from list_work_items")
|
|
551
|
+
},
|
|
552
|
+
annotations: {
|
|
553
|
+
readOnlyHint: true,
|
|
554
|
+
openWorldHint: false
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
async (args) => {
|
|
558
|
+
const result = await getConvexClient().action(
|
|
559
|
+
api.mcp.listWorkItemComments,
|
|
560
|
+
{
|
|
561
|
+
token: getToken(),
|
|
562
|
+
...args
|
|
563
|
+
}
|
|
564
|
+
);
|
|
565
|
+
return {
|
|
566
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// src/tools/list-validations.ts
|
|
573
|
+
import { z as z16 } from "zod";
|
|
574
|
+
function registerListValidations(server) {
|
|
575
|
+
server.registerTool(
|
|
576
|
+
"list_validations",
|
|
577
|
+
{
|
|
578
|
+
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
|
+
inputSchema: {
|
|
580
|
+
projectId: z16.string().describe("projectId from list_projects")
|
|
581
|
+
},
|
|
582
|
+
annotations: {
|
|
583
|
+
readOnlyHint: true,
|
|
584
|
+
openWorldHint: false
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
async ({ projectId }) => {
|
|
588
|
+
const client = getConvexClient();
|
|
589
|
+
const graph = await client.action(api.mcp.listValidations, {
|
|
590
|
+
token: getToken(),
|
|
591
|
+
projectId
|
|
592
|
+
});
|
|
593
|
+
return {
|
|
594
|
+
content: [{ type: "text", text: JSON.stringify(graph, null, 2) }]
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/tools/record-validation.ts
|
|
601
|
+
import { z as z17 } from "zod";
|
|
602
|
+
function registerRecordValidation(server) {
|
|
603
|
+
server.registerTool(
|
|
604
|
+
"record_validation",
|
|
605
|
+
{
|
|
606
|
+
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
|
+
inputSchema: {
|
|
608
|
+
validationTaskId: z17.string().describe("validationTaskId from list_validations"),
|
|
609
|
+
result: z17.enum(["pass", "fail"]).describe("Outcome of running the test")
|
|
610
|
+
},
|
|
611
|
+
annotations: {
|
|
612
|
+
readOnlyHint: false,
|
|
613
|
+
destructiveHint: true,
|
|
614
|
+
idempotentHint: true,
|
|
615
|
+
openWorldHint: false
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
async ({ validationTaskId, result }) => {
|
|
619
|
+
const client = getConvexClient();
|
|
620
|
+
const res = await client.action(api.mcp.recordValidation, {
|
|
621
|
+
token: getToken(),
|
|
622
|
+
validationTaskId,
|
|
623
|
+
result
|
|
624
|
+
});
|
|
625
|
+
return {
|
|
626
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
627
|
+
};
|
|
628
|
+
}
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// src/tools/mark-system-changed.ts
|
|
633
|
+
import { z as z18 } from "zod";
|
|
634
|
+
function registerMarkSystemChanged(server) {
|
|
635
|
+
server.registerTool(
|
|
636
|
+
"mark_system_changed",
|
|
637
|
+
{
|
|
638
|
+
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
|
+
inputSchema: {
|
|
640
|
+
systemId: z18.string().describe("systemId from list_validations")
|
|
641
|
+
},
|
|
642
|
+
annotations: {
|
|
643
|
+
readOnlyHint: false,
|
|
644
|
+
destructiveHint: false,
|
|
645
|
+
idempotentHint: false,
|
|
646
|
+
openWorldHint: false
|
|
647
|
+
}
|
|
648
|
+
},
|
|
649
|
+
async ({ systemId }) => {
|
|
650
|
+
const client = getConvexClient();
|
|
651
|
+
const res = await client.action(api.mcp.markSystemChanged, {
|
|
652
|
+
token: getToken(),
|
|
653
|
+
systemId
|
|
654
|
+
});
|
|
655
|
+
return {
|
|
656
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/tools/create-system.ts
|
|
663
|
+
import { z as z19 } from "zod";
|
|
664
|
+
function registerCreateSystem(server) {
|
|
665
|
+
server.registerTool(
|
|
666
|
+
"create_system",
|
|
667
|
+
{
|
|
668
|
+
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
|
+
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")
|
|
673
|
+
},
|
|
674
|
+
annotations: {
|
|
675
|
+
readOnlyHint: false,
|
|
676
|
+
destructiveHint: false,
|
|
677
|
+
idempotentHint: false,
|
|
678
|
+
openWorldHint: false
|
|
679
|
+
}
|
|
680
|
+
},
|
|
681
|
+
async (args) => {
|
|
682
|
+
const client = getConvexClient();
|
|
683
|
+
const res = await client.action(api.mcp.createSystem, {
|
|
684
|
+
token: getToken(),
|
|
685
|
+
...args
|
|
686
|
+
});
|
|
687
|
+
return {
|
|
688
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/tools/create-validation-task.ts
|
|
695
|
+
import { z as z20 } from "zod";
|
|
696
|
+
function registerCreateValidationTask(server) {
|
|
697
|
+
server.registerTool(
|
|
698
|
+
"create_validation_task",
|
|
699
|
+
{
|
|
700
|
+
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
|
+
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(
|
|
706
|
+
"'auto' if runnable by a test runner, 'manual' if a human checks it. Defaults to manual."
|
|
707
|
+
),
|
|
708
|
+
workItemId: z20.string().optional().describe(
|
|
709
|
+
"Optional workItemId to link this test to a feature for traceability"
|
|
710
|
+
)
|
|
711
|
+
},
|
|
712
|
+
annotations: {
|
|
713
|
+
readOnlyHint: false,
|
|
714
|
+
destructiveHint: false,
|
|
715
|
+
idempotentHint: false,
|
|
716
|
+
openWorldHint: false
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
async (args) => {
|
|
720
|
+
const client = getConvexClient();
|
|
721
|
+
const res = await client.action(api.mcp.createValidationTask, {
|
|
722
|
+
token: getToken(),
|
|
723
|
+
...args
|
|
724
|
+
});
|
|
725
|
+
return {
|
|
726
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// src/tools/add-system-to-task.ts
|
|
733
|
+
import { z as z21 } from "zod";
|
|
734
|
+
function registerAddSystemToTask(server) {
|
|
735
|
+
server.registerTool(
|
|
736
|
+
"add_system_to_task",
|
|
737
|
+
{
|
|
738
|
+
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
|
+
inputSchema: {
|
|
740
|
+
validationTaskId: z21.string().describe("validationTaskId from list_validations"),
|
|
741
|
+
systemId: z21.string().describe("systemId from list_validations")
|
|
742
|
+
},
|
|
743
|
+
annotations: {
|
|
744
|
+
readOnlyHint: false,
|
|
745
|
+
destructiveHint: false,
|
|
746
|
+
idempotentHint: true,
|
|
747
|
+
openWorldHint: false
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
async ({ validationTaskId, systemId }) => {
|
|
751
|
+
const client = getConvexClient();
|
|
752
|
+
const res = await client.action(api.mcp.addSystemToTask, {
|
|
753
|
+
token: getToken(),
|
|
754
|
+
validationTaskId,
|
|
755
|
+
systemId
|
|
756
|
+
});
|
|
757
|
+
return {
|
|
758
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
);
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/tools/add-validation-dependency.ts
|
|
765
|
+
import { z as z22 } from "zod";
|
|
766
|
+
function registerAddValidationDependency(server) {
|
|
767
|
+
server.registerTool(
|
|
768
|
+
"add_validation_dependency",
|
|
769
|
+
{
|
|
770
|
+
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
|
+
inputSchema: {
|
|
772
|
+
validationTaskId: z22.string().describe("The dependent (downstream) task"),
|
|
773
|
+
dependsOnTaskId: z22.string().describe("The dependency (upstream) task it relies on")
|
|
774
|
+
},
|
|
775
|
+
annotations: {
|
|
776
|
+
readOnlyHint: false,
|
|
777
|
+
destructiveHint: false,
|
|
778
|
+
idempotentHint: true,
|
|
779
|
+
openWorldHint: false
|
|
780
|
+
}
|
|
781
|
+
},
|
|
782
|
+
async ({ validationTaskId, dependsOnTaskId }) => {
|
|
783
|
+
const client = getConvexClient();
|
|
784
|
+
const res = await client.action(api.mcp.addValidationDependency, {
|
|
785
|
+
token: getToken(),
|
|
786
|
+
validationTaskId,
|
|
787
|
+
dependsOnTaskId
|
|
788
|
+
});
|
|
789
|
+
return {
|
|
790
|
+
content: [{ type: "text", text: JSON.stringify(res, null, 2) }]
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// src/tools/list-labels.ts
|
|
797
|
+
import { z as z23 } from "zod";
|
|
798
|
+
function registerListLabels(server) {
|
|
799
|
+
server.registerTool(
|
|
800
|
+
"list_labels",
|
|
801
|
+
{
|
|
802
|
+
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
|
+
inputSchema: {
|
|
804
|
+
projectId: z23.string().describe("projectId from list_projects")
|
|
805
|
+
},
|
|
806
|
+
annotations: {
|
|
807
|
+
readOnlyHint: true,
|
|
808
|
+
openWorldHint: false
|
|
809
|
+
}
|
|
810
|
+
},
|
|
811
|
+
async (args) => {
|
|
812
|
+
const result = await getConvexClient().action(api.mcp.listLabels, {
|
|
813
|
+
token: getToken(),
|
|
814
|
+
...args
|
|
815
|
+
});
|
|
816
|
+
return {
|
|
817
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/tools/create-label.ts
|
|
824
|
+
import { z as z24 } from "zod";
|
|
825
|
+
function registerCreateLabel(server) {
|
|
826
|
+
server.registerTool(
|
|
827
|
+
"create_label",
|
|
828
|
+
{
|
|
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.",
|
|
830
|
+
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.")
|
|
834
|
+
},
|
|
835
|
+
annotations: {
|
|
836
|
+
readOnlyHint: false,
|
|
837
|
+
destructiveHint: false,
|
|
838
|
+
idempotentHint: true,
|
|
839
|
+
openWorldHint: false
|
|
840
|
+
}
|
|
841
|
+
},
|
|
842
|
+
async (args) => {
|
|
843
|
+
const result = await getConvexClient().action(api.mcp.createLabel, {
|
|
844
|
+
token: getToken(),
|
|
845
|
+
...args
|
|
846
|
+
});
|
|
847
|
+
return {
|
|
848
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
// src/tools/add-label-to-work-item.ts
|
|
855
|
+
import { z as z25 } from "zod";
|
|
856
|
+
function registerAddLabelToWorkItem(server) {
|
|
857
|
+
server.registerTool(
|
|
858
|
+
"add_label_to_work_item",
|
|
859
|
+
{
|
|
860
|
+
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
|
+
inputSchema: {
|
|
862
|
+
workItemId: z25.string().describe("workItemId from list_work_items"),
|
|
863
|
+
labelId: z25.string().describe("labelId from list_labels or create_label")
|
|
864
|
+
},
|
|
865
|
+
annotations: {
|
|
866
|
+
readOnlyHint: false,
|
|
867
|
+
destructiveHint: false,
|
|
868
|
+
idempotentHint: true,
|
|
869
|
+
openWorldHint: false
|
|
870
|
+
}
|
|
871
|
+
},
|
|
872
|
+
async (args) => {
|
|
873
|
+
const result = await getConvexClient().action(api.mcp.setWorkItemLabel, {
|
|
874
|
+
token: getToken(),
|
|
875
|
+
...args,
|
|
876
|
+
attached: true
|
|
877
|
+
});
|
|
878
|
+
return {
|
|
879
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
// src/tools/remove-label-from-work-item.ts
|
|
886
|
+
import { z as z26 } from "zod";
|
|
887
|
+
function registerRemoveLabelFromWorkItem(server) {
|
|
888
|
+
server.registerTool(
|
|
889
|
+
"remove_label_from_work_item",
|
|
890
|
+
{
|
|
891
|
+
description: "Remove a label from a work item (idempotent \u2014 a no-op if it wasn't attached).",
|
|
892
|
+
inputSchema: {
|
|
893
|
+
workItemId: z26.string().describe("workItemId from list_work_items"),
|
|
894
|
+
labelId: z26.string().describe("labelId from list_labels")
|
|
895
|
+
},
|
|
896
|
+
annotations: {
|
|
897
|
+
readOnlyHint: false,
|
|
898
|
+
destructiveHint: true,
|
|
899
|
+
idempotentHint: true,
|
|
900
|
+
openWorldHint: false
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
async (args) => {
|
|
904
|
+
const result = await getConvexClient().action(api.mcp.setWorkItemLabel, {
|
|
905
|
+
token: getToken(),
|
|
906
|
+
...args,
|
|
907
|
+
attached: false
|
|
908
|
+
});
|
|
909
|
+
return {
|
|
910
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
911
|
+
};
|
|
912
|
+
}
|
|
913
|
+
);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// src/tools/list-milestones.ts
|
|
917
|
+
import { z as z27 } from "zod";
|
|
918
|
+
function registerListMilestones(server) {
|
|
919
|
+
server.registerTool(
|
|
920
|
+
"list_milestones",
|
|
921
|
+
{
|
|
922
|
+
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
|
+
inputSchema: {
|
|
924
|
+
projectId: z27.string().describe("projectId from list_projects")
|
|
925
|
+
},
|
|
926
|
+
annotations: {
|
|
927
|
+
readOnlyHint: true,
|
|
928
|
+
openWorldHint: false
|
|
929
|
+
}
|
|
930
|
+
},
|
|
931
|
+
async (args) => {
|
|
932
|
+
const result = await getConvexClient().action(api.mcp.listMilestones, {
|
|
933
|
+
token: getToken(),
|
|
934
|
+
...args
|
|
935
|
+
});
|
|
936
|
+
return {
|
|
937
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
);
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
// src/tools/create-milestone.ts
|
|
944
|
+
import { z as z28 } from "zod";
|
|
945
|
+
function registerCreateMilestone(server) {
|
|
946
|
+
server.registerTool(
|
|
947
|
+
"create_milestone",
|
|
948
|
+
{
|
|
949
|
+
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
|
+
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")
|
|
955
|
+
},
|
|
956
|
+
annotations: {
|
|
957
|
+
readOnlyHint: false,
|
|
958
|
+
destructiveHint: false,
|
|
959
|
+
idempotentHint: false,
|
|
960
|
+
openWorldHint: false
|
|
961
|
+
}
|
|
962
|
+
},
|
|
963
|
+
async (args) => {
|
|
964
|
+
const result = await getConvexClient().action(api.mcp.createMilestone, {
|
|
965
|
+
token: getToken(),
|
|
966
|
+
...args
|
|
967
|
+
});
|
|
968
|
+
return {
|
|
969
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
970
|
+
};
|
|
971
|
+
}
|
|
972
|
+
);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
// src/tools/add-item-to-milestone.ts
|
|
976
|
+
import { z as z29 } from "zod";
|
|
977
|
+
function registerAddItemToMilestone(server) {
|
|
978
|
+
server.registerTool(
|
|
979
|
+
"add_item_to_milestone",
|
|
980
|
+
{
|
|
981
|
+
description: "Associate a work item with a milestone (idempotent). Both must be in the same project.",
|
|
982
|
+
inputSchema: {
|
|
983
|
+
milestoneId: z29.string().describe("milestoneId from list_milestones or create_milestone"),
|
|
984
|
+
workItemId: z29.string().describe("workItemId from list_work_items")
|
|
985
|
+
},
|
|
986
|
+
annotations: {
|
|
987
|
+
readOnlyHint: false,
|
|
988
|
+
destructiveHint: false,
|
|
989
|
+
idempotentHint: true,
|
|
990
|
+
openWorldHint: false
|
|
991
|
+
}
|
|
992
|
+
},
|
|
993
|
+
async (args) => {
|
|
994
|
+
const result = await getConvexClient().action(
|
|
995
|
+
api.mcp.setWorkItemMilestone,
|
|
996
|
+
{
|
|
997
|
+
token: getToken(),
|
|
998
|
+
...args,
|
|
999
|
+
attached: true
|
|
1000
|
+
}
|
|
1001
|
+
);
|
|
1002
|
+
return {
|
|
1003
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
// src/tools/remove-item-from-milestone.ts
|
|
1010
|
+
import { z as z30 } from "zod";
|
|
1011
|
+
function registerRemoveItemFromMilestone(server) {
|
|
1012
|
+
server.registerTool(
|
|
1013
|
+
"remove_item_from_milestone",
|
|
1014
|
+
{
|
|
1015
|
+
description: "Remove a work item's association with a milestone (idempotent \u2014 a no-op if it wasn't associated).",
|
|
1016
|
+
inputSchema: {
|
|
1017
|
+
milestoneId: z30.string().describe("milestoneId from list_milestones"),
|
|
1018
|
+
workItemId: z30.string().describe("workItemId from list_work_items")
|
|
1019
|
+
},
|
|
1020
|
+
annotations: {
|
|
1021
|
+
readOnlyHint: false,
|
|
1022
|
+
destructiveHint: true,
|
|
1023
|
+
idempotentHint: true,
|
|
1024
|
+
openWorldHint: false
|
|
1025
|
+
}
|
|
1026
|
+
},
|
|
1027
|
+
async (args) => {
|
|
1028
|
+
const result = await getConvexClient().action(
|
|
1029
|
+
api.mcp.setWorkItemMilestone,
|
|
1030
|
+
{
|
|
1031
|
+
token: getToken(),
|
|
1032
|
+
...args,
|
|
1033
|
+
attached: false
|
|
1034
|
+
}
|
|
1035
|
+
);
|
|
1036
|
+
return {
|
|
1037
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/server.ts
|
|
1044
|
+
function createServer() {
|
|
1045
|
+
const server = new McpServer({
|
|
1046
|
+
name: "nimara",
|
|
1047
|
+
version: "0.1.0"
|
|
1048
|
+
});
|
|
1049
|
+
registerListOrgs(server);
|
|
1050
|
+
registerListProjects(server);
|
|
1051
|
+
registerListWorkItems(server);
|
|
1052
|
+
registerListAiReviewQueue(server);
|
|
1053
|
+
registerCreateProject(server);
|
|
1054
|
+
registerCreateWorkItem(server);
|
|
1055
|
+
registerUpdateWorkItem(server);
|
|
1056
|
+
registerCreateDocument(server);
|
|
1057
|
+
registerUpdateDocument(server);
|
|
1058
|
+
registerListDocuments(server);
|
|
1059
|
+
registerCreateProjectLink(server);
|
|
1060
|
+
registerListProjectLinks(server);
|
|
1061
|
+
registerAddWorkItemImage(server);
|
|
1062
|
+
registerListWorkItemImages(server);
|
|
1063
|
+
registerAddWorkItemComment(server);
|
|
1064
|
+
registerListWorkItemComments(server);
|
|
1065
|
+
registerListValidations(server);
|
|
1066
|
+
registerRecordValidation(server);
|
|
1067
|
+
registerMarkSystemChanged(server);
|
|
1068
|
+
registerCreateSystem(server);
|
|
1069
|
+
registerCreateValidationTask(server);
|
|
1070
|
+
registerAddSystemToTask(server);
|
|
1071
|
+
registerAddValidationDependency(server);
|
|
1072
|
+
registerListLabels(server);
|
|
1073
|
+
registerCreateLabel(server);
|
|
1074
|
+
registerAddLabelToWorkItem(server);
|
|
1075
|
+
registerRemoveLabelFromWorkItem(server);
|
|
1076
|
+
registerListMilestones(server);
|
|
1077
|
+
registerCreateMilestone(server);
|
|
1078
|
+
registerAddItemToMilestone(server);
|
|
1079
|
+
registerRemoveItemFromMilestone(server);
|
|
1080
|
+
return server;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
// src/index.ts
|
|
1084
|
+
for (const m of ["log", "info", "warn", "debug"]) {
|
|
1085
|
+
console[m] = (...args) => console.error(...args);
|
|
1086
|
+
}
|
|
1087
|
+
async function main() {
|
|
1088
|
+
const server = createServer();
|
|
1089
|
+
const transport = new StdioServerTransport();
|
|
1090
|
+
await server.connect(transport);
|
|
1091
|
+
}
|
|
1092
|
+
main().catch((err) => {
|
|
1093
|
+
console.error("nimara-mcp: fatal error", err);
|
|
1094
|
+
process.exit(1);
|
|
1095
|
+
});
|
|
1096
|
+
//# sourceMappingURL=index.js.map
|