fix: add ensure_ascii=False to all MCP json.dumps calls (#10234) (#10512)

Python's json.dumps() defaults to ensure_ascii=True, escaping non-ASCII
characters to \uXXXX sequences.  For CJK characters this inflates
token count 3-4x — a single Chinese character like '中' becomes
'\u4e2d' (6 chars vs 3 bytes, ~6 tokens vs ~1 token).

Since MCP tool results feed directly into the model's conversation
context, this silently multiplied API costs for Chinese, Japanese,
and Korean users.

Fix: add ensure_ascii=False to all 20 json.dumps calls in mcp_tool.py.
Raw UTF-8 is valid JSON per RFC 8259 and all downstream consumers
(LLM APIs, display) handle it correctly.

Closes #10234
This commit is contained in:
Teknium 2026-04-15 13:59:57 -07:00 committed by GitHub
parent 19142810ed
commit 861efe274b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -506,7 +506,7 @@ class SamplingHandler:
"type": "function",
"function": {
"name": tu.name,
"arguments": json.dumps(tu.input) if isinstance(tu.input, dict) else str(tu.input),
"arguments": json.dumps(tu.input, ensure_ascii=False) if isinstance(tu.input, dict) else str(tu.input),
},
})
msg_dict: dict = {"role": msg.role, "tool_calls": tc_list}
@ -1274,7 +1274,7 @@ def _interrupted_call_result() -> str:
"""Standardized JSON error for a user-interrupted MCP tool call."""
return json.dumps({
"error": "MCP call interrupted: user sent a new message"
})
}, ensure_ascii=False)
# ---------------------------------------------------------------------------
@ -1361,7 +1361,7 @@ def _make_tool_handler(server_name: str, tool_name: str, tool_timeout: float):
if not server or not server.session:
return json.dumps({
"error": f"MCP server '{server_name}' is not connected"
})
}, ensure_ascii=False)
async def _call():
result = await server.session.call_tool(tool_name, arguments=args)
@ -1375,7 +1375,7 @@ def _make_tool_handler(server_name: str, tool_name: str, tool_timeout: float):
"error": _sanitize_error(
error_text or "MCP tool returned an error"
)
})
}, ensure_ascii=False)
# Collect text from content blocks
parts: List[str] = []
@ -1394,9 +1394,9 @@ def _make_tool_handler(server_name: str, tool_name: str, tool_timeout: float):
return json.dumps({
"result": text_result,
"structuredContent": structured,
})
return json.dumps({"result": structured})
return json.dumps({"result": text_result})
}, ensure_ascii=False)
return json.dumps({"result": structured}, ensure_ascii=False)
return json.dumps({"result": text_result}, ensure_ascii=False)
try:
return _run_on_mcp_loop(_call(), timeout=tool_timeout)
@ -1411,7 +1411,7 @@ def _make_tool_handler(server_name: str, tool_name: str, tool_timeout: float):
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
)
})
}, ensure_ascii=False)
return _handler
@ -1425,7 +1425,7 @@ def _make_list_resources_handler(server_name: str, tool_timeout: float):
if not server or not server.session:
return json.dumps({
"error": f"MCP server '{server_name}' is not connected"
})
}, ensure_ascii=False)
async def _call():
result = await server.session.list_resources()
@ -1441,7 +1441,7 @@ def _make_list_resources_handler(server_name: str, tool_timeout: float):
if hasattr(r, "mimeType") and r.mimeType:
entry["mimeType"] = r.mimeType
resources.append(entry)
return json.dumps({"resources": resources})
return json.dumps({"resources": resources}, ensure_ascii=False)
try:
return _run_on_mcp_loop(_call(), timeout=tool_timeout)
@ -1455,7 +1455,7 @@ def _make_list_resources_handler(server_name: str, tool_timeout: float):
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
)
})
}, ensure_ascii=False)
return _handler
@ -1471,7 +1471,7 @@ def _make_read_resource_handler(server_name: str, tool_timeout: float):
if not server or not server.session:
return json.dumps({
"error": f"MCP server '{server_name}' is not connected"
})
}, ensure_ascii=False)
uri = args.get("uri")
if not uri:
@ -1487,7 +1487,7 @@ def _make_read_resource_handler(server_name: str, tool_timeout: float):
parts.append(block.text)
elif hasattr(block, "blob"):
parts.append(f"[binary data, {len(block.blob)} bytes]")
return json.dumps({"result": "\n".join(parts) if parts else ""})
return json.dumps({"result": "\n".join(parts) if parts else ""}, ensure_ascii=False)
try:
return _run_on_mcp_loop(_call(), timeout=tool_timeout)
@ -1501,7 +1501,7 @@ def _make_read_resource_handler(server_name: str, tool_timeout: float):
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
)
})
}, ensure_ascii=False)
return _handler
@ -1515,7 +1515,7 @@ def _make_list_prompts_handler(server_name: str, tool_timeout: float):
if not server or not server.session:
return json.dumps({
"error": f"MCP server '{server_name}' is not connected"
})
}, ensure_ascii=False)
async def _call():
result = await server.session.list_prompts()
@ -1536,7 +1536,7 @@ def _make_list_prompts_handler(server_name: str, tool_timeout: float):
for a in p.arguments
]
prompts.append(entry)
return json.dumps({"prompts": prompts})
return json.dumps({"prompts": prompts}, ensure_ascii=False)
try:
return _run_on_mcp_loop(_call(), timeout=tool_timeout)
@ -1550,7 +1550,7 @@ def _make_list_prompts_handler(server_name: str, tool_timeout: float):
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
)
})
}, ensure_ascii=False)
return _handler
@ -1566,7 +1566,7 @@ def _make_get_prompt_handler(server_name: str, tool_timeout: float):
if not server or not server.session:
return json.dumps({
"error": f"MCP server '{server_name}' is not connected"
})
}, ensure_ascii=False)
name = args.get("name")
if not name:
@ -1593,7 +1593,7 @@ def _make_get_prompt_handler(server_name: str, tool_timeout: float):
resp = {"messages": messages}
if hasattr(result, "description") and result.description:
resp["description"] = result.description
return json.dumps(resp)
return json.dumps(resp, ensure_ascii=False)
try:
return _run_on_mcp_loop(_call(), timeout=tool_timeout)
@ -1607,7 +1607,7 @@ def _make_get_prompt_handler(server_name: str, tool_timeout: float):
"error": _sanitize_error(
f"MCP call failed: {type(exc).__name__}: {exc}"
)
})
}, ensure_ascii=False)
return _handler