vantage-peers-mcp 2.4.2 → 2.4.3

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
@@ -400,6 +400,9 @@ All orchestrator names are open strings — any lowercase name is accepted. The
400
400
 
401
401
  ## Changelog
402
402
 
403
+ ### 2.4.3 — 2026-05-31 (Day 89)
404
+ - 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.
405
+
403
406
  ### 2.4.1 — 2026-05-30 (Day 88)
404
407
  - 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
408
  - 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
  /**
@@ -843,7 +893,7 @@ export function registerTools(server, convex, oauthCtx) {
843
893
  : Array.isArray(memories?.page)
844
894
  ? memories.page
845
895
  : [];
846
- const baseText = JSON.stringify(memories, null, 2);
896
+ const baseText = capListResponseBytes(memories, JSON.stringify(memories, null, 2), "list_memories");
847
897
  const text = appendMarkerIfEnabled(baseText, () => ({
848
898
  kind: "memory-quote",
849
899
  items: rawList.map((m) => ({
@@ -1136,7 +1186,7 @@ export function registerTools(server, convex, oauthCtx) {
1136
1186
  content: [
1137
1187
  {
1138
1188
  type: "text",
1139
- text: JSON.stringify(peers, null, 2),
1189
+ text: capListResponseBytes(peers, JSON.stringify(peers, null, 2), "list_peers"),
1140
1190
  },
1141
1191
  ],
1142
1192
  };
@@ -1176,7 +1226,7 @@ export function registerTools(server, convex, oauthCtx) {
1176
1226
  from,
1177
1227
  limit: limit ?? 100,
1178
1228
  });
1179
- const baseText = JSON.stringify(messages, null, 2);
1229
+ const baseText = capListResponseBytes(messages, JSON.stringify(messages, null, 2), "list_messages");
1180
1230
  const text = appendMarkerIfEnabled(baseText, () => ({
1181
1231
  kind: "messages-feed",
1182
1232
  items: Array.isArray(messages)
@@ -1219,7 +1269,7 @@ export function registerTools(server, convex, oauthCtx) {
1219
1269
  content: [
1220
1270
  {
1221
1271
  type: "text",
1222
- text: JSON.stringify(status, null, 2),
1272
+ text: capListResponseBytes(status, JSON.stringify(status, null, 2), "list_broadcast_status"),
1223
1273
  },
1224
1274
  ],
1225
1275
  };
@@ -1356,7 +1406,7 @@ export function registerTools(server, convex, oauthCtx) {
1356
1406
  createdBy,
1357
1407
  updatedSince,
1358
1408
  });
1359
- const baseText = JSON.stringify(tasks, null, 2);
1409
+ const baseText = capListResponseBytes(tasks, JSON.stringify(tasks, null, 2), "list_tasks");
1360
1410
  const text = appendMarkerIfEnabled(baseText, () => ({
1361
1411
  kind: "tasks-table",
1362
1412
  items: Array.isArray(tasks)
@@ -1742,7 +1792,7 @@ export function registerTools(server, convex, oauthCtx) {
1742
1792
  content: [
1743
1793
  {
1744
1794
  type: "text",
1745
- text: JSON.stringify(tasks, null, 2),
1795
+ text: capListResponseBytes(tasks, JSON.stringify(tasks, null, 2), "list_tasks_by_mission"),
1746
1796
  },
1747
1797
  ],
1748
1798
  };
@@ -1852,7 +1902,7 @@ export function registerTools(server, convex, oauthCtx) {
1852
1902
  fields,
1853
1903
  updatedSince,
1854
1904
  });
1855
- const baseText = JSON.stringify(missions, null, 2);
1905
+ const baseText = capListResponseBytes(missions, JSON.stringify(missions, null, 2), "list_missions");
1856
1906
  const text = appendMarkerIfEnabled(baseText, () => ({
1857
1907
  kind: "mission-timeline",
1858
1908
  items: Array.isArray(missions)
@@ -2112,7 +2162,7 @@ export function registerTools(server, convex, oauthCtx) {
2112
2162
  content: [
2113
2163
  {
2114
2164
  type: "text",
2115
- text: JSON.stringify(entries, null, 2),
2165
+ text: capListResponseBytes(entries, JSON.stringify(entries, null, 2), "list_diaries"),
2116
2166
  },
2117
2167
  ],
2118
2168
  };
@@ -2266,7 +2316,7 @@ export function registerTools(server, convex, oauthCtx) {
2266
2316
  fields,
2267
2317
  updatedSince,
2268
2318
  });
2269
- const baseText = JSON.stringify(notes, null, 2);
2319
+ const baseText = capListResponseBytes(notes, JSON.stringify(notes, null, 2), "list_briefing_notes");
2270
2320
  const text = appendMarkerIfEnabled(baseText, () => {
2271
2321
  const items = Array.isArray(notes) ? notes : [];
2272
2322
  if (items.length === 0)
@@ -2385,7 +2435,7 @@ export function registerTools(server, convex, oauthCtx) {
2385
2435
  content: [
2386
2436
  {
2387
2437
  type: "text",
2388
- text: JSON.stringify(components, null, 2),
2438
+ text: capListResponseBytes(components, JSON.stringify(components, null, 2), "list_components"),
2389
2439
  },
2390
2440
  ],
2391
2441
  };
@@ -2607,7 +2657,7 @@ export function registerTools(server, convex, oauthCtx) {
2607
2657
  limit: limit ?? 50,
2608
2658
  });
2609
2659
  return {
2610
- content: [{ type: "text", text: JSON.stringify(tasks, null, 2) }],
2660
+ content: [{ type: "text", text: capListResponseBytes(tasks, JSON.stringify(tasks, null, 2), "list_recurring_tasks") }],
2611
2661
  };
2612
2662
  }
2613
2663
  catch (error) {
@@ -2957,7 +3007,7 @@ export function registerTools(server, convex, oauthCtx) {
2957
3007
  content: [
2958
3008
  {
2959
3009
  type: "text",
2960
- text: JSON.stringify(mandates, null, 2),
3010
+ text: capListResponseBytes(mandates, JSON.stringify(mandates, null, 2), "list_mandates"),
2961
3011
  },
2962
3012
  ],
2963
3013
  };
@@ -3167,7 +3217,7 @@ export function registerTools(server, convex, oauthCtx) {
3167
3217
  content: [
3168
3218
  {
3169
3219
  type: "text",
3170
- text: JSON.stringify(bus, null, 2),
3220
+ text: capListResponseBytes(bus, JSON.stringify(bus, null, 2), "list_bus"),
3171
3221
  },
3172
3222
  ],
3173
3223
  };
@@ -3262,7 +3312,7 @@ export function registerTools(server, convex, oauthCtx) {
3262
3312
  content: [
3263
3313
  {
3264
3314
  type: "text",
3265
- text: JSON.stringify(mappings, null, 2),
3315
+ text: capListResponseBytes(mappings, JSON.stringify(mappings, null, 2), "list_repo_mappings"),
3266
3316
  },
3267
3317
  ],
3268
3318
  };
@@ -3362,7 +3412,7 @@ export function registerTools(server, convex, oauthCtx) {
3362
3412
  content: [
3363
3413
  {
3364
3414
  type: "text",
3365
- text: JSON.stringify({ count: results.length, issues: results }, null, 2),
3415
+ text: capListResponseBytes(results, JSON.stringify({ count: results.length, issues: results }, null, 2), "list_issues"),
3366
3416
  },
3367
3417
  ],
3368
3418
  };
@@ -3723,7 +3773,7 @@ export function registerTools(server, convex, oauthCtx) {
3723
3773
  limit,
3724
3774
  });
3725
3775
  return {
3726
- content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
3776
+ content: [{ type: "text", text: capListResponseBytes(results, JSON.stringify(results, null, 2), "list_fix_patterns") }],
3727
3777
  };
3728
3778
  }
3729
3779
  const allResults = await convex.query("fixPatterns:listAll", {
@@ -3731,7 +3781,7 @@ export function registerTools(server, convex, oauthCtx) {
3731
3781
  });
3732
3782
  return {
3733
3783
  content: [
3734
- { type: "text", text: JSON.stringify(allResults, null, 2) },
3784
+ { type: "text", text: capListResponseBytes(allResults, JSON.stringify(allResults, null, 2), "list_fix_patterns") },
3735
3785
  ],
3736
3786
  };
3737
3787
  }
@@ -4018,7 +4068,7 @@ export function registerTools(server, convex, oauthCtx) {
4018
4068
  content: [
4019
4069
  {
4020
4070
  type: "text",
4021
- text: JSON.stringify(errors, null, 2),
4071
+ text: capListResponseBytes(errors, JSON.stringify(errors, null, 2), "list_errors"),
4022
4072
  },
4023
4073
  ],
4024
4074
  };
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.3",
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",