Skip to content

Tool handlers returning UserList lose native content blocks despite Sequence return annotation #3692

Open

Description

@OHHZZ

Initial Checks

Release line

2.x (current stable)

Description

When checking native content returns for MCP tools, I found that a tool annotated Sequence[TextContent] behaves differently depending on the concrete sequence returned. A list produces two native text blocks; collections.UserList produces one text block containing a quoted Python representation of the objects. The call reports is_error=False.

Expected: the returned native content blocks should be preserved for a supported non-string sequence. If tools intentionally support only list/tuple, the Sequence[...] annotation handling should be consistent with that limitation.

On current main, _returns_content() recognizes the Sequence annotation and selects content-only output, while _convert_to_content() only unrolls list/tuple. The UserList therefore reaches the JSON fallback instead of being converted block by block. This is a separate path from Prompt.render() in #3643.

Example Code

from collections import UserList
from collections.abc import Sequence
import anyio
from mcp import Client
from mcp.server.mcpserver import MCPServer
from mcp.types import TextContent

server = MCPServer("probe")
@server.tool()
def normal_list() -> Sequence[TextContent]:
    return [TextContent(type="text", text="one"), TextContent(type="text", text="two")]
@server.tool()
def user_list() -> Sequence[TextContent]:
    return UserList([TextContent(type="text", text="one"), TextContent(type="text", text="two")])

async def main():
    async with Client(server) as client:
        for name in ["normal_list", "user_list"]:
            result = await client.call_tool(name)
            print(name, "blocks=", len(result.content), "texts=", [x.text for x in result.content], "is_error=", result.is_error)
anyio.run(main)

The list tool returns blocks=2, texts ['one', 'two']. The UserList tool returns blocks=1; its sole text is a quoted representation beginning "[TextContent(type='text', text='one', ...) rather than the original two text blocks. Both report is_error=False.

Python & MCP Python SDK

  • macOS, Python 3.14.5.
  • Latest stable: mcp 2.3.0.
  • Also reproduced from main at c9356a2a8fb805ac2202e44c05371cf005edc923, using uv run --frozen --no-default-groups and an in-memory Client(server).
  • No model API or network transport is needed for the reproduction.

Possible scope

If non-string Sequence tool returns are intended to work, align runtime content conversion with the annotation handling. Regression cases should cover UserList, list/tuple, empty sequences, and ensure strings and binary values keep their existing behavior. I would be interested in contributing a focused fix if you accept the issue; no patch has been prepared yet.

AI assistance: Codex helped inspect the source and run the local reproductions. This was found in a focused compatibility probe, not a reported production outage.

Activity

  1. added
    bugSomething isn't working
    v2Affects the v2 line (2.x on main)
    v1Affects the v1.x maintenance line
    on Oct 12, 2026
  2. Janicebenita commented on Oct 12, 2026

    @Janicebenita

    I reproduced this on current main and prepared a focused local fix with public client/server regression coverage for populated and empty UserList results, plus guards for string and binary sequence-like values.

    A clean full Windows run completed with 6,202 passed, 16 skipped, 1 xfailed, and 100% coverage. After adding one final memoryview guard test, two full retries hit unrelated five-second transport/server timeouts; each affected test then passed 5/5 in isolation. The changed files pass Ruff and Pyright.

    I see that the reporter has offered to contribute, so I do not want to pre-empt them. If maintainers and the reporter prefer, I can share the patch or take an assignment under the contribution policy.

    AI assistance was used during implementation and verification; I reviewed the patch and executed the reported checks locally.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingv1Affects the v1.x maintenance linev2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions