【Bug已解决】How to handle token limit when processing large JSON response with MCP client-server? 解决方案

📅 2026/8/21 20:11:24
【Bug已解决】How to handle token limit when processing large JSON response with MCP client-server? 解决方案
【Bug已解决】How to handle token limit when processing large JSON response with MCP client-server? 解决方案一、现象长什么样你用 MCP 的 client-server 架构某个 tool 返回了一个很大的 JSON比如上千行记录、一份长配置结果工具结果塞进对话后直接触发 token 超限后续请求被拒或截断Claude 处理这个大 JSON 时变慢、甚至把上下文窗口挤爆导致输入过长你不想丢掉数据但又不能把整份 JSON 全塞进toolResultMCP 没有内建的大响应分页默认是整个结果一次回传你尝试截断 JSON但截断破坏了结构模型解析失败。一句话MCP tool 返回超大 JSON 时若原样塞进toolResult或 messages会撑爆 token 预算——需要在 server 侧做裁剪/分页/摘要/外置存储只把模型真正需要的部分送进上下文。二、背景MCP 的工具结果最终会变成一条消息进入 LLM 上下文。LLM 上下文是有限且昂贵的。一个动辄几万 token 的 JSON 如果原样回传直接吃掉上下文窗口挤掉 system/history/其他工具结果让每次请求都更贵、更慢模型其实只需要其中一小部分来回答用户问题。正确思路不是把大 JSON 硬塞而是在 server 侧把它变成模型可消化的大小分页取前 N 条、按查询过滤、生成摘要、或把完整数据写到文件/外部存储、只回传路径 摘要。三、根因根因是server 把超大原始 JSON 直接作为工具结果回传未做任何尺寸治理# 错误整份大 JSON 直接回传 app.tool() def query_all_orders(): rows db.fetch_all() # 10 万行 return json.dumps(rows) # 直接进上下文 - token 爆炸# 正确server 侧裁剪/分页/摘要 app.tool() def query_orders(limit20, keywordNone): rows db.fetch_filtered(keyword, limitlimit) return json.dumps({ total_matched: db.count(keyword), returned: len(rows), sample: rows, # 只回前 N 条 })四、最小可运行复现下面用 Python 模拟大 JSON 裁剪后回传import json from dataclasses import dataclass dataclass class _OrderTool: def fetch_all(self) - list: return [{id: i, amount: i * 10} for i in range(100_000)] # 巨大 def tool_result(self, keywordNone, limit: int 20) - str: rows self.fetch_all() if keyword: rows [r for r in rows if keyword in str(r)] total len(rows) sample rows[:limit] # 只回传总数 样本而非全量 return json.dumps({ total_matched: total, returned: len(sample), note: 仅返回前 %d 条样本完整数据请分页获取 % limit, sample: sample, }, ensure_asciiFalse) def main(): tool _OrderTool() print(len(tool.tool_result())) # 远小于全量 print(tool.tool_result(keyword5, limit5)) # 带过滤 if __name__ __main__: main()运行后工具结果从十万行降到总数样本token 量可控。五、解决方案第一层最小直接修复最小修复是server 侧给工具加limit/keyword参数只回传必要部分mcp.tool() def search_orders(keyword: str , limit: int 20) - str: rows db.query(keywordkeyword) total len(rows) sample rows[:limit] return json.dumps({ total_matched: total, returned: len(sample), sample: sample, }, ensure_asciiFalse)若数据必须完整保留改为外置存储把完整 JSON 写文件只回传路径与摘要import tempfile, os mcp.tool() def export_large_report() - str: data generate_huge_report() # 大 JSON path tempfile.mktemp(suffix.json) with open(path, w) as f: json.dump(data, f, ensure_asciiFalse) # 只回传摘要 路径 return json.dumps({ summary: f报告含 {len(data)} 条记录, saved_to: path, hint: 需要具体内容请用 read_file 工具读取该路径, }, ensure_asciiFalse)六、解决方案第二层结构化改进把大响应治理做成策略集中决定裁剪/分页/外置from dataclasses import dataclass, field import json import tempfile from pathlib import Path from typing import Any, Dict, List dataclass(frozenTrue) class McpLargeJsonPolicy: MCP 大 JSON 响应策略尺寸治理保护 token 预算。 规则 - 超过阈值则自动裁剪为 (总数, 样本, 提示) - 或外置存储只回传路径摘要 - 绝不允许原始全量直接进上下文 token_threshold: int 2000 # 超过则治理 sample_limit: int 20 def chars_of(self, obj: Any) - int: return len(json.dumps(obj, ensure_asciiFalse)) def respond(self, data: Any, *, external_dir: str None) - str: if self.chars_of(data) self.token_threshold * 4: return json.dumps(data, ensure_asciiFalse) # 小直接回 # 大裁剪 if isinstance(data, list): total len(data) sample data[: self.sample_limit] payload {total: total, returned: len(sample), sample: sample, note: 已裁剪} else: payload {note: 对象过大已裁剪, keys: list(data.keys())} # 可选外置完整数据 if external_dir: p Path(external_dir) / large_result.json p.write_text(json.dumps(data, ensure_asciiFalse)) payload[saved_to] str(p) return json.dumps(payload, ensure_asciiFalse) def demo() - None: policy McpLargeJsonPolicy() big [{id: i} for i in range(100_000)] out policy.respond(big, external_dir/tmp) assert total in json.loads(out) print(大 JSON 治理 OK:, len(out), 字符) if __name__ __main__: demo()七、解决方案第三层断言 / CI 守护import json import pytest from your_module import McpLargeJsonPolicy def test_small_passthrough(): policy McpLargeJsonPolicy() data [{id: 1}] out json.loads(policy.respond(data)) assert out [{id: 1}] def test_large_truncated(): policy McpLargeJsonPolicy(token_threshold1) # 极小阈值触发治理 big [{id: i} for i in range(100)] out json.loads(policy.respond(big)) assert total in out and sample in out assert out[total] 100 assert len(out[sample]) policy.sample_limit def test_external_saved(): policy McpLargeJsonPolicy(token_threshold1) big [{id: i} for i in range(50)] out json.loads(policy.respond(big, external_dir/tmp)) assert saved_to in out def test_list_type(): policy McpLargeJsonPolicy(token_threshold1) out json.loads(policy.respond([1, 2, 3])) assert out[total] 3 def test_dict_type(): policy McpLargeJsonPolicy(token_threshold1) out json.loads(policy.respond({a: 1, b: 2})) assert keys in out def test_threshold_respected(): policy McpLargeJsonPolicy(token_threshold100000) small [{id: 1}] out json.loads(policy.respond(small)) assert out [{id: 1}] # 未触发治理CI 里加一条对所有 MCP tool 返回断言若超过 token 阈值则已治理含 total/样本/或外置路径避免大 JSON 撑爆上下文。八、排查清单tool 返回是否原样塞了整份大 JSON那必爆 token。是否给工具加了limit/keyword参数做服务端过滤只回必要部分。是否用总数样本提示替代全量模型通常只需样本。是否考虑外置存储写文件只回路径摘要MCP 是否支持分页让客户端分批拉。是否用count_tokens_approximately预估返回尺寸超阈值即治理九、小结MCP client-server 处理大 JSON 响应触发 token 超限根因是 server 把超大原始 JSON 直接作为工具结果回传撑爆上下文。最小修复是 server 侧加limit/keyword只回样本、或外置存储只回路径摘要结构化做法是抽成McpLargeJsonPolicy按 token 阈值自动裁剪/外置最后用 pytest 守护超阈值必治理保护 LLM 的 token 预算。