news 2026/8/21 20:11:14

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

作者头像

张小明

前端开发工程师

1.2k 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 全塞进toolResult
  • MCP 没有内建的"大响应分页",默认是整个结果一次回传;
  • 你尝试截断 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(limit=20, keyword=None): rows = db.fetch_filtered(keyword, limit=limit) 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, keyword=None, 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_ascii=False) def main(): tool = _OrderTool() print(len(tool.tool_result())) # 远小于全量 print(tool.tool_result(keyword="5", limit=5)) # 带过滤 if __name__ == "__main__": main()

运行后工具结果从"十万行"降到"总数+样本",token 量可控。

五、解决方案(第一层:最小直接修复)

最小修复是server 侧给工具加limit/keyword参数,只回传必要部分

@mcp.tool() def search_orders(keyword: str = "", limit: int = 20) -> str: rows = db.query(keyword=keyword) total = len(rows) sample = rows[:limit] return json.dumps({ "total_matched": total, "returned": len(sample), "sample": sample, }, ensure_ascii=False)

若数据必须完整保留,改为外置存储:把完整 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_ascii=False) # 只回传摘要 + 路径 return json.dumps({ "summary": f"报告含 {len(data)} 条记录", "saved_to": path, "hint": "需要具体内容请用 read_file 工具读取该路径", }, ensure_ascii=False)

六、解决方案(第二层:结构化改进)

把"大响应治理"做成策略,集中决定裁剪/分页/外置:

from dataclasses import dataclass, field import json import tempfile from pathlib import Path from typing import Any, Dict, List @dataclass(frozen=True) 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_ascii=False)) def respond(self, data: Any, *, external_dir: str = None) -> str: if self.chars_of(data) <= self.token_threshold * 4: return json.dumps(data, ensure_ascii=False) # 小,直接回 # 大:裁剪 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_ascii=False)) payload["saved_to"] = str(p) return json.dumps(payload, ensure_ascii=False) 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_threshold=1) # 极小阈值触发治理 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_threshold=1) 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_threshold=1) out = json.loads(policy.respond([1, 2, 3])) assert out["total"] == 3 def test_dict_type(): policy = McpLargeJsonPolicy(token_threshold=1) out = json.loads(policy.respond({"a": 1, "b": 2})) assert "keys" in out def test_threshold_respected(): policy = McpLargeJsonPolicy(token_threshold=100000) 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 预算。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/21 20:09:30

Windows输入法删除工具 使用教程:强制删除不需要的输入法,保留单一输入法清爽体验,输入法管理新手 5 分钟上手

作为一名在 IT 运维和桌面支持一线摸爬滚打了 15 年的老技术人&#xff0c;我每天要在十几台电脑之间来回切换。Win10/11总是自动加回微软拼音&#xff0c;手动删了又冒出来&#xff1b;这个工具从注册表深层清理&#xff0c;彻底告别多输入法切换的烦恼。今天就把 Windows输入…

作者头像 李华
网站建设 2026/8/21 20:05:25

还在翻文件夹?把秒级文件搜索搬进任务栏的免费工具

还在翻文件夹&#xff1f;把秒级文件搜索搬进任务栏的免费工具 【免费下载链接】EverythingToolbar Everything integration for the Windows taskbar. 项目地址: https://gitcode.com/gh_mirrors/eve/EverythingToolbar 找一份上周改过的文件&#xff0c;翻了半天文件夹…

作者头像 李华
网站建设 2026/8/21 20:04:34

FPGA VGA显示驱动实战:从时序原理到图形绘制与调试

1. 从“能用”到“懂原理”&#xff1a;VGA接口实战的核心是什么 如果你正在用FPGA、单片机或者嵌入式Linux做显示相关的项目&#xff0c;VGA接口大概率是你绕不开的一环。很多人觉得VGA过时了&#xff0c;但它在工业控制、教学实验、低成本显示方案里依然非常活跃。这个主题最…

作者头像 李华
网站建设 2026/8/21 20:02:09

开源插件化文件转换框架:本地化部署与自定义扩展实践

你是不是也遇到过这样的场景&#xff1a;手头有一堆文件需要转换格式——PDF转Word、图片转PDF、视频转音频、Excel转CSV……网上找工具&#xff0c;要么收费&#xff0c;要么限制文件大小&#xff0c;要么上传到不明服务器让人心里发毛。更头疼的是&#xff0c;这些需求往往零…

作者头像 李华