Structured tool output: `-> bytes` return crashes with a generic error on non-UTF-8 payloads instead of returning the advertised base64/binary string
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 78/100
Línea de trabajo
Start at src/mcp/server/mcpserver/utilities/func_metadata.py, especially convert_result and _convert_to_content, and review tests/server/mcpserver/test_func_metadata.py plus the documented structured-output cases. Reproduce the PNG/non-UTF-8 and nested-model cases, then verify that both return successfully with the advertised binary schema and that existing UTF-8 behavior remains covered.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Initial Checks
- I confirm that I'm using the newest release of my line (the latest 2.x, or the latest 1.x if I'm still on v1)
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Release line
2.x (current stable)
Description
A tool annotated -> bytes publishes output_schema: {"result": {"type": "string", "format": "binary"}}, but when the tool returns actual binary data (PNG magic bytes, a zip, a PDF - anything that is not valid UTF-8) the call fails inside convert_result with PydanticSerializationError. The client only receives is_error: true with the generic text Error executing tool <name> and the data is lost. UTF-8-decodable bytes do not raise, but come back as a raw string ({"result": "hello"}) rather than any base64 encoding.
Observed output (Python 3.13.15, mcp 2.2.0, macOS, run at the v2.2.0 tag):
advertised output_schema: {'properties': {'result': {'format': 'binary', 'title': 'Result', 'type': 'string'}}, 'required': ['result'], 'type': 'object', 'title': 'read_thumbnailOutput'}
is_error: True
content: [TextContent(type='text', text='Error executing tool read_thumbnail', annotations=None, meta=None)]
structured: None
[server] PydanticSerializationError: Error serializing to JSON: invalid utf-8 sequence of 1 bytes from index 0 — at func_metadata.py:653 in _convert_to_content, via convert_result:209
UTF-8-decodable bytes b'hello' -> structured_content={'result': 'hello'} (raw string, not base64)
A bytes field inside an output model (e.g. class Thumb(BaseModel): data: bytes) hits the same crash, with the same advertised format: binary schema for that field.
Expected: bytes is documented as a supported structured-output return type (docstring at src/mcp/server/mcpserver/utilities/func_metadata.py:304 - "Primitive types (str, int, float, bool, bytes, None) - wrapped in a model with a 'result' field"; also docs/servers/structured-output.md: "Every scalar gets the same wrapper: str, int, float, bool, bytes, None"), and tests/server/mcpserver/test_func_metadata.py asserts the published {"type": "string", "format": "binary"} schema. So a -> bytes tool should deliver its payload to the client, encoded so it survives JSON - the way every other bytes path in this package already does: server.py:475 uses base64.b64encode(item.content).decode() for BlobResourceContents, and Image.to_image_content() / Audio.to_audio_content() in utilities/types.py base64-encode their data. It should not raise on the first non-UTF-8 byte and swallow the payload behind a generic error.
Root cause: src/mcp/server/mcpserver/utilities/func_metadata.py:653 - _convert_to_content (reached from convert_result, func_metadata.py:209) serializes a non-str result with pydantic_core.to_json(result, fallback=str, ...), which JSON-encodes bytes by UTF-8-decoding them; the fallback only applies to unknown types, so non-UTF-8 bytes raise PydanticSerializationError.
If it's useful I'm happy to describe or implement the approach (base64-encode bytes in _convert_to_content and the structured dump, following Image/Audio) - happy to be assigned.
Related: the open PRs touching func_metadata.py (#3119, #3118, #2939) cover argument forwarding, schema generation and Annotated metadata, not this; the base64 issues I found (#2376, #3123) are about the Image content path only.
AI disclosure: this issue was prepared with AI assistance; the reproduction above was run and its output verified by me on the v2.2.0 tag.
Example Code
import asyncio
import sys
sys.path.insert(0, "src")
sys.path.insert(0, ".")
from mcp import MCPError
from mcp.server.mcpserver import MCPServer
from tests.interaction._connect import connect_in_memory
PNG_MAGIC = b"\x89PNG\r\n\x1a\n" # every real PNG starts with this (invalid UTF-8)
async def main() -> None:
mcp = MCPServer("files")
@mcp.tool()
def read_thumbnail(name: str) -> bytes:
"""Return a thumbnail's raw bytes."""
return PNG_MAGIC + b"\x00\x00"
async with connect_in_memory(mcp) as client:
tools = await client.list_tools()
print("advertised output_schema:", tools.tools[0].output_schema)
try:
result = await client.call_tool("read_thumbnail", {"name": "x.png"})
print("OK:", result.content)
except MCPError as e:
print("FAILED:", e.error.code, e.error.message)
asyncio.run(main())
Python & MCP Python SDK
mcp (python-sdk): 2.2.0 (tag v2.2.0 = commit 9972c21, main)
Python: 3.13.15
OS: macOS (Darwin)
- Lenguaje dominante
- Python
- Estrellas
- 24.3k
- Forks
- 4k
- Merge medio
- 1 d 19 min
- PR fusionados (30 d)
- 29
Guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de modelcontextprotocol/python-sdk
-
Streamable HTTP client logs a WARNING for valid 202 Accepted on session termination (DELETE) Abiertov1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
modelcontextprotocol/python-sdk#3546 · 5 comentarios ·
-
v1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
modelcontextprotocol/python-sdk#3545 · 1 comentario ·
-
v1 v2
Dificultad 1/5 Menos de una hora Aptitud para principiantes 91/100
modelcontextprotocol/python-sdk#3508 · 2 comentarios ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 64/100
modelcontextprotocol/python-sdk#3504 ·
-
v1 v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
modelcontextprotocol/python-sdk#3492 · 1 comentario ·
Todos los issues de modelcontextprotocol/python-sdk
Issues similares
-
fix: inaccuracy ⚠️
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
uabrc/uabrc.github.io#1255 · 1 comentario ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 84/100
ethereum-optimism/factory#64 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 90/100
duckdb/duckdb-python#627 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 68/100
-
Add link for tutorial Abiertodocumentation
Dificultad 1/5 Menos de una hora Aptitud para principiantes 78/100
Qiskit/qiskit-addon-sqd#376 ·