vantage-peers-mcp 2.4.2 → 2.4.6

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
@@ -9,6 +9,8 @@ MCP server for [VantagePeers](https://vantagepeers.com) — shared memory, messa
9
9
 
10
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
+ **Companion plugin — 32 skills (target v2.7.0):** the `vantage-peers` Claude Code plugin wraps these 84 tools with 32 high-level skills split across three phases — Phase A (11 skills, v2.5.0): `dispatch-message`, `dispatch-task-create`, `dispatch-task-complete`, `dispatch-task-start`, `dispatch-subagent`, `identity-set`, `mission-bootstrap`, `check-messages` v5.1, `check-tasks` v2, `daily-start` v3, `close-day` v2 — Phase B (10 skills, v2.6.0): `memory-write`, `briefing-write`, `mission-template-apply`, `task-structure`, `component-register`, `component-discover`, `issue-triage`, `fix-pattern-cycle`, `episode-log`, `recall-deep` — Phase C (11 skills, v2.7.0): `briefing-recall`, `repo-link`, `recurring-schedule`, `deploy-track`, `profile-lookup`, `peers-discovery`, `mandate-lifecycle`, `bu-manage`, `messages-history`, `memory-edit`, `diary-discover`.
13
+
12
14
  ## Quick start
13
15
 
14
16
  ```bash
@@ -400,6 +402,9 @@ All orchestrator names are open strings — any lowercase name is accepted. The
400
402
 
401
403
  ## Changelog
402
404
 
405
+ ### 2.4.3 — 2026-05-31 (Day 89)
406
+ - fix(overflow): defensive byte-cap on all 17 `list_*` tools — `capListResponseBytes` truncates any list response above 60 KB and wraps the result in a `_meta` envelope (`_truncated`, `_showing`, `_total`, `_advice`) so MCP clients (Claude.ai, ChatGPT, Claude Code) never reject a list result for exceeding their token budget. Day 89 Pi 75,003-char `list_tasks` overflow incident reproduced and capped in regression test. PR #565.
407
+
403
408
  ### 2.4.1 — 2026-05-30 (Day 88)
404
409
  - 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
410
  - 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.
@@ -21,6 +21,8 @@ export declare const MAX_CONTENT_BYTES = 900000;
21
21
  * @param toolName Caller tool name (used only in the error message).
22
22
  */
23
23
  export declare function assertContentSize(content: string, toolName: string): number;
24
+ export declare const MAX_LIST_RESPONSE_BYTES = 60000;
25
+ export declare function capListResponseBytes(items: unknown, rawText: string, toolName: string, maxBytes?: number): string;
24
26
  /**
25
27
  * Convex document IDs are 32 lowercase alphanumeric characters (a-z0-9).
26
28
  * Exported so tests can validate the schema independently of the MCP server.
package/dist/src/tools.js CHANGED
@@ -71,6 +71,56 @@ export function assertContentSize(content, toolName) {
71
71
  return contentBytes;
72
72
  }
73
73
  // ─────────────────────────────────────────────────────────────────────────────
74
+ // List response byte cap (overflow protection for MCP clients)
75
+ //
76
+ // MCP clients (Claude.ai, ChatGPT, Claude Code) reject tool results that
77
+ // exceed their token budget — typical ceiling ~25k tokens ≈ 75 KB JSON.
78
+ // When that happens the entire response is lost to a downstream truncation
79
+ // error and the user must fall back to reading the on-disk overflow file.
80
+ //
81
+ // `capListResponseBytes` guards every bulk list_* tool: if the serialized
82
+ // payload exceeds MAX_LIST_RESPONSE_BYTES (60 KB), it truncates the items
83
+ // array (halving until it fits) and wraps the result in a _meta envelope
84
+ // that tells the caller exactly how to refine the query.
85
+ //
86
+ // 60 KB leaves headroom for tool-call JSON framing and the UI stream marker
87
+ // before any MCP client hits its own ceiling. The cap is byte-counted on the
88
+ // raw JSON string, not on item count, because content-heavy rows
89
+ // (memories / diaries / briefing notes) blow past 30 items easily.
90
+ // ─────────────────────────────────────────────────────────────────────────────
91
+ export const MAX_LIST_RESPONSE_BYTES = 60_000;
92
+ export function capListResponseBytes(items, rawText, toolName, maxBytes = MAX_LIST_RESPONSE_BYTES) {
93
+ const byteLen = Buffer.byteLength(rawText, "utf8");
94
+ if (byteLen <= maxBytes)
95
+ return rawText;
96
+ if (!Array.isArray(items) || items.length === 0)
97
+ return rawText;
98
+ let n = items.length;
99
+ let truncated = items;
100
+ let truncatedText = rawText;
101
+ while (n > 1) {
102
+ n = Math.max(1, Math.floor(n / 2));
103
+ truncated = items.slice(0, n);
104
+ truncatedText = JSON.stringify(truncated, null, 2);
105
+ if (Buffer.byteLength(truncatedText, "utf8") <= maxBytes - 600)
106
+ break;
107
+ }
108
+ const envelope = {
109
+ _meta: {
110
+ _truncated: true,
111
+ _showing: truncated.length,
112
+ _total: items.length,
113
+ _bytesOriginal: byteLen,
114
+ _bytesCap: maxBytes,
115
+ _tool: toolName,
116
+ _advice: `Response exceeded ${maxBytes} bytes. Showing first ${truncated.length}/${items.length}. ` +
117
+ `Pass fields="lite", a smaller limit, stricter filters (status, assignedTo, project, namespace, updatedSince), or paginate.`,
118
+ },
119
+ items: truncated,
120
+ };
121
+ return JSON.stringify(envelope, null, 2);
122
+ }
123
+ // ─────────────────────────────────────────────────────────────────────────────
74
124
  // Shared Zod schemas
75
125
  // ─────────────────────────────────────────────────────────────────────────────
76
126
  /**
@@ -815,6 +865,9 @@ export function registerTools(server, convex, oauthCtx) {
815
865
  type: memoryTypeSchema
816
866
  .optional()
817
867
  .describe("Filter to a specific type — omit to return all types"),
868
+ createdBy: assigneeSchema
869
+ .optional()
870
+ .describe("Filter by creator/orchestrator role — mirrors list_tasks pattern for cross-tool consistency."),
818
871
  limit: z
819
872
  .number()
820
873
  .int()
@@ -828,7 +881,7 @@ export function registerTools(server, convex, oauthCtx) {
828
881
  openWorldHint: false,
829
882
  destructiveHint: false,
830
883
  title: "List memories",
831
- }, async ({ namespace, type, limit }) => {
884
+ }, async ({ namespace, type, createdBy, limit }) => {
832
885
  try {
833
886
  const nsDenied = guardRead(namespace);
834
887
  if (nsDenied)
@@ -836,6 +889,7 @@ export function registerTools(server, convex, oauthCtx) {
836
889
  const memories = await convex.query("memories:listMemories", {
837
890
  namespace,
838
891
  type,
892
+ createdBy,
839
893
  limit: limit ?? 20,
840
894
  });
841
895
  const rawList = Array.isArray(memories)
@@ -843,7 +897,7 @@ export function registerTools(server, convex, oauthCtx) {
843
897
  : Array.isArray(memories?.page)
844
898
  ? memories.page
845
899
  : [];
846
- const baseText = JSON.stringify(memories, null, 2);
900
+ const baseText = capListResponseBytes(memories, JSON.stringify(memories, null, 2), "list_memories");
847
901
  const text = appendMarkerIfEnabled(baseText, () => ({
848
902
  kind: "memory-quote",
849
903
  items: rawList.map((m) => ({
@@ -1136,7 +1190,7 @@ export function registerTools(server, convex, oauthCtx) {
1136
1190
  content: [
1137
1191
  {
1138
1192
  type: "text",
1139
- text: JSON.stringify(peers, null, 2),
1193
+ text: capListResponseBytes(peers, JSON.stringify(peers, null, 2), "list_peers"),
1140
1194
  },
1141
1195
  ],
1142
1196
  };
@@ -1176,7 +1230,7 @@ export function registerTools(server, convex, oauthCtx) {
1176
1230
  from,
1177
1231
  limit: limit ?? 100,
1178
1232
  });
1179
- const baseText = JSON.stringify(messages, null, 2);
1233
+ const baseText = capListResponseBytes(messages, JSON.stringify(messages, null, 2), "list_messages");
1180
1234
  const text = appendMarkerIfEnabled(baseText, () => ({
1181
1235
  kind: "messages-feed",
1182
1236
  items: Array.isArray(messages)
@@ -1219,7 +1273,7 @@ export function registerTools(server, convex, oauthCtx) {
1219
1273
  content: [
1220
1274
  {
1221
1275
  type: "text",
1222
- text: JSON.stringify(status, null, 2),
1276
+ text: capListResponseBytes(status, JSON.stringify(status, null, 2), "list_broadcast_status"),
1223
1277
  },
1224
1278
  ],
1225
1279
  };
@@ -1356,7 +1410,7 @@ export function registerTools(server, convex, oauthCtx) {
1356
1410
  createdBy,
1357
1411
  updatedSince,
1358
1412
  });
1359
- const baseText = JSON.stringify(tasks, null, 2);
1413
+ const baseText = capListResponseBytes(tasks, JSON.stringify(tasks, null, 2), "list_tasks");
1360
1414
  const text = appendMarkerIfEnabled(baseText, () => ({
1361
1415
  kind: "tasks-table",
1362
1416
  items: Array.isArray(tasks)
@@ -1742,7 +1796,7 @@ export function registerTools(server, convex, oauthCtx) {
1742
1796
  content: [
1743
1797
  {
1744
1798
  type: "text",
1745
- text: JSON.stringify(tasks, null, 2),
1799
+ text: capListResponseBytes(tasks, JSON.stringify(tasks, null, 2), "list_tasks_by_mission"),
1746
1800
  },
1747
1801
  ],
1748
1802
  };
@@ -1852,7 +1906,7 @@ export function registerTools(server, convex, oauthCtx) {
1852
1906
  fields,
1853
1907
  updatedSince,
1854
1908
  });
1855
- const baseText = JSON.stringify(missions, null, 2);
1909
+ const baseText = capListResponseBytes(missions, JSON.stringify(missions, null, 2), "list_missions");
1856
1910
  const text = appendMarkerIfEnabled(baseText, () => ({
1857
1911
  kind: "mission-timeline",
1858
1912
  items: Array.isArray(missions)
@@ -2083,6 +2137,9 @@ export function registerTools(server, convex, oauthCtx) {
2083
2137
  orchestrator: creatorSchema
2084
2138
  .optional()
2085
2139
  .describe("Filter to a specific orchestrator — omit for all"),
2140
+ createdBy: assigneeSchema
2141
+ .optional()
2142
+ .describe("Filter by creator/orchestrator role — alias of `orchestrator` for cross-tool consistency (mirrors list_tasks pattern). If both are passed, `createdBy` wins."),
2086
2143
  limit: z
2087
2144
  .number()
2088
2145
  .int()
@@ -2096,23 +2153,25 @@ export function registerTools(server, convex, oauthCtx) {
2096
2153
  openWorldHint: false,
2097
2154
  destructiveHint: false,
2098
2155
  title: "List diary entries",
2099
- }, async ({ orchestrator, limit }) => {
2156
+ }, async ({ orchestrator, createdBy, limit }) => {
2100
2157
  try {
2158
+ // createdBy is an alias of orchestrator (diary's author field). If both set, createdBy wins.
2159
+ const effectiveOrchestrator = createdBy ?? orchestrator;
2101
2160
  // Non-master: must scope to own orchestrator id.
2102
2161
  if (oauthCtx && !isMasterScope(oauthCtx)) {
2103
- if (orchestrator !== oauthCtx.userId) {
2162
+ if (effectiveOrchestrator !== oauthCtx.userId) {
2104
2163
  return mcpError(`Forbidden: list_diaries requires orchestrator='${oauthCtx.userId}' for non-master scope (current: ${oauthCtx.scopeProfile}).`);
2105
2164
  }
2106
2165
  }
2107
2166
  const entries = await convex.query("diary:list", {
2108
- orchestrator,
2167
+ orchestrator: effectiveOrchestrator,
2109
2168
  limit: limit ?? 20,
2110
2169
  });
2111
2170
  return {
2112
2171
  content: [
2113
2172
  {
2114
2173
  type: "text",
2115
- text: JSON.stringify(entries, null, 2),
2174
+ text: capListResponseBytes(entries, JSON.stringify(entries, null, 2), "list_diaries"),
2116
2175
  },
2117
2176
  ],
2118
2177
  };
@@ -2266,7 +2325,7 @@ export function registerTools(server, convex, oauthCtx) {
2266
2325
  fields,
2267
2326
  updatedSince,
2268
2327
  });
2269
- const baseText = JSON.stringify(notes, null, 2);
2328
+ const baseText = capListResponseBytes(notes, JSON.stringify(notes, null, 2), "list_briefing_notes");
2270
2329
  const text = appendMarkerIfEnabled(baseText, () => {
2271
2330
  const items = Array.isArray(notes) ? notes : [];
2272
2331
  if (items.length === 0)
@@ -2385,7 +2444,7 @@ export function registerTools(server, convex, oauthCtx) {
2385
2444
  content: [
2386
2445
  {
2387
2446
  type: "text",
2388
- text: JSON.stringify(components, null, 2),
2447
+ text: capListResponseBytes(components, JSON.stringify(components, null, 2), "list_components"),
2389
2448
  },
2390
2449
  ],
2391
2450
  };
@@ -2607,7 +2666,7 @@ export function registerTools(server, convex, oauthCtx) {
2607
2666
  limit: limit ?? 50,
2608
2667
  });
2609
2668
  return {
2610
- content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }],
2669
+ content: [{ type: "text", text: capListResponseBytes(tasks, JSON.stringify(tasks, null, 2), "list_recurring_tasks") }],
2611
2670
  };
2612
2671
  }
2613
2672
  catch (error) {
@@ -2957,7 +3016,7 @@ export function registerTools(server, convex, oauthCtx) {
2957
3016
  content: [
2958
3017
  {
2959
3018
  type: "text",
2960
- text: JSON.stringify(mandates, null, 2),
3019
+ text: capListResponseBytes(mandates, JSON.stringify(mandates, null, 2), "list_mandates"),
2961
3020
  },
2962
3021
  ],
2963
3022
  };
@@ -3167,7 +3226,7 @@ export function registerTools(server, convex, oauthCtx) {
3167
3226
  content: [
3168
3227
  {
3169
3228
  type: "text",
3170
- text: JSON.stringify(bus, null, 2),
3229
+ text: capListResponseBytes(bus, JSON.stringify(bus, null, 2), "list_bus"),
3171
3230
  },
3172
3231
  ],
3173
3232
  };
@@ -3262,7 +3321,7 @@ export function registerTools(server, convex, oauthCtx) {
3262
3321
  content: [
3263
3322
  {
3264
3323
  type: "text",
3265
- text: JSON.stringify(mappings, null, 2),
3324
+ text: capListResponseBytes(mappings, JSON.stringify(mappings, null, 2), "list_repo_mappings"),
3266
3325
  },
3267
3326
  ],
3268
3327
  };
@@ -3362,7 +3421,7 @@ export function registerTools(server, convex, oauthCtx) {
3362
3421
  content: [
3363
3422
  {
3364
3423
  type: "text",
3365
- text: JSON.stringify({ count: results.length, issues: results }, null, 2),
3424
+ text: capListResponseBytes(results, JSON.stringify({ count: results.length, issues: results }, null, 2), "list_issues"),
3366
3425
  },
3367
3426
  ],
3368
3427
  };
@@ -3723,7 +3782,7 @@ export function registerTools(server, convex, oauthCtx) {
3723
3782
  limit,
3724
3783
  });
3725
3784
  return {
3726
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
3785
+ content: [{ type: "text", text: capListResponseBytes(results, JSON.stringify(results, null, 2), "list_fix_patterns") }],
3727
3786
  };
3728
3787
  }
3729
3788
  const allResults = await convex.query("fixPatterns:listAll", {
@@ -3731,7 +3790,7 @@ export function registerTools(server, convex, oauthCtx) {
3731
3790
  });
3732
3791
  return {
3733
3792
  content: [
3734
- { type: "text", text: JSON.stringify(allResults, null, 2) },
3793
+ { type: "text", text: capListResponseBytes(allResults, JSON.stringify(allResults, null, 2), "list_fix_patterns") },
3735
3794
  ],
3736
3795
  };
3737
3796
  }
@@ -4018,7 +4077,7 @@ export function registerTools(server, convex, oauthCtx) {
4018
4077
  content: [
4019
4078
  {
4020
4079
  type: "text",
4021
- text: JSON.stringify(errors, null, 2),
4080
+ text: capListResponseBytes(errors, JSON.stringify(errors, null, 2), "list_errors"),
4022
4081
  },
4023
4082
  ],
4024
4083
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vantage-peers-mcp",
3
- "version": "2.4.2",
3
+ "version": "2.4.6",
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",