modelcontextprotocol / modelcontextprotocol/python-sdk

ClientSessionGroup raises KeyError when a server exposes no tools, resources or prompts

Ouverte
#3,384 2 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

v1 v2
Langage dominant
Python
Étoiles
24.3k
Forks
4k
Merge moyen
1 j 1 h
PR mergées (30 j)
31

Description

Initial Checks
Release line

2.x (current stable), and it reproduces on the latest 1.x too.

Description

Connecting a ClientSessionGroup to a server that registers nothing fails with a raw KeyError. A server that registers even one tool, resource or prompt is fine.

_aggregate_components ends with this cleanup step, which only runs when all three list calls came back empty (session_group.py:420 on 2.1.0):

# Clean up exit stack for session if we couldn't retrieve anything
# from the server.
if not any((prompts_temp, resources_temp, tools_temp)):
    del self._session_exit_stacks[session]  # pragma: no cover

Two different things go wrong depending on how you got there.

Via connect_with_session(), the key was never inserted, so the del raises KeyError. That is the traceback below.

Via connect_to_server(), the key does exist, so the del succeeds. It removes the bookkeeping entry without closing the stack. The next lines then register the session in self._sessions anyway, so the group reports the session as connected while holding no way to close it. Calling disconnect_from_server() afterwards takes the session_known_for_stack is False branch and returns without closing anything, so the transport stays open until the whole group is torn down. I confirmed this by pre-registering a stack with a close callback and watching the callback never fire.

What I expected: connecting to a server that exposes nothing should either succeed with an empty component set, or fail with a clear error. If the group decides to drop the session, the exit stack should be closed rather than forgotten.

Two smaller things in the same condition, for whoever picks this up. The comment says "clean up exit stack" but the code only forgets it. And not any(...) treats "the server legitimately exposes nothing" and "all three list calls raised" as the same case, which may deserve separating.

Example Code
import anyio
import mcp_types as types
from mcp.client.client import Client
from mcp.client.session_group import ClientSessionGroup
from mcp.server.mcpserver import MCPServer

INFO = types.Implementation(name="empty", version="1.0")


async def main() -> None:
    # A server that registers no tools, no resources and no prompts.
    async with Client(MCPServer("empty")) as client:
        group = ClientSessionGroup()
        await group.connect_with_session(INFO, client.session)


anyio.run(main)

Output:

  File ".../mcp/client/session_group.py", line 420, in _aggregate_components
    del self._session_exit_stacks[session]  # pragma: no cover
        ~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^
KeyError: <mcp.client.session.ClientSession object at 0x77e8450e16a0>
Python & MCP Python SDK
Python 3.13.11, Linux.

mcp 2.1.0  (latest 2.x): fails at session_group.py:420
mcp 1.29.1 (latest 1.x): fails at session_group.py:409
Also reproduces on main at d8b63830.

Reported with AI assistance; I ran the reproduction above and read the output.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Commencez dans mcp/client/session_group.py, au niveau de _aggregate_components vers la ligne 420, puis faites passer l’exemple de serveur vide de l’issue par connect_with_session() et connect_to_server(). Vérifiez les chemins existants de nettoyage de session et disconnect_from_server() ; le travail est terminé lorsque les serveurs vides ne lèvent plus de KeyError et que toute pile de session ignorée est correctement fermée, avec une couverture pour les deux chemins de connexion.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
backend-api-design
Type d'issue
Bug
Difficulté
3/5
Temps estimé
1-2 jours
Activité
Active
Clarté
Plutôt claire
Accessibilité débutants
70/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.