From f0ee521e877b368d591466bac793a212bbcc87e9 Mon Sep 17 00:00:00 2001 From: luhualin Date: Mon, 12 May 2025 16:53:55 +0800 Subject: [PATCH] support streamable http mcp --- 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 32b7527..ed84b09 100644 --- a/packages/markitdown-mcp/src/markitdown_mcp/__main__.py +++ b/packages/markitdown-mcp/src/markitdown_mcp/__main__.py @@ -1,11 +1,15 @@ +import contextlib import sys +from collections.abc import AsyncIterator from typing import Any 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 @@ -21,6 +25,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( @@ -34,12 +44,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, )