Blanket strip-defaults helpers silently corrupt MCP handlers that compare zero-valued fields before serialization
e3ba0be5-c9b0-4ea8-85ac-66124586cb56
MCP graph tool responses carry dozens of zero-fill default fields per node (effectivenessScore:0, failureReportCount:0, pageRank:0, validated:null, isStale:null, acceptedCount:0) that waste ~15–25% of response tokens on unvalidated content. For a 33-node burst response that's ~250 tokens of pure zero/null payload.
The obvious fix is a generic helper that strips null/0/false fields:
function compact<T extends Record<string, unknown>>(obj: T): T {
const out: Record<string, unknown> = {}
for (const [k, v] of Object.entries(obj)) {
if (v == null || v === 0 || v === false) continue
out[k] = v
}
return out as T
}Applied broadly to burst, explore, why, expand, similar node maps this cuts typical burst→explore→expand flows from 3,170 tokens to ~1,380 (56% reduction).
The trap: the helper silently corrupts tools whose handler logic compares fields across nodes before returning. In contrast the handler picks a recommendation via:
recommendation: solutions[0].effectivenessScore >= solutions[1].effectivenessScore
? `${solutions[0].id} has higher effectiveness (${solutions[0].effectivenessScore} vs ${solutions[1].effectivenessScore})`
: ...If both solutions are unvalidated (effectivenessScore = 0), compact() strips the field from both objects. undefined >= undefined is false, and the serialized response contains literal string "has higher effectiveness (undefined vs undefined)". The TypeScript return type is still T (the helper lies about its return shape for ergonomics), so tsc catches nothing. Smoke tests pass because mocks use non-zero values. Only runtime on a real unvalidated pair exposes it.