From 9fd680c366f334d0a4313fdb652fe1aaa391f4fc Mon Sep 17 00:00:00 2001 From: Betula-L Date: Thu, 22 May 2025 05:34:50 +0800 Subject: [PATCH] support streamable http mcp (#1245) Co-authored-by: luhualin --- packages/markitdown-mcp/README.md | 11 +++++--- packages/markitdown-mcp/pyproject.toml | 2 +- .../src/markitdown_mcp/__main__.py | 27 +++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) diff --git a/packages/markitdown-mcp/README.md b/packages/markitdown-mcp/README.md index 5b92fb3..b1ea4d4 100644 --- a/packages/markitdown-mcp/README.md +++ b/packages/markitdown-mcp/README.md @@ -4,7 +4,7 @@ ![PyPI - Downloads](https://img.shields.io/pypi/dd/markitdown-mcp) [![Built by AutoGen Team](https://img.shields.io/badge/Built%20by-AutoGen%20Team-blue)](https://github.com/microsoft/autogen) -The `markitdown-mcp` package provides a lightweight STDIO and SSE MCP server for calling MarkItDown. +The `markitdown-mcp` package provides a lightweight STDIO, SSE and Streamable HTTP MCP server for calling MarkItDown. It exposes one tool: `convert_to_markdown(uri)`, where uri can be any `http:`, `https:`, `file:`, or `data:` URI. @@ -25,7 +25,7 @@ To run the MCP server, ussing STDIO (default) use the following command: markitdown-mcp ``` -To run the MCP server, using SSE use the following command: +To run the MCP server, using SSE or Streamable HTTP use the following command: ```bash markitdown-mcp --sse --host 127.0.0.1 --port 3001 @@ -114,6 +114,11 @@ If using SSE: * input `http://127.0.0.1:3001/sse` as the URL, and * click `Connect` +If using Streamable HTTP: +* select `Streamable HTTP` as the transport type, +* input `http://127.0.0.1:3001/mcp` as the URL, and +* click `Connect` + Finally: * click the `Tools` tab, * click `List Tools`, @@ -122,7 +127,7 @@ Finally: ## Security Considerations -The server does not support authentication, and runs with the privileges if the user running it. For this reason, when running in SSE mode, it is recommended to run the server bound to `localhost` (default). +The server does not support authentication, and runs with the privileges if the user running it. For this reason, when running in SSE or Streamable HTTP mode, it is recommended to run the server bound to `localhost` (default). ## Trademarks diff --git a/packages/markitdown-mcp/pyproject.toml b/packages/markitdown-mcp/pyproject.toml index 6cbc0e5..746253b 100644 --- a/packages/markitdown-mcp/pyproject.toml +++ b/packages/markitdown-mcp/pyproject.toml @@ -24,7 +24,7 @@ classifiers = [ "Programming Language :: Python :: Implementation :: PyPy", ] dependencies = [ - "mcp~=1.5.0", + "mcp~=1.8.0", "markitdown[all]>=0.1.1,<0.2.0", ] diff --git a/packages/markitdown-mcp/src/markitdown_mcp/__main__.py b/packages/markitdown-mcp/src/markitdown_mcp/__main__.py index 9e404ab..4732f5e 100644 --- a/packages/markitdown-mcp/src/markitdown_mcp/__main__.py +++ b/packages/markitdown-mcp/src/markitdown_mcp/__main__.py @@ -1,10 +1,14 @@ +import contextlib import sys +from collections.abc import AsyncIterator from mcp.server.fastmcp import FastMCP from starlette.applications import Starlette from mcp.server.sse import SseServerTransport from starlette.requests import Request from starlette.routing import Mount, Route +from starlette.types import Receive, Scope, Send from mcp.server import Server +from mcp.server.streamable_http_manager import StreamableHTTPSessionManager from markitdown import MarkItDown import uvicorn @@ -20,6 +24,12 @@ async def convert_to_markdown(uri: str) -> str: def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlette: sse = SseServerTransport("/messages/") + session_manager = StreamableHTTPSessionManager( + app=mcp_server, + event_store=None, + json_response=True, + stateless=True, + ) async def handle_sse(request: Request) -> None: async with sse.connect_sse( @@ -33,12 +43,29 @@ def create_starlette_app(mcp_server: Server, *, debug: bool = False) -> Starlett mcp_server.create_initialization_options(), ) + async def handle_streamable_http( + scope: Scope, receive: Receive, send: Send + ) -> None: + await session_manager.handle_request(scope, receive, send) + + @contextlib.asynccontextmanager + async def lifespan(app: Starlette) -> AsyncIterator[None]: + """Context manager for session manager.""" + async with session_manager.run(): + print("Application started with StreamableHTTP session manager!") + try: + yield + finally: + print("Application shutting down...") + return Starlette( debug=debug, routes=[ Route("/sse", endpoint=handle_sse), + Mount("/mcp", app=handle_streamable_http), Mount("/messages/", app=sse.handle_post_message), ], + lifespan=lifespan, )