modelcontextprotocol / modelcontextprotocol/python-sdk

In-process callback failures break tool calls and notification sends

オープン
#3,434 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

v2
主要言語
Python
スター
24.3k
フォーク
4k
平均マージ
1日 1時間
マージ済み PR(30日)
31

説明

Initial Checks

  • I confirm that I am using the newest release of the 2.x line.
  • I confirm that I searched the issue tracker before opening this issue.

Release line

2.x (current stable)

Description

Client(server) uses DirectDispatcher. If a progress callback raises, that exception crosses back into server tool execution, so a tool that otherwise returns successfully fails with UnexpectedToolError. DirectDispatcher also lets an inbound notification handler exception escape back to the sender's notify() call.

The stream-backed JSONRPCDispatcher logs and contains both callback failures. I expected the in-process path to keep the same boundary: log the callback failure, but let the protocol operation continue.

I have a small patch and regression tests for both paths and would like to fix this if maintainers want an outside pull request.

I used GitHub Copilot CLI to inspect the dispatcher paths, write the patch, and draft this report. I verified the reproduction and tests.

Example Code

import anyio

from mcp import Client
from mcp.server.mcpserver import Context, MCPServer

server = MCPServer("callback-repro")


@server.tool()
async def work(context: Context) -> str:
    await context.report_progress(1, 1)
    return "done"


async def broken_progress(progress: float, total: float | None, message: str | None) -> None:
    raise RuntimeError("consumer failed")


async def main() -> None:
    async with Client(server, mode="2026-07-28") as client:
        result = await client.call_tool("work", progress_callback=broken_progress)
        print(result.content[0].text)


anyio.run(main)

Current main ends with:

UnexpectedToolError: Error executing tool work

The tool should print done. The callback failure should still be logged.

Verification

With only the regression tests applied to clean main:

uv run --frozen pytest tests/shared/test_dispatcher.py \
  -k 'progress_callback_exception_does_not_fail_request or notification_handler_exception_does_not_reach_sender' -q
# 2 failed, 2 passed. Both DirectDispatcher cases failed.

With the two callback guards applied:

uv run --frozen pytest tests/shared/test_dispatcher.py \
  -k 'progress_callback_exception_does_not_fail_request or notification_handler_exception_does_not_reach_sender' -q
# 4 passed

Python & MCP Python SDK

Python 3.12.13
mcp 2.1.1
mcp 2.1.2.dev4+d060b36e at d060b36e1d095ef6e93e07ba5d59bb69b2ad449a
macOS

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

DirectDispatcher のパスから始め、コールバックの失敗処理をストリームベースの JSONRPCDispatcher と比較します。失敗を再現するため、指定された 2 つの -k ケースを指定して tests/shared/test_dispatcher.py を実行します。両方の DirectDispatcher ケースがパスし、成功したツール呼び出しと通知の送信が引き続き機能し、コールバックの失敗がログに記録されれば完了です。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python
領域
backend-api-design
issue の種類
バグ
難易度
3/5
見積もり時間
1〜2日
活発さ
活発
明瞭さ
明確に書かれている
初心者へのやさしさ
84/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。