vantage-peers-mcp 2.4.0 → 2.4.1

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 CHANGED
@@ -3,11 +3,11 @@
3
3
  [![npm version](https://img.shields.io/npm/v/vantage-peers-mcp)](https://www.npmjs.com/package/vantage-peers-mcp)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/vantage-peers-mcp)](https://www.npmjs.com/package/vantage-peers-mcp)
5
5
  [![License: FSL-1.1-Apache-2.0](https://img.shields.io/badge/license-FSL--1.1--Apache--2.0-blue)](https://github.com/vantageos-agency/vantage-peers/blob/main/LICENSE)
6
- [![Tests: 82/82](https://img.shields.io/badge/MCP_tools-82_registered-green)]()
6
+ [![Tests: 84/84](https://img.shields.io/badge/MCP_tools-84_registered-green)]()
7
7
 
8
8
  MCP server for [VantagePeers](https://vantagepeers.com) — shared memory, messaging, and task coordination for AI agent teams.
9
9
 
10
- 82 tools across 18 categories: memory, profiles, tasks, missions, mission templates, messages, diary, briefing notes, search (RAG), issues, fix patterns, error monitoring, deployments, business units, components, mandates, recurring tasks, and session.
10
+ 84 tools across 18 categories: memory, profiles, tasks, missions, mission templates, messages, diary, briefing notes, search (RAG), issues, fix patterns, error monitoring, deployments, business units, components, mandates, recurring tasks, and session. All tools ship with ChatGPT Apps SDK annotations (`readOnlyHint`, `openWorldHint`, `destructiveHint`) for native UX in ChatGPT custom connectors.
11
11
 
12
12
  ## Quick start
13
13
 
@@ -74,7 +74,7 @@ VantagePeers ships a built-in OAuth 2.1 authorization server so Claude.ai web ca
74
74
 
75
75
  The server also reads `CONVEX_URL` from `.env.local` in the parent directory if not set via environment.
76
76
 
77
- ## Tools (82)
77
+ ## Tools (84)
78
78
 
79
79
  ### Memory (6)
80
80
  `store_memory`, `recall`, `list_memories`, `soft_delete_memory`, `get_memory`, `store_episode`
@@ -400,6 +400,17 @@ All orchestrator names are open strings — any lowercase name is accepted. The
400
400
 
401
401
  ## Changelog
402
402
 
403
+ ### 2.4.1 — 2026-05-30 (Day 88)
404
+ - fix(dcr): `oauthDcr:validateAccessToken` exposed as PUBLIC `query` (was `internalQuery`, unreachable via `ConvexHttpClient.query()` → Path 3 DCR returned 401 even with valid token) — issue #556 / PR #557.
405
+ - fix(dcr): `WWW-Authenticate` header now emits `Bearer resource_metadata="..."` per MCP spec §Protected Resource Metadata Discovery (was `resource="..."` — broke Claude.ai PRM discovery bootstrap on 401) — PR #557.
406
+ - feat(mcp): ChatGPT Apps SDK tool annotations on all 84 tools (`readOnlyHint`, `openWorldHint`, `destructiveHint`) — 34 read-only + 41 write + 9 destructive — PR #555.
407
+ - security(dcr): DCR scope isolation — new `public-readonly` profile + cross-tenant assertion tests + `scopeProfile` forced to `client-generic` for auto-discovery flow (never `master`) — PR #554.
408
+ - docs(cloud): dedicated `/docs/cloud/` section in vantage-peers-site for VantagePeers Cloud (multi-tenant, multi-clients MCP: Claude.ai, ChatGPT, Claude Code, Codex) — site PR #120.
409
+
410
+ ### 2.4.0 — 2026-05-29 (Day 86)
411
+ - feat(m3): `iframeEmbedSessions` table + `__VP_TOOL_RESULT__` stream marker + ack-checklist primitive — PR #545.
412
+ - feat(v0.0.2-auth): `credentials:issueBearerFromClerk` httpAction + audit log + iter 2 P1 fixes — PR #546.
413
+
403
414
  ### 2.3.0 — 2026-05-26
404
415
  - `list_tasks`, `list_missions`, `list_tasks_by_mission`, `list_briefing_notes` now accept `fields=lite` for compact payloads (less tokens).
405
416
  - Status filters now accept arrays and aliases: `status=["todo","in_progress"]`, `status="open"` (expands to non-terminal), `status="active"` (in_progress only on tasks; plan+execute on missions), `status="all"` (no filter).
package/dist/src/auth.js CHANGED
@@ -139,11 +139,13 @@ export function checkNamespaceWrite(ctx, namespace) {
139
139
  // ─────────────────────────────────────────────────────────────────────────────
140
140
  export function bearerAuthMiddleware() {
141
141
  return async (c, next) => {
142
- // RFC 6750 §3 point clients at the protected-resource metadata so
143
- // Claude.ai's OAuth connector can bootstrap discovery from any 401.
142
+ // MCP spec §"Protected Resource Metadata Discovery Requirements" + RFC 6750 §3 —
143
+ // the param MUST be `resource_metadata=` (not `resource=`). Claude.ai's OAuth
144
+ // connector looks for `resource_metadata=` to bootstrap PRM discovery; with
145
+ // `resource=` the entire DCR chain breaks before any token is issued.
144
146
  const publicBaseUrl = process.env.PUBLIC_BASE_URL ??
145
147
  "https://vantage-peers-production.up.railway.app";
146
- const wwwAuthHeader = `Bearer resource="${publicBaseUrl}/.well-known/oauth-protected-resource"`;
148
+ const wwwAuthHeader = `Bearer resource_metadata="${publicBaseUrl}/.well-known/oauth-protected-resource"`;
147
149
  const authHeader = c.req.header("Authorization");
148
150
  if (!authHeader?.startsWith("Bearer ")) {
149
151
  c.header("WWW-Authenticate", wwwAuthHeader);
@@ -215,6 +217,10 @@ export function bearerAuthMiddleware() {
215
217
  // ── (3) DCR OAuth token — check oauthTokens via oauthDcr:validateAccessToken
216
218
  // Uses raw token (not hashed) — the DCR table stores tokens in plaintext.
217
219
  // This path handles Claude.ai clients registered via POST /register.
220
+ // NOTE: validateAccessToken is exposed as a PUBLIC query (not internalQuery)
221
+ // because ConvexHttpClient.query() only resolves public functions. Making it
222
+ // internal silently breaks the DCR path (#556). Security: lookup is keyed
223
+ // on the high-entropy opaque token; returns null on miss with no PII echo.
218
224
  let dcrResult = null;
219
225
  try {
220
226
  dcrResult = (await internalClient().query(
package/dist/src/tools.js CHANGED
@@ -153,7 +153,9 @@ const taskStatusValues = [
153
153
  "done",
154
154
  ];
155
155
  const taskStatusAliases = ["open", "active", "all"];
156
- export const taskStatusSchema = z.enum(taskStatusValues).describe("Task status");
156
+ export const taskStatusSchema = z
157
+ .enum(taskStatusValues)
158
+ .describe("Task status");
157
159
  const missionStatusValues = [
158
160
  "brainstorm",
159
161
  "plan",
@@ -176,7 +178,7 @@ export const taskStatusFilterSchema = z
176
178
  ])
177
179
  .describe('Task status filter. Single status ("todo"|"in_progress"|"review"|"blocked"|"done"), ' +
178
180
  'alias ("open" = todo+in_progress+review+blocked, "active" = todo+in_progress, "all" = no filter), ' +
179
- 'or array of direct statuses (no aliases inside array).');
181
+ "or array of direct statuses (no aliases inside array).");
180
182
  export const missionStatusFilterSchema = z
181
183
  .union([
182
184
  z.enum([...missionStatusValues, ...missionStatusAliases]),
@@ -184,13 +186,13 @@ export const missionStatusFilterSchema = z
184
186
  ])
185
187
  .describe('Mission status filter. Single status ("brainstorm"|"plan"|"execute"|"validate"|"complete"), ' +
186
188
  'alias ("open" = brainstorm+plan+execute+validate, "active" = plan+execute, "all" = no filter), ' +
187
- 'or array of direct statuses (no aliases inside array).');
189
+ "or array of direct statuses (no aliases inside array).");
188
190
  // v2.3.2 — fields projection toggle. "lite" returns compact projection
189
191
  // (5-10× smaller payload), "full" (default) returns full doc.
190
192
  export const fieldsSchema = z
191
193
  .enum(["lite", "full"])
192
194
  .describe('Field projection — "lite" returns compact fields only ' +
193
- '(typical 5-10× smaller payload for large list scans), ' +
195
+ "(typical 5-10× smaller payload for large list scans), " +
194
196
  '"full" (default) returns the full document.');
195
197
  // v2.3.3 — Unix timestamp ms filter for "updated since".
196
198
  // Pass `Date.now() - 24*60*60*1000` for "last 24h" pattern.
@@ -296,7 +298,8 @@ export function parseConvexError(rawMessage) {
296
298
  let code = "ServerError";
297
299
  let remainder = stripped;
298
300
  for (const candidate of knownCodes) {
299
- if (stripped.startsWith(candidate + ":") || stripped.startsWith(candidate + " ")) {
301
+ if (stripped.startsWith(candidate + ":") ||
302
+ stripped.startsWith(candidate + " ")) {
300
303
  code = candidate;
301
304
  remainder = stripped.slice(candidate.length).replace(/^[:\s]+/, "");
302
305
  break;
@@ -305,10 +308,13 @@ export function parseConvexError(rawMessage) {
305
308
  // Extract "Path: .<fieldPath>" from the tail of the message
306
309
  // Convex appends this as the last sentence: "Path: .linkedMemoryIds[4]"
307
310
  let path = null;
308
- const pathMatch = remainder.match(/\bPath:\s*([\w.\[\]"']+)\s*$/);
311
+ const pathMatch = remainder.match(/\bPath:\s*([\w.[\]"']+)\s*$/);
309
312
  if (pathMatch) {
310
313
  path = pathMatch[1];
311
- remainder = remainder.slice(0, pathMatch.index).trim().replace(/\.\s*$/, "");
314
+ remainder = remainder
315
+ .slice(0, pathMatch.index)
316
+ .trim()
317
+ .replace(/\.\s*$/, "");
312
318
  }
313
319
  // Build a concise hint for common patterns
314
320
  let hint = null;
@@ -412,6 +418,11 @@ export function registerTools(server, convex, oauthCtx) {
412
418
  .string()
413
419
  .optional()
414
420
  .describe("Optional expiry ISO timestamp e.g. '2026-06-01T00:00:00Z'"),
421
+ }, {
422
+ readOnlyHint: false,
423
+ openWorldHint: false,
424
+ destructiveHint: false,
425
+ title: "Store memory",
415
426
  }, async ({ namespace, type, content, createdBy, relatesTo, ttl }) => {
416
427
  let contentBytes = 0;
417
428
  try {
@@ -461,6 +472,11 @@ export function registerTools(server, convex, oauthCtx) {
461
472
  memoryId: z
462
473
  .string()
463
474
  .describe("Convex document ID of the memory to soft-delete"),
475
+ }, {
476
+ readOnlyHint: false,
477
+ openWorldHint: false,
478
+ destructiveHint: true,
479
+ title: "Delete memory (soft)",
464
480
  }, async ({ memoryId }) => {
465
481
  try {
466
482
  const denied = guardMasterOnly("soft_delete_memory");
@@ -485,6 +501,11 @@ export function registerTools(server, convex, oauthCtx) {
485
501
  // ── get_memory ──────────────────────────────────────────────────────────────
486
502
  server.tool("get_memory", "Fetch a single memory by its ID. Returns full memory content including relations and episode data.", {
487
503
  memoryId: z.string().describe("Memory document ID"),
504
+ }, {
505
+ readOnlyHint: true,
506
+ openWorldHint: false,
507
+ destructiveHint: false,
508
+ title: "Get memory",
488
509
  }, async ({ memoryId }) => {
489
510
  try {
490
511
  const memory = await convex.query("memories:getMemory", {
@@ -519,6 +540,11 @@ export function registerTools(server, convex, oauthCtx) {
519
540
  .optional()
520
541
  .default(5)
521
542
  .describe("Maximum number of results to return (default 5)"),
543
+ }, {
544
+ readOnlyHint: true,
545
+ openWorldHint: false,
546
+ destructiveHint: false,
547
+ title: "Recall memories",
522
548
  }, async ({ query, namespace, type, limit }) => {
523
549
  try {
524
550
  const nsDenied = guardRead(namespace);
@@ -559,6 +585,11 @@ export function registerTools(server, convex, oauthCtx) {
559
585
  .optional()
560
586
  .default(10)
561
587
  .describe("Max results"),
588
+ }, {
589
+ readOnlyHint: true,
590
+ openWorldHint: false,
591
+ destructiveHint: false,
592
+ title: "Search memories (text)",
562
593
  }, async ({ query, namespace, type, limit }) => {
563
594
  try {
564
595
  const nsDenied = guardRead(namespace);
@@ -603,6 +634,11 @@ export function registerTools(server, convex, oauthCtx) {
603
634
  .max(1)
604
635
  .optional()
605
636
  .describe("Weight for text results in RRF (default: 0.5)"),
637
+ }, {
638
+ readOnlyHint: true,
639
+ openWorldHint: false,
640
+ destructiveHint: false,
641
+ title: "Search memories (hybrid)",
606
642
  }, async ({ query, namespace, type, limit, vectorWeight, textWeight }) => {
607
643
  try {
608
644
  const nsDenied = guardRead(namespace);
@@ -642,6 +678,11 @@ export function registerTools(server, convex, oauthCtx) {
642
678
  .string()
643
679
  .describe("The lesson extracted — procedural memory, what to do differently"),
644
680
  severity: severitySchema,
681
+ }, {
682
+ readOnlyHint: false,
683
+ openWorldHint: false,
684
+ destructiveHint: false,
685
+ title: "Store episode",
645
686
  }, async ({ namespace, createdBy, context, goal, action, outcome, insight, severity, }) => {
646
687
  try {
647
688
  const fromDenied = guardFrom(createdBy);
@@ -677,6 +718,11 @@ export function registerTools(server, convex, oauthCtx) {
677
718
  server.tool("get_profile", "Fetch an orchestrator profile (static identity + dynamic session state). " +
678
719
  "Returns null if the profile does not exist yet — call update_profile to create it.", {
679
720
  orchestratorId: z.string().describe("Orchestrator identifier"),
721
+ }, {
722
+ readOnlyHint: true,
723
+ openWorldHint: false,
724
+ destructiveHint: false,
725
+ title: "Get orchestrator profile",
680
726
  }, async ({ orchestratorId }) => {
681
727
  try {
682
728
  const profile = await convex.query("profiles:getProfile", {
@@ -724,6 +770,11 @@ export function registerTools(server, convex, oauthCtx) {
724
770
  })
725
771
  .optional()
726
772
  .describe("Mutable session state — updated each session"),
773
+ }, {
774
+ readOnlyHint: false,
775
+ openWorldHint: false,
776
+ destructiveHint: false,
777
+ title: "Update orchestrator profile",
727
778
  }, async ({ orchestratorId, name, static: staticFields, dynamic }) => {
728
779
  try {
729
780
  const fromDenied = guardFrom(orchestratorId);
@@ -766,6 +817,11 @@ export function registerTools(server, convex, oauthCtx) {
766
817
  .optional()
767
818
  .default(20)
768
819
  .describe("Maximum number of memories to return (default 20)"),
820
+ }, {
821
+ readOnlyHint: true,
822
+ openWorldHint: false,
823
+ destructiveHint: false,
824
+ title: "List memories",
769
825
  }, async ({ namespace, type, limit }) => {
770
826
  try {
771
827
  const nsDenied = guardRead(namespace);
@@ -822,6 +878,11 @@ export function registerTools(server, convex, oauthCtx) {
822
878
  .string()
823
879
  .optional()
824
880
  .describe("Tenant identifier for multi-tenant isolation"),
881
+ }, {
882
+ readOnlyHint: false,
883
+ openWorldHint: false,
884
+ destructiveHint: false,
885
+ title: "Send message",
825
886
  }, async ({ from, fromInstanceId, channel, content, sessionDay, tenantId, }) => {
826
887
  let contentBytes = 0;
827
888
  try {
@@ -876,6 +937,11 @@ export function registerTools(server, convex, oauthCtx) {
876
937
  .int()
877
938
  .optional()
878
939
  .describe("Unix timestamp (ms). If provided, only messages with _creationTime > since are returned. Use for incremental polling — pass the timestamp of your last check to get only new messages. Omit for full unread backlog."),
940
+ }, {
941
+ readOnlyHint: true,
942
+ openWorldHint: false,
943
+ destructiveHint: false,
944
+ title: "Check messages",
879
945
  }, async ({ recipient, recipientInstanceId, tenantId, since }) => {
880
946
  try {
881
947
  const messages = await convex.query("messages:checkNewMessages", {
@@ -915,6 +981,11 @@ export function registerTools(server, convex, oauthCtx) {
915
981
  receiptIds: z
916
982
  .union([z.array(receiptIdSchema).min(1), receiptIdSchema])
917
983
  .describe("Receipt IDs to mark as read — array or single string"),
984
+ }, {
985
+ readOnlyHint: false,
986
+ openWorldHint: false,
987
+ destructiveHint: false,
988
+ title: "Mark messages as read",
918
989
  }, async ({ receiptIds }) => {
919
990
  try {
920
991
  let receiptIdsArray;
@@ -957,6 +1028,11 @@ export function registerTools(server, convex, oauthCtx) {
957
1028
  callerOrchestrator: creatorSchema
958
1029
  .optional()
959
1030
  .describe("Optional RBAC — must be the sender or system"),
1031
+ }, {
1032
+ readOnlyHint: false,
1033
+ openWorldHint: false,
1034
+ destructiveHint: true,
1035
+ title: "Delete message",
960
1036
  }, async ({ messageId, callerOrchestrator }) => {
961
1037
  try {
962
1038
  if (callerOrchestrator) {
@@ -991,6 +1067,11 @@ export function registerTools(server, convex, oauthCtx) {
991
1067
  .optional()
992
1068
  .describe("Instance ID — e.g. 'pi-chromebook', 'pi-vps', 'tau-vps-1'"),
993
1069
  summary: z.string().describe("1-2 sentence summary of current work"),
1070
+ }, {
1071
+ readOnlyHint: false,
1072
+ openWorldHint: false,
1073
+ destructiveHint: false,
1074
+ title: "Set instance summary",
994
1075
  }, async ({ orchestratorId, instanceId, summary }) => {
995
1076
  try {
996
1077
  const fromDenied = guardFrom(orchestratorId);
@@ -1017,7 +1098,12 @@ export function registerTools(server, convex, oauthCtx) {
1017
1098
  });
1018
1099
  // ── list_peers ──────────────────────────────────────────────────────────────
1019
1100
  server.tool("list_peers", "List all orchestrator profiles with their current status and summary. " +
1020
- "Replaces claude-peers list_peers.", {}, async () => {
1101
+ "Replaces claude-peers list_peers.", {}, {
1102
+ readOnlyHint: true,
1103
+ openWorldHint: false,
1104
+ destructiveHint: false,
1105
+ title: "List peers",
1106
+ }, async () => {
1021
1107
  try {
1022
1108
  const profiles = await convex.query("profiles:listProfiles", {});
1023
1109
  const peers = profiles.map((p) => ({
@@ -1059,6 +1145,11 @@ export function registerTools(server, convex, oauthCtx) {
1059
1145
  .optional()
1060
1146
  .default(100)
1061
1147
  .describe("Max messages to return (default 100)"),
1148
+ }, {
1149
+ readOnlyHint: true,
1150
+ openWorldHint: false,
1151
+ destructiveHint: false,
1152
+ title: "List messages",
1062
1153
  }, async ({ sessionDay, from, limit }) => {
1063
1154
  try {
1064
1155
  const messages = await convex.query("messages:listMessages", {
@@ -1092,6 +1183,11 @@ export function registerTools(server, convex, oauthCtx) {
1092
1183
  messageId: z
1093
1184
  .string()
1094
1185
  .describe("Convex document ID of the broadcast message"),
1186
+ }, {
1187
+ readOnlyHint: true,
1188
+ openWorldHint: false,
1189
+ destructiveHint: false,
1190
+ title: "List broadcast status",
1095
1191
  }, async ({ messageId }) => {
1096
1192
  try {
1097
1193
  const status = await convex.query("messages:listBroadcastStatus", {
@@ -1144,6 +1240,11 @@ export function registerTools(server, convex, oauthCtx) {
1144
1240
  .optional()
1145
1241
  .describe("Optional due date as Unix timestamp (ms)"),
1146
1242
  createdBy: creatorSchema,
1243
+ }, {
1244
+ readOnlyHint: false,
1245
+ openWorldHint: false,
1246
+ destructiveHint: false,
1247
+ title: "Create task",
1147
1248
  }, async ({ title, description, project, tags, assignedTo, assignedToInstance, priority, status, dependsOn, missionId, estimatedMinutes, dueDate, createdBy, }) => {
1148
1249
  try {
1149
1250
  const fromDenied = guardFrom(createdBy);
@@ -1206,6 +1307,11 @@ export function registerTools(server, convex, oauthCtx) {
1206
1307
  .optional()
1207
1308
  .describe("Filter by task creator (e.g. 'pi' to find Pi-dispatched tasks)"),
1208
1309
  updatedSince: updatedSinceSchema.optional(),
1310
+ }, {
1311
+ readOnlyHint: true,
1312
+ openWorldHint: false,
1313
+ destructiveHint: false,
1314
+ title: "List tasks",
1209
1315
  }, async ({ assignedTo, assignedToInstance, status, project, limit, fields, createdBy, updatedSince, }) => {
1210
1316
  try {
1211
1317
  const tasks = await convex.query("tasks:list", {
@@ -1276,6 +1382,11 @@ export function registerTools(server, convex, oauthCtx) {
1276
1382
  callerOrchestrator: creatorSchema
1277
1383
  .optional()
1278
1384
  .describe("Optional RBAC — if provided, must be creator or assignee"),
1385
+ }, {
1386
+ readOnlyHint: false,
1387
+ openWorldHint: false,
1388
+ destructiveHint: false,
1389
+ title: "Update task",
1279
1390
  }, async ({ taskId, title, description, project, tags, assignedTo, priority, status, dependsOn, missionId, estimatedMinutes, actualMinutes, startedAt, completedAt, dueDate, callerOrchestrator, }) => {
1280
1391
  try {
1281
1392
  if (callerOrchestrator) {
@@ -1330,6 +1441,11 @@ export function registerTools(server, convex, oauthCtx) {
1330
1441
  callerOrchestrator: creatorSchema
1331
1442
  .optional()
1332
1443
  .describe("Optional RBAC — if provided, must be creator or assignee"),
1444
+ }, {
1445
+ readOnlyHint: false,
1446
+ openWorldHint: false,
1447
+ destructiveHint: false,
1448
+ title: "Complete task",
1333
1449
  }, async ({ taskId, completionNote, callerOrchestrator }) => {
1334
1450
  try {
1335
1451
  if (callerOrchestrator) {
@@ -1362,6 +1478,11 @@ export function registerTools(server, convex, oauthCtx) {
1362
1478
  callerOrchestrator: creatorSchema
1363
1479
  .optional()
1364
1480
  .describe("Optional RBAC — if provided, must be creator or assignee"),
1481
+ }, {
1482
+ readOnlyHint: false,
1483
+ openWorldHint: false,
1484
+ destructiveHint: false,
1485
+ title: "Start task",
1365
1486
  }, async ({ taskId, callerOrchestrator }) => {
1366
1487
  try {
1367
1488
  if (callerOrchestrator) {
@@ -1395,6 +1516,11 @@ export function registerTools(server, convex, oauthCtx) {
1395
1516
  .string()
1396
1517
  .optional()
1397
1518
  .describe("Instance identifier, e.g. 'sigma-vps'"),
1519
+ }, {
1520
+ readOnlyHint: false,
1521
+ openWorldHint: false,
1522
+ destructiveHint: false,
1523
+ title: "Checkout task",
1398
1524
  }, async ({ taskId, callerOrchestrator, callerInstance }) => {
1399
1525
  try {
1400
1526
  const fromDenied = guardFrom(callerOrchestrator);
@@ -1424,6 +1550,11 @@ export function registerTools(server, convex, oauthCtx) {
1424
1550
  callerOrchestrator: creatorSchema
1425
1551
  .optional()
1426
1552
  .describe("Optional RBAC — must be creator or system"),
1553
+ }, {
1554
+ readOnlyHint: false,
1555
+ openWorldHint: false,
1556
+ destructiveHint: true,
1557
+ title: "Delete task",
1427
1558
  }, async ({ taskId, callerOrchestrator }) => {
1428
1559
  try {
1429
1560
  if (callerOrchestrator) {
@@ -1459,6 +1590,11 @@ export function registerTools(server, convex, oauthCtx) {
1459
1590
  callerOrchestrator: creatorSchema
1460
1591
  .optional()
1461
1592
  .describe("Optional RBAC — must be creator or assignee"),
1593
+ }, {
1594
+ readOnlyHint: false,
1595
+ openWorldHint: false,
1596
+ destructiveHint: true,
1597
+ title: "Block task",
1462
1598
  }, async ({ taskId, reason, blockedBy, callerOrchestrator }) => {
1463
1599
  try {
1464
1600
  if (callerOrchestrator) {
@@ -1502,6 +1638,11 @@ export function registerTools(server, convex, oauthCtx) {
1502
1638
  callerOrchestrator: creatorSchema
1503
1639
  .optional()
1504
1640
  .describe("Optional RBAC — must be creator or assignee"),
1641
+ }, {
1642
+ readOnlyHint: false,
1643
+ openWorldHint: false,
1644
+ destructiveHint: false,
1645
+ title: "Add task dependency",
1505
1646
  }, async ({ taskId, dependsOn, callerOrchestrator }) => {
1506
1647
  try {
1507
1648
  if (callerOrchestrator) {
@@ -1545,10 +1686,13 @@ export function registerTools(server, convex, oauthCtx) {
1545
1686
  fields: fieldsSchema
1546
1687
  .optional()
1547
1688
  .describe('Field projection ("lite"|"full")'),
1548
- createdBy: assigneeSchema
1549
- .optional()
1550
- .describe("Filter by task creator"),
1689
+ createdBy: assigneeSchema.optional().describe("Filter by task creator"),
1551
1690
  updatedSince: updatedSinceSchema.optional(),
1691
+ }, {
1692
+ readOnlyHint: true,
1693
+ openWorldHint: false,
1694
+ destructiveHint: false,
1695
+ title: "List tasks by mission",
1552
1696
  }, async ({ missionId, status, limit, fields, createdBy, updatedSince }) => {
1553
1697
  try {
1554
1698
  const tasks = await convex.query("tasks:listByMission", {
@@ -1592,6 +1736,11 @@ export function registerTools(server, convex, oauthCtx) {
1592
1736
  .describe("Target completion date (Unix ms)"),
1593
1737
  progress: z.number().optional().describe("Progress percentage (0-100)"),
1594
1738
  createdBy: creatorSchema,
1739
+ }, {
1740
+ readOnlyHint: false,
1741
+ openWorldHint: false,
1742
+ destructiveHint: false,
1743
+ title: "Create mission",
1595
1744
  }, async ({ name, description, project, status, priority, pilot, agents, brief, startDate, targetDate, progress, createdBy, }) => {
1596
1745
  try {
1597
1746
  const fromDenied = guardFrom(createdBy);
@@ -1646,6 +1795,11 @@ export function registerTools(server, convex, oauthCtx) {
1646
1795
  .optional()
1647
1796
  .describe('Field projection ("lite"|"full")'),
1648
1797
  updatedSince: updatedSinceSchema.optional(),
1798
+ }, {
1799
+ readOnlyHint: true,
1800
+ openWorldHint: false,
1801
+ destructiveHint: false,
1802
+ title: "List missions",
1649
1803
  }, async ({ project, pilot, status, limit, fields, updatedSince }) => {
1650
1804
  try {
1651
1805
  const missions = await convex.query("missions:list", {
@@ -1682,6 +1836,11 @@ export function registerTools(server, convex, oauthCtx) {
1682
1836
  // ── get_mission ─────────────────────────────────────────────────────────────
1683
1837
  server.tool("get_mission", "Fetch a single mission by ID. Returns full mission details including status, pilot, agents, progress, and dates.", {
1684
1838
  missionId: z.string().describe("Convex document ID of the mission"),
1839
+ }, {
1840
+ readOnlyHint: true,
1841
+ openWorldHint: false,
1842
+ destructiveHint: false,
1843
+ title: "Get mission",
1685
1844
  }, async ({ missionId }) => {
1686
1845
  try {
1687
1846
  const mission = await convex.query("missions:get", {
@@ -1717,6 +1876,11 @@ export function registerTools(server, convex, oauthCtx) {
1717
1876
  startDate: z.number().optional().describe("New start date (Unix ms)"),
1718
1877
  targetDate: z.number().optional().describe("New target date (Unix ms)"),
1719
1878
  progress: z.number().optional().describe("New progress (0-100)"),
1879
+ }, {
1880
+ readOnlyHint: false,
1881
+ openWorldHint: false,
1882
+ destructiveHint: false,
1883
+ title: "Update mission",
1720
1884
  }, async ({ missionId, name, description, project, status, priority, pilot, agents, brief, startDate, targetDate, progress, }) => {
1721
1885
  try {
1722
1886
  if (pilot) {
@@ -1755,6 +1919,11 @@ export function registerTools(server, convex, oauthCtx) {
1755
1919
  server.tool("update_mission_status", "Change a mission's status. Shortcut for updating only the status field.", {
1756
1920
  missionId: z.string().describe("Convex document ID of the mission"),
1757
1921
  status: missionStatusSchema.describe("New status"),
1922
+ }, {
1923
+ readOnlyHint: false,
1924
+ openWorldHint: false,
1925
+ destructiveHint: false,
1926
+ title: "Update mission status",
1758
1927
  }, async ({ missionId, status }) => {
1759
1928
  try {
1760
1929
  await convex.mutation("missions:updateStatus", {
@@ -1782,6 +1951,11 @@ export function registerTools(server, convex, oauthCtx) {
1782
1951
  content: z.string().describe("Full diary entry content"),
1783
1952
  highlights: flexArrayOptional.describe("Key highlights of the day"),
1784
1953
  blockers: flexArrayOptional.describe("Blockers encountered"),
1954
+ }, {
1955
+ readOnlyHint: false,
1956
+ openWorldHint: false,
1957
+ destructiveHint: false,
1958
+ title: "Write diary entry",
1785
1959
  }, async ({ date, orchestrator, content, highlights, blockers }) => {
1786
1960
  let contentBytes = 0;
1787
1961
  try {
@@ -1821,6 +1995,11 @@ export function registerTools(server, convex, oauthCtx) {
1821
1995
  server.tool("get_diary", "Fetch a diary entry for a specific date and orchestrator. Returns null if no entry exists.", {
1822
1996
  date: z.string().describe("ISO date string — e.g. '2026-03-25'"),
1823
1997
  orchestrator: creatorSchema.describe("Which orchestrator's diary to fetch"),
1998
+ }, {
1999
+ readOnlyHint: true,
2000
+ openWorldHint: false,
2001
+ destructiveHint: false,
2002
+ title: "Get diary entry",
1824
2003
  }, async ({ date, orchestrator }) => {
1825
2004
  try {
1826
2005
  const entry = await convex.query("diary:get", {
@@ -1864,6 +2043,11 @@ export function registerTools(server, convex, oauthCtx) {
1864
2043
  .optional()
1865
2044
  .default(20)
1866
2045
  .describe("Maximum entries to return (default 20)"),
2046
+ }, {
2047
+ readOnlyHint: true,
2048
+ openWorldHint: false,
2049
+ destructiveHint: false,
2050
+ title: "List diary entries",
1867
2051
  }, async ({ orchestrator, limit }) => {
1868
2052
  try {
1869
2053
  const entries = await convex.query("diary:list", {
@@ -1906,6 +2090,11 @@ export function registerTools(server, convex, oauthCtx) {
1906
2090
  "Passing a briefingNotes ID will fail with ArgumentValidationError at path .linkedMemoryIds[N]. " +
1907
2091
  "If cross-linking briefings is needed, request the linkedBriefingIds feature instead."),
1908
2092
  createdBy: creatorSchema,
2093
+ }, {
2094
+ readOnlyHint: false,
2095
+ openWorldHint: false,
2096
+ destructiveHint: false,
2097
+ title: "Create briefing note",
1909
2098
  }, async ({ title, topic, participants, content, decisions, linkedMemoryIds, createdBy, }) => {
1910
2099
  let contentBytes = 0;
1911
2100
  try {
@@ -1945,7 +2134,12 @@ export function registerTools(server, convex, oauthCtx) {
1945
2134
  }
1946
2135
  });
1947
2136
  // ── update_briefing_note ────────────────────────────────────────────────────
1948
- server.tool("update_briefing_note", updateBriefingNoteDescription, updateBriefingNoteSchema.shape, async ({ noteId, callerOrchestrator, title, topic, participants, content, decisions, linkedMemoryIds, }) => {
2137
+ server.tool("update_briefing_note", updateBriefingNoteDescription, updateBriefingNoteSchema.shape, {
2138
+ readOnlyHint: false,
2139
+ openWorldHint: false,
2140
+ destructiveHint: false,
2141
+ title: "Update briefing note",
2142
+ }, async ({ noteId, callerOrchestrator, title, topic, participants, content, decisions, linkedMemoryIds, }) => {
1949
2143
  let contentBytes = 0;
1950
2144
  try {
1951
2145
  if (content !== undefined) {
@@ -2002,6 +2196,11 @@ export function registerTools(server, convex, oauthCtx) {
2002
2196
  .optional()
2003
2197
  .describe('Field projection ("lite"|"full")'),
2004
2198
  updatedSince: updatedSinceSchema.optional(),
2199
+ }, {
2200
+ readOnlyHint: true,
2201
+ openWorldHint: false,
2202
+ destructiveHint: false,
2203
+ title: "List briefing notes",
2005
2204
  }, async ({ topic, limit, fields, updatedSince }) => {
2006
2205
  try {
2007
2206
  const notes = await convex.query("briefingNotes:list", {
@@ -2055,6 +2254,11 @@ export function registerTools(server, convex, oauthCtx) {
2055
2254
  .optional()
2056
2255
  .describe("Project this component belongs to"),
2057
2256
  createdBy: creatorSchema,
2257
+ }, {
2258
+ readOnlyHint: false,
2259
+ openWorldHint: false,
2260
+ destructiveHint: false,
2261
+ title: "Register component",
2058
2262
  }, async ({ name, type, team, content, version, project, createdBy }) => {
2059
2263
  let contentBytes = 0;
2060
2264
  try {
@@ -2105,6 +2309,11 @@ export function registerTools(server, convex, oauthCtx) {
2105
2309
  .optional()
2106
2310
  .default(100)
2107
2311
  .describe("Maximum components to return (default 100)"),
2312
+ }, {
2313
+ readOnlyHint: true,
2314
+ openWorldHint: false,
2315
+ destructiveHint: false,
2316
+ title: "List components",
2108
2317
  }, async ({ type, team, limit }) => {
2109
2318
  try {
2110
2319
  const components = await convex.query("components:list", {
@@ -2129,6 +2338,11 @@ export function registerTools(server, convex, oauthCtx) {
2129
2338
  server.tool("get_component", "Fetch a single component by name and type. Returns the full content.", {
2130
2339
  name: z.string().describe("Component name"),
2131
2340
  type: componentTypeSchema,
2341
+ }, {
2342
+ readOnlyHint: true,
2343
+ openWorldHint: false,
2344
+ destructiveHint: false,
2345
+ title: "Get component",
2132
2346
  }, async ({ name, type }) => {
2133
2347
  try {
2134
2348
  const component = await convex.query("components:get", {
@@ -2156,6 +2370,11 @@ export function registerTools(server, convex, oauthCtx) {
2156
2370
  content: z.string().optional().describe("New content/source code"),
2157
2371
  version: z.string().optional().describe("New version string"),
2158
2372
  project: z.string().optional().describe("New project name"),
2373
+ }, {
2374
+ readOnlyHint: false,
2375
+ openWorldHint: false,
2376
+ destructiveHint: false,
2377
+ title: "Update component",
2159
2378
  }, async ({ componentId, ...fields }) => {
2160
2379
  let contentBytes = 0;
2161
2380
  try {
@@ -2191,6 +2410,11 @@ export function registerTools(server, convex, oauthCtx) {
2191
2410
  componentId: z
2192
2411
  .string()
2193
2412
  .describe("Convex document ID of the component to delete"),
2413
+ }, {
2414
+ readOnlyHint: false,
2415
+ openWorldHint: false,
2416
+ destructiveHint: true,
2417
+ title: "Delete component",
2194
2418
  }, async ({ componentId }) => {
2195
2419
  try {
2196
2420
  const result = await convex.mutation("components:remove", {
@@ -2211,6 +2435,11 @@ export function registerTools(server, convex, oauthCtx) {
2211
2435
  .describe("Search term to match against component name or team"),
2212
2436
  type: componentTypeSchema.optional().describe("Filter by component type"),
2213
2437
  limit: z.number().int().optional().describe("Max results (default 50)"),
2438
+ }, {
2439
+ readOnlyHint: true,
2440
+ openWorldHint: false,
2441
+ destructiveHint: false,
2442
+ title: "Search components",
2214
2443
  }, async ({ query, type, limit }) => {
2215
2444
  try {
2216
2445
  const results = await convex.query("components:search", {
@@ -2243,6 +2472,11 @@ export function registerTools(server, convex, oauthCtx) {
2243
2472
  .string()
2244
2473
  .describe("5-field cron: minute hour day-of-month month day-of-week"),
2245
2474
  createdBy: creatorSchema,
2475
+ }, {
2476
+ readOnlyHint: false,
2477
+ openWorldHint: false,
2478
+ destructiveHint: false,
2479
+ title: "Create recurring task",
2246
2480
  }, async ({ title, description, assignedTo, priority, project, tags, cronExpression, createdBy, }) => {
2247
2481
  try {
2248
2482
  const fromDenied = guardFrom(createdBy);
@@ -2291,6 +2525,11 @@ export function registerTools(server, convex, oauthCtx) {
2291
2525
  .optional()
2292
2526
  .default(50)
2293
2527
  .describe("Max results"),
2528
+ }, {
2529
+ readOnlyHint: true,
2530
+ openWorldHint: false,
2531
+ destructiveHint: false,
2532
+ title: "List recurring tasks",
2294
2533
  }, async ({ assignedTo, active, limit }) => {
2295
2534
  try {
2296
2535
  const tasks = await convex.query("recurringTasks:list", {
@@ -2309,6 +2548,11 @@ export function registerTools(server, convex, oauthCtx) {
2309
2548
  // ── pause_recurring_task ────────────────────────────────────────────────────
2310
2549
  server.tool("pause_recurring_task", "Pause a recurring task — stops auto-creating tasks until resumed.", {
2311
2550
  taskId: z.string().describe("Recurring task ID"),
2551
+ }, {
2552
+ readOnlyHint: false,
2553
+ openWorldHint: false,
2554
+ destructiveHint: false,
2555
+ title: "Pause recurring task",
2312
2556
  }, async ({ taskId }) => {
2313
2557
  try {
2314
2558
  const result = await convex.mutation("recurringTasks:pause", {
@@ -2325,6 +2569,11 @@ export function registerTools(server, convex, oauthCtx) {
2325
2569
  // ── resume_recurring_task ───────────────────────────────────────────────────
2326
2570
  server.tool("resume_recurring_task", "Resume a paused recurring task — recalculates next run time.", {
2327
2571
  taskId: z.string().describe("Recurring task ID"),
2572
+ }, {
2573
+ readOnlyHint: false,
2574
+ openWorldHint: false,
2575
+ destructiveHint: false,
2576
+ title: "Resume recurring task",
2328
2577
  }, async ({ taskId }) => {
2329
2578
  try {
2330
2579
  const result = await convex.mutation("recurringTasks:resume", {
@@ -2341,6 +2590,11 @@ export function registerTools(server, convex, oauthCtx) {
2341
2590
  // ── delete_recurring_task ───────────────────────────────────────────────────
2342
2591
  server.tool("delete_recurring_task", "Permanently delete a recurring task template.", {
2343
2592
  taskId: z.string().describe("Recurring task ID"),
2593
+ }, {
2594
+ readOnlyHint: false,
2595
+ openWorldHint: false,
2596
+ destructiveHint: true,
2597
+ title: "Delete recurring task",
2344
2598
  }, async ({ taskId }) => {
2345
2599
  try {
2346
2600
  const result = await convex.mutation("recurringTasks:remove", {
@@ -2370,6 +2624,11 @@ export function registerTools(server, convex, oauthCtx) {
2370
2624
  .string()
2371
2625
  .optional()
2372
2626
  .describe("New cron expression (5-field)"),
2627
+ }, {
2628
+ readOnlyHint: false,
2629
+ openWorldHint: false,
2630
+ destructiveHint: false,
2631
+ title: "Update recurring task",
2373
2632
  }, async ({ recurringTaskId, ...fields }) => {
2374
2633
  try {
2375
2634
  if (fields.assignedTo) {
@@ -2417,6 +2676,11 @@ export function registerTools(server, convex, oauthCtx) {
2417
2676
  .string()
2418
2677
  .optional()
2419
2678
  .describe("Signed authorization document or reference"),
2679
+ }, {
2680
+ readOnlyHint: false,
2681
+ openWorldHint: false,
2682
+ destructiveHint: false,
2683
+ title: "Create mandate",
2420
2684
  }, async ({ requestedBy, fulfilledBy, service, budget, spendingLimits, approvedCategories, mandateDocument, }) => {
2421
2685
  try {
2422
2686
  const fromDenied = guardFrom(requestedBy);
@@ -2453,6 +2717,11 @@ export function registerTools(server, convex, oauthCtx) {
2453
2717
  .string()
2454
2718
  .describe("Convex document ID of the mandate to accept"),
2455
2719
  callerOrchestrator: creatorSchema.describe("Must be the fulfilledBy orchestrator or system"),
2720
+ }, {
2721
+ readOnlyHint: false,
2722
+ openWorldHint: false,
2723
+ destructiveHint: false,
2724
+ title: "Accept mandate",
2456
2725
  }, async ({ mandateId, callerOrchestrator }) => {
2457
2726
  try {
2458
2727
  const fromDenied = guardFrom(callerOrchestrator);
@@ -2488,6 +2757,11 @@ export function registerTools(server, convex, oauthCtx) {
2488
2757
  .array(z.string())
2489
2758
  .optional()
2490
2759
  .describe("Task IDs created to fulfill this mandate"),
2760
+ }, {
2761
+ readOnlyHint: false,
2762
+ openWorldHint: false,
2763
+ destructiveHint: false,
2764
+ title: "Update mandate",
2491
2765
  }, async ({ mandateId, callerOrchestrator, status, tokensCost, linkedTaskIds, }) => {
2492
2766
  try {
2493
2767
  const fromDenied = guardFrom(callerOrchestrator);
@@ -2521,6 +2795,11 @@ export function registerTools(server, convex, oauthCtx) {
2521
2795
  .describe("Convex document ID of the mandate to settle"),
2522
2796
  callerOrchestrator: creatorSchema.describe("Must be the requestedBy orchestrator or system"),
2523
2797
  finalCost: z.number().describe("Final actual token cost to record"),
2798
+ }, {
2799
+ readOnlyHint: false,
2800
+ openWorldHint: false,
2801
+ destructiveHint: false,
2802
+ title: "Settle mandate",
2524
2803
  }, async ({ mandateId, callerOrchestrator, finalCost }) => {
2525
2804
  try {
2526
2805
  const fromDenied = guardFrom(callerOrchestrator);
@@ -2550,6 +2829,11 @@ export function registerTools(server, convex, oauthCtx) {
2550
2829
  proposedAmount: z
2551
2830
  .number()
2552
2831
  .describe("Proposed token spend amount to validate"),
2832
+ }, {
2833
+ readOnlyHint: true,
2834
+ openWorldHint: false,
2835
+ destructiveHint: false,
2836
+ title: "Validate mandate spending",
2553
2837
  }, async ({ mandateId, proposedAmount }) => {
2554
2838
  try {
2555
2839
  const result = await convex.query("mandates:validateSpending", {
@@ -2584,6 +2868,11 @@ export function registerTools(server, convex, oauthCtx) {
2584
2868
  .optional()
2585
2869
  .default(50)
2586
2870
  .describe("Maximum mandates to return (default 50)"),
2871
+ }, {
2872
+ readOnlyHint: true,
2873
+ openWorldHint: false,
2874
+ destructiveHint: false,
2875
+ title: "List mandates",
2587
2876
  }, async ({ requestedBy, fulfilledBy, status, limit }) => {
2588
2877
  try {
2589
2878
  const mandates = await convex.query("mandates:list", {
@@ -2633,6 +2922,11 @@ export function registerTools(server, convex, oauthCtx) {
2633
2922
  .optional()
2634
2923
  .default(10)
2635
2924
  .describe("Management fee percentage (default 10)"),
2925
+ }, {
2926
+ readOnlyHint: false,
2927
+ openWorldHint: false,
2928
+ destructiveHint: false,
2929
+ title: "Create BU",
2636
2930
  }, async ({ name, description, purpose, domain, orchestratorId, status, businessModel, targetCustomers, services, pricing, revenueProjections, coreTeam, coreProcesses, dependencies, kpis, managementFee, }) => {
2637
2931
  try {
2638
2932
  const fromDenied = guardFrom(orchestratorId);
@@ -2693,6 +2987,11 @@ export function registerTools(server, convex, oauthCtx) {
2693
2987
  dependencies: flexArrayOptional.describe("New dependencies"),
2694
2988
  kpis: flexArrayOptional.describe("New KPIs"),
2695
2989
  managementFee: z.number().optional().describe("New management fee %"),
2990
+ }, {
2991
+ readOnlyHint: false,
2992
+ openWorldHint: false,
2993
+ destructiveHint: false,
2994
+ title: "Update BU",
2696
2995
  }, async ({ buId, name, description, purpose, domain, orchestratorId, status, businessModel, targetCustomers, services, pricing, revenueProjections, coreTeam, coreProcesses, dependencies, kpis, managementFee, }) => {
2697
2996
  try {
2698
2997
  if (orchestratorId) {
@@ -2735,6 +3034,11 @@ export function registerTools(server, convex, oauthCtx) {
2735
3034
  // ── get_bu ──────────────────────────────────────────────────────────────────
2736
3035
  server.tool("get_bu", "Fetch a single business unit by its Convex document ID. Returns null if not found.", {
2737
3036
  buId: z.string().describe("Convex document ID of the business unit"),
3037
+ }, {
3038
+ readOnlyHint: true,
3039
+ openWorldHint: false,
3040
+ destructiveHint: false,
3041
+ title: "Get BU",
2738
3042
  }, async ({ buId }) => {
2739
3043
  try {
2740
3044
  const bu = await convex.query("businessUnits:get", {
@@ -2769,6 +3073,11 @@ export function registerTools(server, convex, oauthCtx) {
2769
3073
  .optional()
2770
3074
  .default(50)
2771
3075
  .describe("Maximum BUs to return (default 50)"),
3076
+ }, {
3077
+ readOnlyHint: true,
3078
+ openWorldHint: false,
3079
+ destructiveHint: false,
3080
+ title: "List BUs",
2772
3081
  }, async ({ orchestratorId, status, limit }) => {
2773
3082
  try {
2774
3083
  const bus = await convex.query("businessUnits:list", {
@@ -2794,6 +3103,11 @@ export function registerTools(server, convex, oauthCtx) {
2794
3103
  buId: z
2795
3104
  .string()
2796
3105
  .describe("Convex document ID of the business unit to delete"),
3106
+ }, {
3107
+ readOnlyHint: false,
3108
+ openWorldHint: false,
3109
+ destructiveHint: true,
3110
+ title: "Delete BU",
2797
3111
  }, async ({ buId }) => {
2798
3112
  try {
2799
3113
  const result = await convex.mutation("businessUnits:remove", {
@@ -2828,6 +3142,11 @@ export function registerTools(server, convex, oauthCtx) {
2828
3142
  .optional()
2829
3143
  .default(true)
2830
3144
  .describe("Whether this mapping is active (default true)"),
3145
+ }, {
3146
+ readOnlyHint: false,
3147
+ openWorldHint: false,
3148
+ destructiveHint: false,
3149
+ title: "Add repo mapping",
2831
3150
  }, async ({ repo, orchestrator, project, active }) => {
2832
3151
  try {
2833
3152
  const id = await convex.mutation("githubRepoMapping:add", {
@@ -2850,7 +3169,12 @@ export function registerTools(server, convex, oauthCtx) {
2850
3169
  }
2851
3170
  });
2852
3171
  // ── list_repo_mappings ──────────────────────────────────────────────────────
2853
- server.tool("list_repo_mappings", "List all GitHub repo → orchestrator mappings. Shows which repos are monitored and which orchestrator handles each.", {}, async () => {
3172
+ server.tool("list_repo_mappings", "List all GitHub repo → orchestrator mappings. Shows which repos are monitored and which orchestrator handles each.", {}, {
3173
+ readOnlyHint: true,
3174
+ openWorldHint: false,
3175
+ destructiveHint: false,
3176
+ title: "List repo mappings",
3177
+ }, async () => {
2854
3178
  try {
2855
3179
  const mappings = await convex.query("githubRepoMapping:list", {});
2856
3180
  return {
@@ -2871,6 +3195,11 @@ export function registerTools(server, convex, oauthCtx) {
2871
3195
  repo: z
2872
3196
  .string()
2873
3197
  .describe("Full repo name to remove — e.g. 'vantageos-agency/vantage-peers'"),
3198
+ }, {
3199
+ readOnlyHint: false,
3200
+ openWorldHint: false,
3201
+ destructiveHint: true,
3202
+ title: "Remove repo mapping",
2874
3203
  }, async ({ repo }) => {
2875
3204
  try {
2876
3205
  const result = await convex.mutation("githubRepoMapping:remove", {
@@ -2911,6 +3240,11 @@ export function registerTools(server, convex, oauthCtx) {
2911
3240
  .optional()
2912
3241
  .default(50)
2913
3242
  .describe("Maximum number of issues to return (default 50)"),
3243
+ }, {
3244
+ readOnlyHint: true,
3245
+ openWorldHint: false,
3246
+ destructiveHint: false,
3247
+ title: "List issues",
2914
3248
  }, async ({ project, status, assignedTo, limit }) => {
2915
3249
  try {
2916
3250
  let results;
@@ -2959,6 +3293,11 @@ export function registerTools(server, convex, oauthCtx) {
2959
3293
  .string()
2960
3294
  .describe("Full repo name — e.g. 'myreeldream-ai/MyShortReel-beta'"),
2961
3295
  issueNumber: z.number().int().describe("GitHub issue number"),
3296
+ }, {
3297
+ readOnlyHint: true,
3298
+ openWorldHint: false,
3299
+ destructiveHint: false,
3300
+ title: "Get issue",
2962
3301
  }, async ({ repo, issueNumber }) => {
2963
3302
  try {
2964
3303
  const issue = await convex.query("issues:getByRepoNumber", {
@@ -2987,6 +3326,11 @@ export function registerTools(server, convex, oauthCtx) {
2987
3326
  status: z
2988
3327
  .enum(["open", "in_progress", "fixed", "verified", "closed"])
2989
3328
  .describe("New status for the issue"),
3329
+ }, {
3330
+ readOnlyHint: false,
3331
+ openWorldHint: false,
3332
+ destructiveHint: false,
3333
+ title: "Update issue status",
2990
3334
  }, async ({ repo, issueNumber, status }) => {
2991
3335
  try {
2992
3336
  await convex.mutation("issues:updateStatus", {
@@ -3017,6 +3361,11 @@ export function registerTools(server, convex, oauthCtx) {
3017
3361
  fixedBy: z
3018
3362
  .string()
3019
3363
  .describe("Who fixed it — orchestrator name or person"),
3364
+ }, {
3365
+ readOnlyHint: false,
3366
+ openWorldHint: false,
3367
+ destructiveHint: false,
3368
+ title: "Link commit to issue",
3020
3369
  }, async ({ repo, issueNumber, commitSha, fixedBy }) => {
3021
3370
  try {
3022
3371
  await convex.mutation("issues:linkCommit", {
@@ -3047,6 +3396,11 @@ export function registerTools(server, convex, oauthCtx) {
3047
3396
  verifiedBy: z
3048
3397
  .string()
3049
3398
  .describe("Who verified the fix — orchestrator name or person"),
3399
+ }, {
3400
+ readOnlyHint: false,
3401
+ openWorldHint: false,
3402
+ destructiveHint: false,
3403
+ title: "Verify issue",
3050
3404
  }, async ({ repo, issueNumber, verifiedBy }) => {
3051
3405
  try {
3052
3406
  await convex.mutation("issues:verify", {
@@ -3073,6 +3427,11 @@ export function registerTools(server, convex, oauthCtx) {
3073
3427
  .string()
3074
3428
  .optional()
3075
3429
  .describe("Filter stats to a specific project — omit for all projects"),
3430
+ }, {
3431
+ readOnlyHint: true,
3432
+ openWorldHint: false,
3433
+ destructiveHint: false,
3434
+ title: "Issue statistics",
3076
3435
  }, async ({ project }) => {
3077
3436
  try {
3078
3437
  const stats = await convex.query("issues:getStats", {
@@ -3112,6 +3471,11 @@ export function registerTools(server, convex, oauthCtx) {
3112
3471
  .describe("The fix that worked — set later if not known yet"),
3113
3472
  files: flexArrayOptional.describe("Files involved in the fix"),
3114
3473
  linkedIssueIds: flexArrayOptional.describe("VantagePeers issue IDs linked to this pattern"),
3474
+ }, {
3475
+ readOnlyHint: false,
3476
+ openWorldHint: false,
3477
+ destructiveHint: false,
3478
+ title: "Create fix pattern",
3115
3479
  }, async ({ symptom, rootCause, tags, stack, sourceProject, createdBy, severity, validatedFix, files, linkedIssueIds, }) => {
3116
3480
  try {
3117
3481
  const fromDenied = guardFrom(createdBy);
@@ -3150,6 +3514,11 @@ export function registerTools(server, convex, oauthCtx) {
3150
3514
  why: z.string().describe("Why it worked or didn't — the reasoning"),
3151
3515
  createdBy: creatorSchema,
3152
3516
  commit: z.string().optional().describe("Git commit hash of this attempt"),
3517
+ }, {
3518
+ readOnlyHint: false,
3519
+ openWorldHint: false,
3520
+ destructiveHint: false,
3521
+ title: "Add fix attempt",
3153
3522
  }, async ({ patternId, description, worked, why, createdBy, commit }) => {
3154
3523
  try {
3155
3524
  const fromDenied = guardFrom(createdBy);
@@ -3180,6 +3549,11 @@ export function registerTools(server, convex, oauthCtx) {
3180
3549
  server.tool("validate_fix", "Set or update the validated fix on a pattern. Use after confirming a fix works.", {
3181
3550
  patternId: z.string().describe("ID of the fix pattern"),
3182
3551
  validatedFix: z.string().describe("Description of the validated fix"),
3552
+ }, {
3553
+ readOnlyHint: false,
3554
+ openWorldHint: false,
3555
+ destructiveHint: false,
3556
+ title: "Validate fix",
3183
3557
  }, async ({ patternId, validatedFix }) => {
3184
3558
  try {
3185
3559
  await convex.mutation("fixPatterns:validate", {
@@ -3209,6 +3583,11 @@ export function registerTools(server, convex, oauthCtx) {
3209
3583
  .int()
3210
3584
  .optional()
3211
3585
  .describe("Max results to return (default 10)"),
3586
+ }, {
3587
+ readOnlyHint: true,
3588
+ openWorldHint: false,
3589
+ destructiveHint: false,
3590
+ title: "Search fix patterns",
3212
3591
  }, async ({ query, limit }) => {
3213
3592
  try {
3214
3593
  const results = await convex.action("search:searchFixPatterns", {
@@ -3235,6 +3614,11 @@ export function registerTools(server, convex, oauthCtx) {
3235
3614
  .optional()
3236
3615
  .describe("Filter by source project — omit for all"),
3237
3616
  limit: z.number().int().optional().describe("Max results (default 50)"),
3617
+ }, {
3618
+ readOnlyHint: true,
3619
+ openWorldHint: false,
3620
+ destructiveHint: false,
3621
+ title: "List fix patterns",
3238
3622
  }, async ({ project, limit }) => {
3239
3623
  try {
3240
3624
  if (project) {
@@ -3263,6 +3647,11 @@ export function registerTools(server, convex, oauthCtx) {
3263
3647
  server.tool("link_issue_to_pattern", "Link a VantagePeers issue to a fix pattern. Creates a bidirectional reference.", {
3264
3648
  patternId: z.string().describe("ID of the fix pattern"),
3265
3649
  issueId: z.string().describe("VantagePeers issue ID to link"),
3650
+ }, {
3651
+ readOnlyHint: false,
3652
+ openWorldHint: false,
3653
+ destructiveHint: false,
3654
+ title: "Link issue to fix pattern",
3266
3655
  }, async ({ patternId, issueId }) => {
3267
3656
  try {
3268
3657
  await convex.mutation("fixPatterns:linkIssue", {
@@ -3286,6 +3675,11 @@ export function registerTools(server, convex, oauthCtx) {
3286
3675
  server.tool("get_mission_template", "Fetch a mission template by name. Returns the template with all steps, or null if not found. " +
3287
3676
  "Use 'issue-resolution-v2' for the default Issue Resolution Protocol.", {
3288
3677
  name: z.string().describe("Template name — e.g. 'issue-resolution-v2'"),
3678
+ }, {
3679
+ readOnlyHint: true,
3680
+ openWorldHint: false,
3681
+ destructiveHint: false,
3682
+ title: "Get mission template",
3289
3683
  }, async ({ name }) => {
3290
3684
  try {
3291
3685
  const template = await convex.query("missionTemplates:getByName", { name });
@@ -3340,6 +3734,11 @@ export function registerTools(server, convex, oauthCtx) {
3340
3734
  .boolean()
3341
3735
  .optional()
3342
3736
  .describe("Mark as the default template for its type"),
3737
+ }, {
3738
+ readOnlyHint: false,
3739
+ openWorldHint: false,
3740
+ destructiveHint: false,
3741
+ title: "Update mission template",
3343
3742
  }, async ({ name, description, steps, createdBy, isDefault }) => {
3344
3743
  try {
3345
3744
  const fromDenied = guardFrom(createdBy);
@@ -3385,7 +3784,12 @@ export function registerTools(server, convex, oauthCtx) {
3385
3784
  .string()
3386
3785
  .optional()
3387
3786
  .describe("Orchestrator making this call — used as createdBy on tasks. Defaults to 'system'."),
3388
- }, async ({ templateName, missionId, context, titlePrefix, callerOrchestrator }) => {
3787
+ }, {
3788
+ readOnlyHint: false,
3789
+ openWorldHint: false,
3790
+ destructiveHint: false,
3791
+ title: "Instantiate mission template",
3792
+ }, async ({ templateName, missionId, context, titlePrefix, callerOrchestrator, }) => {
3389
3793
  try {
3390
3794
  const denied = guardMasterOnly("instantiate_template_into_mission");
3391
3795
  if (denied)
@@ -3429,6 +3833,11 @@ export function registerTools(server, convex, oauthCtx) {
3429
3833
  orchestrator: z
3430
3834
  .string()
3431
3835
  .describe("Orchestrator responsible for this deployment — e.g. 'sigma'"),
3836
+ }, {
3837
+ readOnlyHint: false,
3838
+ openWorldHint: false,
3839
+ destructiveHint: false,
3840
+ title: "Add deployment",
3432
3841
  }, async ({ name, deploymentUrl, deployKeyEnvVar, githubRepo, orchestrator, }) => {
3433
3842
  try {
3434
3843
  const id = await convex.mutation("errorMonitor:addDeployment", {
@@ -3456,6 +3865,11 @@ export function registerTools(server, convex, oauthCtx) {
3456
3865
  name: z
3457
3866
  .string()
3458
3867
  .describe("Name of the deployment to deactivate — e.g. 'your-deployment-123'"),
3868
+ }, {
3869
+ readOnlyHint: false,
3870
+ openWorldHint: false,
3871
+ destructiveHint: true,
3872
+ title: "Remove deployment",
3459
3873
  }, async ({ name }) => {
3460
3874
  try {
3461
3875
  await convex.mutation("errorMonitor:removeDeployment", { name });
@@ -3487,6 +3901,11 @@ export function registerTools(server, convex, oauthCtx) {
3487
3901
  .optional()
3488
3902
  .default(50)
3489
3903
  .describe("Maximum number of errors to return (default 50)"),
3904
+ }, {
3905
+ readOnlyHint: true,
3906
+ openWorldHint: false,
3907
+ destructiveHint: false,
3908
+ title: "List errors",
3490
3909
  }, async ({ deployment, limit }) => {
3491
3910
  try {
3492
3911
  const errors = await convex.query("errorMonitor:listErrors", {
@@ -3509,6 +3928,11 @@ export function registerTools(server, convex, oauthCtx) {
3509
3928
  // ── get_error ───────────────────────────────────────────────────────────────
3510
3929
  server.tool("get_error", "Fetch a single error log entry by its Convex document ID, including stack trace and issue linkage.", {
3511
3930
  errorId: z.string().describe("Convex document ID of the errorLogs entry"),
3931
+ }, {
3932
+ readOnlyHint: true,
3933
+ openWorldHint: false,
3934
+ destructiveHint: false,
3935
+ title: "Get error",
3512
3936
  }, async ({ errorId }) => {
3513
3937
  try {
3514
3938
  const error = await convex.query("errorMonitor:getError", {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vantage-peers-mcp",
3
- "version": "2.4.0",
3
+ "version": "2.4.1",
4
4
  "description": "MCP server for VantagePeers — shared memory, messaging, and task coordination for AI agent teams",
5
5
  "type": "module",
6
6
  "main": "./dist/server.js",