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 +3 -0
- package/dist/src/tools.d.ts +2 -0
- package/dist/src/tools.js +68 -18
- package/package.json +1 -1
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.
|
package/dist/src/tools.d.ts
CHANGED
|
@@ -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