@nimara-app/mcp 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
9
  // package.json
10
10
  var package_default = {
11
11
  name: "@nimara-app/mcp",
12
- version: "0.7.0",
12
+ version: "0.8.0",
13
13
  private: false,
14
14
  mcpName: "io.github.langskip-studios/nimara",
15
15
  description: "Nimara MCP server \u2014 AI tool integration for Claude, Codex, and others",
@@ -20,7 +20,8 @@ var package_default = {
20
20
  "nimara-mcp": "dist/index.js"
21
21
  },
22
22
  files: [
23
- "dist",
23
+ "dist/index.js",
24
+ "dist/index.js.map",
24
25
  "README.md"
25
26
  ],
26
27
  keywords: [
@@ -45,7 +46,8 @@ var package_default = {
45
46
  dev: "node build.mjs",
46
47
  start: "node dist/index.js",
47
48
  typecheck: "tsc --noEmit",
48
- prepublishOnly: "npm run typecheck && npm run build"
49
+ prepublishOnly: "npm run typecheck && npm run build",
50
+ test: "vitest run"
49
51
  },
50
52
  dependencies: {
51
53
  "@modelcontextprotocol/sdk": "^1.0.0",
@@ -53,10 +55,15 @@ var package_default = {
53
55
  zod: "^3.23.0"
54
56
  },
55
57
  devDependencies: {
58
+ "@clerk/hono": "^0.1.79",
59
+ "@clerk/mcp-tools": "^0.6.0",
56
60
  "@nimara/convex": "workspace:*",
57
61
  "@types/node": "^20.0.0",
58
62
  esbuild: "^0.23.0",
59
- typescript: "^5.0.0"
63
+ hono: "^4.13.8",
64
+ typescript: "^5.0.0",
65
+ vitest: "^2.1.9",
66
+ wrangler: "4.132.0"
60
67
  },
61
68
  engines: {
62
69
  node: ">=20"
@@ -78,9 +85,29 @@ People are userIds from list_project_members: mention one in a comment as [@Name
78
85
 
79
86
  // src/convex.ts
80
87
  import { ConvexHttpClient } from "convex/browser";
88
+ import { ConvexError } from "convex/values";
81
89
  var cachedClient = null;
90
+ function describeConvexError(err) {
91
+ if (err instanceof ConvexError) {
92
+ const data = err.data;
93
+ if (typeof data === "string" && data.trim()) return data;
94
+ if (typeof data === "object" && data !== null) {
95
+ const { code, message } = data;
96
+ if ((code === "user" || code === "forbidden") && typeof message === "string" && message.trim()) {
97
+ return message;
98
+ }
99
+ if (code === "unauthenticated") return "This token is not valid. Generate a new one in Nimara \u2192 Settings \u2192 Personal Access Tokens.";
100
+ }
101
+ }
102
+ const raw = err instanceof Error ? err.message : String(err);
103
+ const requestId = /\[Request ID: ([^\]]+)\]/.exec(raw)?.[1];
104
+ if (requestId || err instanceof ConvexError) {
105
+ return requestId ? `Nimara could not complete that call (request ${requestId}). Check the arguments and try again.` : "Nimara could not complete that call. Check the arguments and try again.";
106
+ }
107
+ return `Nimara could not reach its backend: ${raw || "unknown transport error"}. Nothing is wrong with the arguments; check the server's NIMARA_CONVEX_URL and try again.`;
108
+ }
82
109
  function getConvexClient() {
83
- if (cachedClient) return cachedClient;
110
+ if (cachedClient) return wrap(cachedClient);
84
111
  const url = process.env.NIMARA_CONVEX_URL;
85
112
  if (!url) {
86
113
  throw new Error(
@@ -88,9 +115,22 @@ function getConvexClient() {
88
115
  );
89
116
  }
90
117
  cachedClient = new ConvexHttpClient(url);
91
- return cachedClient;
118
+ return wrap(cachedClient);
92
119
  }
93
- function getToken() {
120
+ function wrap(client) {
121
+ return {
122
+ action: async (ref, args) => {
123
+ try {
124
+ return await client.action(ref, args);
125
+ } catch (err) {
126
+ throw new Error(describeConvexError(err));
127
+ }
128
+ }
129
+ };
130
+ }
131
+ function getToken(extra) {
132
+ const fromRequest = extra?.authInfo?.token;
133
+ if (fromRequest) return fromRequest;
94
134
  const token = process.env.NIMARA_TOKEN;
95
135
  if (!token) {
96
136
  throw new Error(
@@ -122,13 +162,14 @@ function registerListOrgs(server) {
122
162
  description: "List all organizations (and the personal workspace) the authenticated user belongs to. Use this first to discover org IDs needed by other tools.",
123
163
  inputSchema: {},
124
164
  annotations: {
165
+ title: "List organizations",
125
166
  readOnlyHint: true,
126
167
  openWorldHint: false
127
168
  }
128
169
  },
129
- async () => {
170
+ async (_args, extra) => {
130
171
  const client = getConvexClient();
131
- const orgs = await client.action(api.mcp.listOrgs, { token: getToken() });
172
+ const orgs = await client.action(api.mcp.listOrgs, { token: getToken(extra) });
132
173
  return {
133
174
  content: [{ type: "text", text: JSON.stringify(orgs, null, 2) }]
134
175
  };
@@ -145,14 +186,15 @@ function registerListProjects(server) {
145
186
  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.",
146
187
  inputSchema: { orgId: z.string().describe("orgId from list_orgs") },
147
188
  annotations: {
189
+ title: "List projects",
148
190
  readOnlyHint: true,
149
191
  openWorldHint: false
150
192
  }
151
193
  },
152
- async ({ orgId }) => {
194
+ async ({ orgId }, extra) => {
153
195
  const client = getConvexClient();
154
196
  const projects = await client.action(api.mcp.listProjects, {
155
- token: getToken(),
197
+ token: getToken(extra),
156
198
  orgId
157
199
  });
158
200
  return {
@@ -195,6 +237,7 @@ function registerListWorkItems(server) {
195
237
  )
196
238
  },
197
239
  annotations: {
240
+ title: "List work items",
198
241
  readOnlyHint: true,
199
242
  openWorldHint: false
200
243
  }
@@ -208,9 +251,9 @@ function registerListWorkItems(server) {
208
251
  type,
209
252
  parentId,
210
253
  needsReview
211
- }) => {
254
+ }, extra) => {
212
255
  const page = await getConvexClient().action(api.mcp.listWorkItems, {
213
- token: getToken(),
256
+ token: getToken(extra),
214
257
  projectId,
215
258
  limit,
216
259
  cursor,
@@ -251,13 +294,14 @@ function registerGetWorkItems(server) {
251
294
  )
252
295
  },
253
296
  annotations: {
297
+ title: "Get work items by id",
254
298
  readOnlyHint: true,
255
299
  openWorldHint: false
256
300
  }
257
301
  },
258
- async ({ displayIds, workItemIds, projectId }) => {
302
+ async ({ displayIds, workItemIds, projectId }, extra) => {
259
303
  const result = await getConvexClient().action(api.mcp.getWorkItems, {
260
- token: getToken(),
304
+ token: getToken(extra),
261
305
  displayIds,
262
306
  workItemIds,
263
307
  projectId
@@ -287,11 +331,14 @@ function registerSearchWorkItems(server) {
287
331
  ),
288
332
  limit: z4.number().int().min(1).max(25).optional().describe("Maximum results (default and maximum 25).")
289
333
  },
290
- annotations: { readOnlyHint: true }
334
+ annotations: {
335
+ title: "Search work items",
336
+ readOnlyHint: true
337
+ }
291
338
  },
292
- async ({ projectId, q, limit }) => {
339
+ async ({ projectId, q, limit }, extra) => {
293
340
  const result = await getConvexClient().action(api.mcp.searchWorkItems, {
294
- token: getToken(),
341
+ token: getToken(extra),
295
342
  projectId,
296
343
  q,
297
344
  limit
@@ -318,13 +365,14 @@ function registerListAiReviewQueue(server) {
318
365
  )
319
366
  },
320
367
  annotations: {
368
+ title: "List the AI review queue",
321
369
  readOnlyHint: true,
322
370
  openWorldHint: false
323
371
  }
324
372
  },
325
- async ({ projectId, limit, cursor }) => {
373
+ async ({ projectId, limit, cursor }, extra) => {
326
374
  const page = await getConvexClient().action(api.mcp.listAiReviewQueue, {
327
- token: getToken(),
375
+ token: getToken(extra),
328
376
  projectId,
329
377
  limit,
330
378
  cursor
@@ -347,13 +395,14 @@ function registerGetActiveBlock(server) {
347
395
  projectId: z6.string().describe("projectId from list_projects")
348
396
  },
349
397
  annotations: {
398
+ title: "Get the active block",
350
399
  readOnlyHint: true,
351
400
  openWorldHint: false
352
401
  }
353
402
  },
354
- async ({ projectId }) => {
403
+ async ({ projectId }, extra) => {
355
404
  const block = await getConvexClient().action(api.mcp.getActiveBlock, {
356
- token: getToken(),
405
+ token: getToken(extra),
357
406
  projectId
358
407
  });
359
408
  return {
@@ -400,16 +449,17 @@ function registerCreateWorkItem(server) {
400
449
  priority: z7.enum(["p0", "p1", "p2", "p3"]).optional().describe("p0 highest, p3 lowest. Defaults to p2.")
401
450
  },
402
451
  annotations: {
452
+ title: "Create a work item",
403
453
  readOnlyHint: false,
404
454
  destructiveHint: false,
405
455
  idempotentHint: false,
406
456
  openWorldHint: false
407
457
  }
408
458
  },
409
- async (args) => {
459
+ async (args, extra) => {
410
460
  const client = getConvexClient();
411
461
  const result = await client.action(api.mcp.createWorkItem, {
412
- token: getToken(),
462
+ token: getToken(extra),
413
463
  ...args
414
464
  });
415
465
  return {
@@ -432,7 +482,9 @@ function registerCreateProject(server) {
432
482
  prefix: z8.string().min(1).max(8).describe(
433
483
  "Short uppercase prefix for work item IDs (e.g. 'NIM'). Unique per org."
434
484
  ),
435
- description: z8.string().optional().describe("Optional markdown description"),
485
+ description: z8.string().max(280).optional().describe(
486
+ "Optional, and usually best left out. At most one or two plain sentences (280 characters) saying what the project is for \u2014 it shows behind an info button on the project page, so headings, stack notes, decisions and lineage do not belong here. Put that kind of context in a document (create_document) or the project's README instead."
487
+ ),
436
488
  isPrivate: z8.boolean().optional().describe(
437
489
  "If true, only explicitly added members see it. Defaults to false."
438
490
  ),
@@ -441,16 +493,17 @@ function registerCreateProject(server) {
441
493
  )
442
494
  },
443
495
  annotations: {
496
+ title: "Create a project",
444
497
  readOnlyHint: false,
445
498
  destructiveHint: false,
446
499
  idempotentHint: false,
447
500
  openWorldHint: false
448
501
  }
449
502
  },
450
- async (args) => {
503
+ async (args, extra) => {
451
504
  const client = getConvexClient();
452
505
  const result = await client.action(api.mcp.createProject, {
453
- token: getToken(),
506
+ token: getToken(extra),
454
507
  ...args
455
508
  });
456
509
  return {
@@ -526,15 +579,16 @@ function registerUpdateWorkItem(server) {
526
579
  model: z9.string().optional().describe("With agentWorking: the model you are running as, e.g. claude-opus-5, so the app can say which agent is on this. Your harness is reported automatically.")
527
580
  },
528
581
  annotations: {
582
+ title: "Update a work item",
529
583
  readOnlyHint: false,
530
584
  destructiveHint: true,
531
585
  idempotentHint: true,
532
586
  openWorldHint: false
533
587
  }
534
588
  },
535
- async ({ model, ...args }) => {
589
+ async ({ model, ...args }, extra) => {
536
590
  const result = await getConvexClient().action(api.mcp.updateWorkItem, {
537
- token: getToken(),
591
+ token: getToken(extra),
538
592
  ...args,
539
593
  agent: agentArg(model)
540
594
  });
@@ -563,6 +617,7 @@ function registerMoveWorkItem(server) {
563
617
  )
564
618
  },
565
619
  annotations: {
620
+ title: "Move a work item to another project",
566
621
  readOnlyHint: false,
567
622
  // Renumbering display IDs and deleting label/milestone links are
568
623
  // overwrites, not additions — this belongs with the tools a client
@@ -574,10 +629,10 @@ function registerMoveWorkItem(server) {
574
629
  openWorldHint: false
575
630
  }
576
631
  },
577
- async ({ workItemId, targetProjectId }) => {
632
+ async ({ workItemId, targetProjectId }, extra) => {
578
633
  const result = await getConvexClient().action(
579
634
  api.mcp.moveWorkItemToProject,
580
- { token: getToken(), workItemId, targetProjectId }
635
+ { token: getToken(extra), workItemId, targetProjectId }
581
636
  );
582
637
  return {
583
638
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
@@ -592,26 +647,27 @@ function registerCreateDocument(server) {
592
647
  server.registerTool(
593
648
  "create_document",
594
649
  {
595
- description: "Create a markdown project document. Use type 'prd' for project docs or 'fd' for feature/work-item documents.",
650
+ description: "Create a markdown document in a project: a spec, a design, a report, research. Pass workItemId to link it to the item it is about. Leave type alone unless the document is THE project brief.",
596
651
  inputSchema: {
597
652
  projectId: z11.string().describe("projectId from list_projects"),
598
653
  title: z11.string().min(1),
599
- type: z11.enum(["prd", "fd"]).describe(
600
- "Document type. 'fd' is stored as the app's feature document type."
654
+ type: z11.enum(["note", "prd", "fd"]).optional().describe(
655
+ "Defaults to 'note', which is right for almost everything. 'prd' is reserved for the project's own product requirements brief, one per project, never linked to a work item. 'fd' is an older spelling of 'note' and still accepted."
601
656
  ),
602
- workItemId: z11.string().optional().describe("Required for fd documents; omitted for prd."),
657
+ workItemId: z11.string().optional().describe("The work item this document is about, so it shows on that item. Not allowed with type 'prd'."),
603
658
  content: z11.string().optional().describe("Optional initial markdown content.")
604
659
  },
605
660
  annotations: {
661
+ title: "Create a document",
606
662
  readOnlyHint: false,
607
663
  destructiveHint: false,
608
664
  idempotentHint: false,
609
665
  openWorldHint: false
610
666
  }
611
667
  },
612
- async (args) => {
668
+ async (args, extra) => {
613
669
  const result = await getConvexClient().action(api.mcp.createDocument, {
614
- token: getToken(),
670
+ token: getToken(extra),
615
671
  ...args
616
672
  });
617
673
  return {
@@ -636,15 +692,16 @@ function registerUpdateDocument(server) {
636
692
  title: z12.string().min(1).optional().describe("New title (optional).")
637
693
  },
638
694
  annotations: {
695
+ title: "Update a document",
639
696
  readOnlyHint: false,
640
697
  destructiveHint: true,
641
698
  idempotentHint: true,
642
699
  openWorldHint: false
643
700
  }
644
701
  },
645
- async (args) => {
702
+ async (args, extra) => {
646
703
  const result = await getConvexClient().action(api.mcp.updateDocument, {
647
- token: getToken(),
704
+ token: getToken(extra),
648
705
  ...args
649
706
  });
650
707
  return {
@@ -666,13 +723,14 @@ function registerListDocuments(server) {
666
723
  workItemId: z13.string().optional().describe("Optional workItemId to list linked FD documents.")
667
724
  },
668
725
  annotations: {
726
+ title: "List documents",
669
727
  readOnlyHint: true,
670
728
  openWorldHint: false
671
729
  }
672
730
  },
673
- async (args) => {
731
+ async (args, extra) => {
674
732
  const result = await getConvexClient().action(api.mcp.listDocuments, {
675
- token: getToken(),
733
+ token: getToken(extra),
676
734
  ...args
677
735
  });
678
736
  return {
@@ -705,15 +763,16 @@ function registerCreateProjectLink(server) {
705
763
  category: projectLinkCategory.optional().describe("Defaults to other.")
706
764
  },
707
765
  annotations: {
766
+ title: "Add a project link",
708
767
  readOnlyHint: false,
709
768
  destructiveHint: false,
710
769
  idempotentHint: false,
711
770
  openWorldHint: false
712
771
  }
713
772
  },
714
- async (args) => {
773
+ async (args, extra) => {
715
774
  const result = await getConvexClient().action(api.mcp.createProjectLink, {
716
- token: getToken(),
775
+ token: getToken(extra),
717
776
  ...args
718
777
  });
719
778
  return {
@@ -734,13 +793,14 @@ function registerListProjectLinks(server) {
734
793
  projectId: z15.string().describe("projectId from list_projects")
735
794
  },
736
795
  annotations: {
796
+ title: "List project links",
737
797
  readOnlyHint: true,
738
798
  openWorldHint: false
739
799
  }
740
800
  },
741
- async ({ projectId }) => {
801
+ async ({ projectId }, extra) => {
742
802
  const result = await getConvexClient().action(api.mcp.listProjectLinks, {
743
- token: getToken(),
803
+ token: getToken(extra),
744
804
  projectId
745
805
  });
746
806
  return {
@@ -761,13 +821,14 @@ function registerListProjectMembers(server) {
761
821
  projectId: z16.string().describe("projectId from list_projects")
762
822
  },
763
823
  annotations: {
824
+ title: "List project members",
764
825
  readOnlyHint: true,
765
826
  openWorldHint: false
766
827
  }
767
828
  },
768
- async ({ projectId }) => {
829
+ async ({ projectId }, extra) => {
769
830
  const result = await getConvexClient().action(api.mcp.listProjectMembers, {
770
- token: getToken(),
831
+ token: getToken(extra),
771
832
  projectId
772
833
  });
773
834
  return {
@@ -789,15 +850,16 @@ function registerUnlinkPr(server) {
789
850
  prNumber: z17.number().int().positive().describe("The PR number, e.g. 354 for #354")
790
851
  },
791
852
  annotations: {
853
+ title: "Unlink a pull request",
792
854
  readOnlyHint: false,
793
855
  destructiveHint: true,
794
856
  idempotentHint: true,
795
857
  openWorldHint: false
796
858
  }
797
859
  },
798
- async ({ workItemId, prNumber }) => {
860
+ async ({ workItemId, prNumber }, extra) => {
799
861
  const result = await getConvexClient().action(api.mcp.unlinkPR, {
800
- token: getToken(),
862
+ token: getToken(extra),
801
863
  workItemId,
802
864
  prNumber
803
865
  });
@@ -819,13 +881,14 @@ function registerListScratchNotes(server) {
819
881
  projectId: z18.string().describe("projectId from list_projects")
820
882
  },
821
883
  annotations: {
884
+ title: "List scratch notes",
822
885
  readOnlyHint: true,
823
886
  openWorldHint: false
824
887
  }
825
888
  },
826
- async ({ projectId }) => {
889
+ async ({ projectId }, extra) => {
827
890
  const notes = await getConvexClient().action(api.mcp.listScratchNotes, {
828
- token: getToken(),
891
+ token: getToken(extra),
829
892
  projectId
830
893
  });
831
894
  return {
@@ -847,15 +910,16 @@ function registerResolveScratchNote(server) {
847
910
  workItemId: z19.string().optional().describe("The workItemId the note became, from create_work_item or list_work_items.")
848
911
  },
849
912
  annotations: {
913
+ title: "Resolve a scratch note",
850
914
  readOnlyHint: false,
851
915
  destructiveHint: false,
852
916
  idempotentHint: true,
853
917
  openWorldHint: false
854
918
  }
855
919
  },
856
- async ({ noteId, workItemId }) => {
920
+ async ({ noteId, workItemId }, extra) => {
857
921
  const result = await getConvexClient().action(api.mcp.resolveScratchNote, {
858
- token: getToken(),
922
+ token: getToken(extra),
859
923
  noteId,
860
924
  workItemId
861
925
  });
@@ -878,15 +942,16 @@ function registerHeartbeatWorkItem(server) {
878
942
  model: z20.string().optional().describe("The model you are running as, e.g. claude-opus-5 or gpt-5, so the app can say which agent is on this. Your harness is reported automatically.")
879
943
  },
880
944
  annotations: {
945
+ title: "Keep an agent lease alive",
881
946
  readOnlyHint: false,
882
947
  destructiveHint: false,
883
948
  idempotentHint: true,
884
949
  openWorldHint: false
885
950
  }
886
951
  },
887
- async ({ workItemId, model }) => {
952
+ async ({ workItemId, model }, extra) => {
888
953
  const result = await getConvexClient().action(api.mcp.heartbeatWorkItem, {
889
- token: getToken(),
954
+ token: getToken(extra),
890
955
  workItemId,
891
956
  agent: agentArg(model)
892
957
  });
@@ -909,15 +974,16 @@ function registerWatchWorkItem(server) {
909
974
  watching: z21.boolean().default(true).describe("true to watch, false to stop")
910
975
  },
911
976
  annotations: {
977
+ title: "Watch a work item",
912
978
  readOnlyHint: false,
913
979
  destructiveHint: false,
914
980
  idempotentHint: true,
915
981
  openWorldHint: false
916
982
  }
917
983
  },
918
- async ({ workItemId, watching }) => {
984
+ async ({ workItemId, watching }, extra) => {
919
985
  const result = await getConvexClient().action(api.mcp.watchWorkItem, {
920
- token: getToken(),
986
+ token: getToken(extra),
921
987
  workItemId,
922
988
  watching
923
989
  });
@@ -942,15 +1008,16 @@ function registerAddWorkItemImage(server) {
942
1008
  altText: z22.string().optional().describe("Optional description/caption for the image")
943
1009
  },
944
1010
  annotations: {
1011
+ title: "Attach an image to a work item",
945
1012
  readOnlyHint: false,
946
1013
  destructiveHint: false,
947
1014
  idempotentHint: false,
948
1015
  openWorldHint: false
949
1016
  }
950
1017
  },
951
- async (args) => {
1018
+ async (args, extra) => {
952
1019
  const result = await getConvexClient().action(api.mcp.addWorkItemImage, {
953
- token: getToken(),
1020
+ token: getToken(extra),
954
1021
  ...args
955
1022
  });
956
1023
  return {
@@ -971,15 +1038,16 @@ function registerListWorkItemImages(server) {
971
1038
  workItemId: z23.string().describe("workItemId from list_work_items")
972
1039
  },
973
1040
  annotations: {
1041
+ title: "List work item images",
974
1042
  readOnlyHint: true,
975
1043
  openWorldHint: false
976
1044
  }
977
1045
  },
978
- async ({ workItemId }) => {
1046
+ async ({ workItemId }, extra) => {
979
1047
  const result = await getConvexClient().action(
980
1048
  api.mcp.listWorkItemImages,
981
1049
  {
982
- token: getToken(),
1050
+ token: getToken(extra),
983
1051
  workItemId
984
1052
  }
985
1053
  );
@@ -1005,17 +1073,18 @@ function registerAddWorkItemComment(server) {
1005
1073
  source: z24.enum(["user", "ai"]).optional().describe('Who authored it; defaults to "ai" for MCP callers')
1006
1074
  },
1007
1075
  annotations: {
1076
+ title: "Comment on a work item",
1008
1077
  readOnlyHint: false,
1009
1078
  destructiveHint: false,
1010
1079
  idempotentHint: false,
1011
1080
  openWorldHint: false
1012
1081
  }
1013
1082
  },
1014
- async (args) => {
1083
+ async (args, extra) => {
1015
1084
  const result = await getConvexClient().action(
1016
1085
  api.mcp.addWorkItemComment,
1017
1086
  {
1018
- token: getToken(),
1087
+ token: getToken(extra),
1019
1088
  ...args,
1020
1089
  agent: agentArg()
1021
1090
  }
@@ -1038,15 +1107,16 @@ function registerListWorkItemComments(server) {
1038
1107
  workItemId: z25.string().describe("workItemId from list_work_items")
1039
1108
  },
1040
1109
  annotations: {
1110
+ title: "List work item comments",
1041
1111
  readOnlyHint: true,
1042
1112
  openWorldHint: false
1043
1113
  }
1044
1114
  },
1045
- async (args) => {
1115
+ async (args, extra) => {
1046
1116
  const result = await getConvexClient().action(
1047
1117
  api.mcp.listWorkItemComments,
1048
1118
  {
1049
- token: getToken(),
1119
+ token: getToken(extra),
1050
1120
  ...args
1051
1121
  }
1052
1122
  );
@@ -1068,14 +1138,15 @@ function registerListValidations(server) {
1068
1138
  projectId: z26.string().describe("projectId from list_projects")
1069
1139
  },
1070
1140
  annotations: {
1141
+ title: "List validations",
1071
1142
  readOnlyHint: true,
1072
1143
  openWorldHint: false
1073
1144
  }
1074
1145
  },
1075
- async ({ projectId }) => {
1146
+ async ({ projectId }, extra) => {
1076
1147
  const client = getConvexClient();
1077
1148
  const graph = await client.action(api.mcp.listValidations, {
1078
- token: getToken(),
1149
+ token: getToken(extra),
1079
1150
  projectId
1080
1151
  });
1081
1152
  return {
@@ -1097,16 +1168,17 @@ function registerRecordValidation(server) {
1097
1168
  result: z27.enum(["pass", "fail"]).describe("Outcome of running the test")
1098
1169
  },
1099
1170
  annotations: {
1171
+ title: "Record a validation result",
1100
1172
  readOnlyHint: false,
1101
1173
  destructiveHint: true,
1102
1174
  idempotentHint: true,
1103
1175
  openWorldHint: false
1104
1176
  }
1105
1177
  },
1106
- async ({ validationTaskId, result }) => {
1178
+ async ({ validationTaskId, result }, extra) => {
1107
1179
  const client = getConvexClient();
1108
1180
  const res = await client.action(api.mcp.recordValidation, {
1109
- token: getToken(),
1181
+ token: getToken(extra),
1110
1182
  validationTaskId,
1111
1183
  result
1112
1184
  });
@@ -1128,16 +1200,17 @@ function registerMarkSystemChanged(server) {
1128
1200
  systemId: z28.string().describe("systemId from list_validations")
1129
1201
  },
1130
1202
  annotations: {
1203
+ title: "Mark a system as changed",
1131
1204
  readOnlyHint: false,
1132
1205
  destructiveHint: false,
1133
1206
  idempotentHint: false,
1134
1207
  openWorldHint: false
1135
1208
  }
1136
1209
  },
1137
- async ({ systemId }) => {
1210
+ async ({ systemId }, extra) => {
1138
1211
  const client = getConvexClient();
1139
1212
  const res = await client.action(api.mcp.markSystemChanged, {
1140
- token: getToken(),
1213
+ token: getToken(extra),
1141
1214
  systemId
1142
1215
  });
1143
1216
  return {
@@ -1160,16 +1233,17 @@ function registerCreateSystem(server) {
1160
1233
  description: z29.string().optional().describe("Optional details")
1161
1234
  },
1162
1235
  annotations: {
1236
+ title: "Create a system",
1163
1237
  readOnlyHint: false,
1164
1238
  destructiveHint: false,
1165
1239
  idempotentHint: false,
1166
1240
  openWorldHint: false
1167
1241
  }
1168
1242
  },
1169
- async (args) => {
1243
+ async (args, extra) => {
1170
1244
  const client = getConvexClient();
1171
1245
  const res = await client.action(api.mcp.createSystem, {
1172
- token: getToken(),
1246
+ token: getToken(extra),
1173
1247
  ...args
1174
1248
  });
1175
1249
  return {
@@ -1198,16 +1272,17 @@ function registerCreateValidationTask(server) {
1198
1272
  )
1199
1273
  },
1200
1274
  annotations: {
1275
+ title: "Create a validation task",
1201
1276
  readOnlyHint: false,
1202
1277
  destructiveHint: false,
1203
1278
  idempotentHint: false,
1204
1279
  openWorldHint: false
1205
1280
  }
1206
1281
  },
1207
- async (args) => {
1282
+ async (args, extra) => {
1208
1283
  const client = getConvexClient();
1209
1284
  const res = await client.action(api.mcp.createValidationTask, {
1210
- token: getToken(),
1285
+ token: getToken(extra),
1211
1286
  ...args
1212
1287
  });
1213
1288
  return {
@@ -1229,16 +1304,17 @@ function registerAddSystemToTask(server) {
1229
1304
  systemId: z31.string().describe("systemId from list_validations")
1230
1305
  },
1231
1306
  annotations: {
1307
+ title: "Link a system to a task",
1232
1308
  readOnlyHint: false,
1233
1309
  destructiveHint: false,
1234
1310
  idempotentHint: true,
1235
1311
  openWorldHint: false
1236
1312
  }
1237
1313
  },
1238
- async ({ validationTaskId, systemId }) => {
1314
+ async ({ validationTaskId, systemId }, extra) => {
1239
1315
  const client = getConvexClient();
1240
1316
  const res = await client.action(api.mcp.addSystemToTask, {
1241
- token: getToken(),
1317
+ token: getToken(extra),
1242
1318
  validationTaskId,
1243
1319
  systemId
1244
1320
  });
@@ -1261,16 +1337,17 @@ function registerAddValidationDependency(server) {
1261
1337
  dependsOnTaskId: z32.string().describe("The dependency (upstream) task it relies on")
1262
1338
  },
1263
1339
  annotations: {
1340
+ title: "Add a validation dependency",
1264
1341
  readOnlyHint: false,
1265
1342
  destructiveHint: false,
1266
1343
  idempotentHint: true,
1267
1344
  openWorldHint: false
1268
1345
  }
1269
1346
  },
1270
- async ({ validationTaskId, dependsOnTaskId }) => {
1347
+ async ({ validationTaskId, dependsOnTaskId }, extra) => {
1271
1348
  const client = getConvexClient();
1272
1349
  const res = await client.action(api.mcp.addValidationDependency, {
1273
- token: getToken(),
1350
+ token: getToken(extra),
1274
1351
  validationTaskId,
1275
1352
  dependsOnTaskId
1276
1353
  });
@@ -1292,13 +1369,14 @@ function registerListLabels(server) {
1292
1369
  projectId: z33.string().describe("projectId from list_projects")
1293
1370
  },
1294
1371
  annotations: {
1372
+ title: "List labels",
1295
1373
  readOnlyHint: true,
1296
1374
  openWorldHint: false
1297
1375
  }
1298
1376
  },
1299
- async (args) => {
1377
+ async (args, extra) => {
1300
1378
  const result = await getConvexClient().action(api.mcp.listLabels, {
1301
- token: getToken(),
1379
+ token: getToken(extra),
1302
1380
  ...args
1303
1381
  });
1304
1382
  return {
@@ -1321,15 +1399,16 @@ function registerCreateLabel(server) {
1321
1399
  color: z34.string().regex(/^#[0-9a-fA-F]{6}$/).optional().describe("Hex color like #2563eb. Defaults to a neutral slate.")
1322
1400
  },
1323
1401
  annotations: {
1402
+ title: "Create a label",
1324
1403
  readOnlyHint: false,
1325
1404
  destructiveHint: false,
1326
1405
  idempotentHint: true,
1327
1406
  openWorldHint: false
1328
1407
  }
1329
1408
  },
1330
- async (args) => {
1409
+ async (args, extra) => {
1331
1410
  const result = await getConvexClient().action(api.mcp.createLabel, {
1332
- token: getToken(),
1411
+ token: getToken(extra),
1333
1412
  ...args
1334
1413
  });
1335
1414
  return {
@@ -1351,15 +1430,16 @@ function registerAddLabelToWorkItem(server) {
1351
1430
  labelId: z35.string().describe("labelId from list_labels or create_label")
1352
1431
  },
1353
1432
  annotations: {
1433
+ title: "Add a label to a work item",
1354
1434
  readOnlyHint: false,
1355
1435
  destructiveHint: false,
1356
1436
  idempotentHint: true,
1357
1437
  openWorldHint: false
1358
1438
  }
1359
1439
  },
1360
- async (args) => {
1440
+ async (args, extra) => {
1361
1441
  const result = await getConvexClient().action(api.mcp.setWorkItemLabel, {
1362
- token: getToken(),
1442
+ token: getToken(extra),
1363
1443
  ...args,
1364
1444
  attached: true
1365
1445
  });
@@ -1382,15 +1462,16 @@ function registerRemoveLabelFromWorkItem(server) {
1382
1462
  labelId: z36.string().describe("labelId from list_labels")
1383
1463
  },
1384
1464
  annotations: {
1465
+ title: "Remove a label from a work item",
1385
1466
  readOnlyHint: false,
1386
1467
  destructiveHint: true,
1387
1468
  idempotentHint: true,
1388
1469
  openWorldHint: false
1389
1470
  }
1390
1471
  },
1391
- async (args) => {
1472
+ async (args, extra) => {
1392
1473
  const result = await getConvexClient().action(api.mcp.setWorkItemLabel, {
1393
- token: getToken(),
1474
+ token: getToken(extra),
1394
1475
  ...args,
1395
1476
  attached: false
1396
1477
  });
@@ -1412,13 +1493,14 @@ function registerListMilestones(server) {
1412
1493
  projectId: z37.string().describe("projectId from list_projects")
1413
1494
  },
1414
1495
  annotations: {
1496
+ title: "List milestones",
1415
1497
  readOnlyHint: true,
1416
1498
  openWorldHint: false
1417
1499
  }
1418
1500
  },
1419
- async (args) => {
1501
+ async (args, extra) => {
1420
1502
  const result = await getConvexClient().action(api.mcp.listMilestones, {
1421
- token: getToken(),
1503
+ token: getToken(extra),
1422
1504
  ...args
1423
1505
  });
1424
1506
  return {
@@ -1442,15 +1524,16 @@ function registerCreateMilestone(server) {
1442
1524
  dueDate: z38.number().optional().describe("Optional due date as a unix timestamp in milliseconds")
1443
1525
  },
1444
1526
  annotations: {
1527
+ title: "Create a milestone",
1445
1528
  readOnlyHint: false,
1446
1529
  destructiveHint: false,
1447
1530
  idempotentHint: false,
1448
1531
  openWorldHint: false
1449
1532
  }
1450
1533
  },
1451
- async (args) => {
1534
+ async (args, extra) => {
1452
1535
  const result = await getConvexClient().action(api.mcp.createMilestone, {
1453
- token: getToken(),
1536
+ token: getToken(extra),
1454
1537
  ...args
1455
1538
  });
1456
1539
  return {
@@ -1472,17 +1555,18 @@ function registerAddItemToMilestone(server) {
1472
1555
  workItemId: z39.string().describe("workItemId from list_work_items")
1473
1556
  },
1474
1557
  annotations: {
1558
+ title: "Add an item to a milestone",
1475
1559
  readOnlyHint: false,
1476
1560
  destructiveHint: false,
1477
1561
  idempotentHint: true,
1478
1562
  openWorldHint: false
1479
1563
  }
1480
1564
  },
1481
- async (args) => {
1565
+ async (args, extra) => {
1482
1566
  const result = await getConvexClient().action(
1483
1567
  api.mcp.setWorkItemMilestone,
1484
1568
  {
1485
- token: getToken(),
1569
+ token: getToken(extra),
1486
1570
  ...args,
1487
1571
  attached: true
1488
1572
  }
@@ -1506,17 +1590,18 @@ function registerRemoveItemFromMilestone(server) {
1506
1590
  workItemId: z40.string().describe("workItemId from list_work_items")
1507
1591
  },
1508
1592
  annotations: {
1593
+ title: "Remove an item from a milestone",
1509
1594
  readOnlyHint: false,
1510
1595
  destructiveHint: true,
1511
1596
  idempotentHint: true,
1512
1597
  openWorldHint: false
1513
1598
  }
1514
1599
  },
1515
- async (args) => {
1600
+ async (args, extra) => {
1516
1601
  const result = await getConvexClient().action(
1517
1602
  api.mcp.setWorkItemMilestone,
1518
1603
  {
1519
- token: getToken(),
1604
+ token: getToken(extra),
1520
1605
  ...args,
1521
1606
  attached: false
1522
1607
  }