python-sdk icon indicating copy to clipboard operation
python-sdk copied to clipboard

SSE client connect SSE Server got Error: " Error in post_writer" and "TypeError: 'NoneType' object is not callable"

Open stevensu1977 opened this issue 1 year ago • 5 comments

Hi Guys , I want to build sse server and client from sdk example , but I'm not lucky, it can't work, SSE server and client got error :

SSE Server got starlette error like this :

.venv/lib/python3.13/site-packages/starlette/routing.py", line 74, in app await response(scope, receive, send) ~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^ TypeError: 'NoneType' object is not callable

SSE Client ERROR: "Error in post_writer: " , anyone met have this issue ?

mcp 1.1.1 sse-starlette 2.1.3 starlette 0.41.3 uvicorn 0.32.1

sse server code :

  import sys
  import logging
  import anyio
  import click
  import httpx
  import mcp.types as types
  from mcp.server import Server
  
  
  logging.basicConfig(
      #level=logging.CRITICAL,
      level=logging.DEBUG,
      format="%(asctime)s - %(levelname)s - %(message)s",
      stream=sys.stderr,
  )
  
  async def fetch_website(
      url: str,
  ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
      headers = {
          "User-Agent": "MCP Test Server (github.com/modelcontextprotocol/python-sdk)"
      }
      async with httpx.AsyncClient(follow_redirects=True, headers=headers) as client:
          response = await client.get(url)
          response.raise_for_status()
          return [types.TextContent(type="text", text=response.text)]
  
  
  @click.command()
  @click.option("--port", default=8000, help="Port to listen on for SSE")
  @click.option(
      "--transport",
      type=click.Choice(["stdio", "sse"]),
      default="stdio",
      help="Transport type",
  )
  def main(port: int, transport: str) -> int:
      app = Server("mcp-website-fetcher")
  
      @app.call_tool()
      async def fetch_tool(
          name: str, arguments: dict
      ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
          if name != "fetch":
              raise ValueError(f"Unknown tool: {name}")
          if "url" not in arguments:
              raise ValueError("Missing required argument 'url'")
          return await fetch_website(arguments["url"])
  
      @app.list_tools()
      async def list_tools() -> list[types.Tool]:
          print("list_tools")
          return [
              types.Tool(
                  name="fetch",
                  description="Fetches a website and returns its content",
                  inputSchema={
                      "type": "object",
                      "required": ["url"],
                      "properties": {
                          "url": {
                              "type": "string",
                              "description": "URL to fetch",
                          }
                      },
                  },
              )
          ]
  
      if transport == "sse":
          from mcp.server.sse import SseServerTransport
          from starlette.applications import Starlette
          from starlette.routing import Route
          from starlette.responses import Response
  
          sse = SseServerTransport("/messages")
  
          async def handle_sse(request):
              async with sse.connect_sse(
                  request.scope, request.receive, request._send
              ) as streams:
                  await app.run(
                      streams[0], streams[1], app.create_initialization_options()
                  )
  
          async def handle_messages(request):
              print(request)
              await sse.handle_post_message(request.scope, request.receive, request._send)
  
          async def handle_hello(request):
              print(request)
              return Response("Hello, World!", status_code=200)
  
          starlette_app = Starlette(
              debug=True,
              routes=[
                  Route("/sse", endpoint=handle_sse),
                  Route("/messages", endpoint=handle_messages, methods=["POST"]),
                  Route("/hello", endpoint=handle_hello, methods=["GET"]),
              ],
          )
  
          import uvicorn
          
          uvicorn.run(starlette_app, host="0.0.0.0", port=port)
      else:
          from mcp.server.stdio import stdio_server
  
          async def arun():
              async with stdio_server() as streams:
                  await app.run(
                      streams[0], streams[1], app.create_initialization_options()
                  )
  
          anyio.run(arun)
  
      return 0
  
  main()

sse client code:

  import asyncio
  import subprocess
  
  from mcp.client.session import ClientSession
  from mcp.client.sse import sse_client
  
  
  def main():
      # Start the server
      
      try:
          # Run the client logic
          asyncio.run(client_logic())
      finally:
          # Terminate the server process
          print("Server terminated.")
  
  
  async def client_logic():
      async with sse_client(url="http://127.0.0.1:8000/sse") as (read, write):
          async with ClientSession(read, write) as session:
              await session.initialize()
  
              # List available tools
              tools = await session.list_tools()
              print(tools)
  
              # Call the fetch tool
              result = await session.call_tool("fetch", {"url": "https://example.com"})
              print(result)
  
  
  if __name__ == "__main__":
      main()

stevensu1977 avatar Dec 12 '24 13:12 stevensu1977

Update , I changed to python 3.10.12, SSE server still have TypeError: 'NoneType' object is not callable, but client works!

(.venv) ➜  sse git:(main) ✗ python example_client.py http://localhost:8000/sse
nextCursor=None tools=[Tool(name='fetch', description='Fetches a website and returns its content', inputSchema={'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'description': 'URL to fetch'}}})]
content=[TextContent(type='text', text='<!doctype html>\n<html>\n<head>\n    <title>Example Domain</title>\n\n    <meta charset="utf-8" />\n    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />\n    <meta name="viewport" content="width=device-width, initial-scale=1" />\n    <style type="text/css">\n    body {\n        background-color: #f0f0f2;\n        margin: 0;\n        padding: 0;\n        font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;\n        \n    }\n    div {\n        width: 600px;\n        margin: 5em auto;\n        padding: 2em;\n        background-color: #fdfdff;\n        border-radius: 0.5em;\n        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n    }\n    a:link, a:visited {\n        color: #38488f;\n        text-decoration: none;\n    }\n    @media (max-width: 700px) {\n        div {\n            margin: 0 auto;\n            width: auto;\n        }\n    }\n    </style>    \n</head>\n\n<body>\n<div>\n    <h1>Example Domain</h1>\n    <p>This domain is for use in illustrative examples in documents. You may use this\n    domain in literature without prior coordination or asking for permission.</p>\n    <p><a href="https://www.iana.org/domains/example">More information...</a></p>\n</div>\n</body>\n</html>\n')] isError=False

stevensu1977 avatar Dec 13 '24 02:12 stevensu1977

Update , I changed to python 3.10.12, SSE server still have TypeError: 'NoneType' object is not callable, but client works!

(.venv) ➜  sse git:(main) ✗ python example_client.py http://localhost:8000/sse
nextCursor=None tools=[Tool(name='fetch', description='Fetches a website and returns its content', inputSchema={'type': 'object', 'required': ['url'], 'properties': {'url': {'type': 'string', 'description': 'URL to fetch'}}})]
content=[TextContent(type='text', text='<!doctype html>\n<html>\n<head>\n    <title>Example Domain</title>\n\n    <meta charset="utf-8" />\n    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />\n    <meta name="viewport" content="width=device-width, initial-scale=1" />\n    <style type="text/css">\n    body {\n        background-color: #f0f0f2;\n        margin: 0;\n        padding: 0;\n        font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;\n        \n    }\n    div {\n        width: 600px;\n        margin: 5em auto;\n        padding: 2em;\n        background-color: #fdfdff;\n        border-radius: 0.5em;\n        box-shadow: 2px 3px 7px 2px rgba(0,0,0,0.02);\n    }\n    a:link, a:visited {\n        color: #38488f;\n        text-decoration: none;\n    }\n    @media (max-width: 700px) {\n        div {\n            margin: 0 auto;\n            width: auto;\n        }\n    }\n    </style>    \n</head>\n\n<body>\n<div>\n    <h1>Example Domain</h1>\n    <p>This domain is for use in illustrative examples in documents. You may use this\n    domain in literature without prior coordination or asking for permission.</p>\n    <p><a href="https://www.iana.org/domains/example">More information...</a></p>\n</div>\n</body>\n</html>\n')] isError=False

stevensu1977 avatar Dec 13 '24 03:12 stevensu1977

+1 seems like SSE transport is broken in Python 3.13

byt3bl33d3r avatar Dec 15 '24 23:12 byt3bl33d3r

+1 .. It would be great if we can have python and javascript sse client example...

raolak avatar Dec 19 '24 04:12 raolak

Sse is broken , tried tool example:

/python3.11/site-packages/starlette/routing.py", line 74, in app
    await response(scope, receive, send)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not callable

likewise for fastMCP https://github.com/jlowin/fastmcp/issues/69 and other reports: https://github.com/modelcontextprotocol/python-sdk/pull/83

apappascs avatar Dec 21 '24 18:12 apappascs

+1, seems to be broken for Python 3.12 as well, downgraded to 3.10.12 (thanks @stevensu1977) and client worked (although still getting the same error on server)

drorrose avatar Dec 29 '24 18:12 drorrose