""" A low-level MCP Server exposing prompts over Streamable HTTP, without FastMCP or any frameowkrs Note that SSE, Server-Sent Events, is depreciated by MCP now - it keeps the seesion alive and cost more in cloud deployment Streamable HTTP is the new way to go Example source :https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/servers/simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless/server.py Author: Alex Punnen Status: Demo """ from mcp.server.lowlevel import Server from mcp.server.streamable_http_manager import StreamableHTTPSessionManager from starlette.applications import Starlette import uvicorn from starlette.routing import Mount from starlette.types import Receive, Scope, Send from collections.abc import AsyncIterator import contextlib import mcp.types as types def main(port: int = 4200) -> int: #--------------------------------------------------------------------------------------- # Define the MCP Application and add the artifacts to expose # to the MCP Client . In this case it is just the prompts # see - https://github.com/modelcontextprotocol/python-sdk/?tab=readme-ov-file#prompts #--------------------------------------------------------------------------------------- app = Server("mcp-server") # Add an tool @app.call_tool() async def add(name: str, arguments: dict) -> int: # see https://github.com/modelcontextprotocol/python-sdk/blob/6353dd192c41b891ef3bf1dfc093db46f6e2175a/examples/servers/simple-tool/mcp_simple_tool/server.py#L31 """Add two numbers""" print(f"Server received: a={name} (type={type(name)}), arguments={arguments} (type={type(arguments)})") # if name != "add": raise ValueError(f"Unknown tool: {name}") result = int(arguments["a"]) + int(arguments["b"]) return [types.TextContent(type='text', text=str(result))] @app.list_tools() async def list_tools() -> list[types.Tool]: return [ types.Tool( name="add", description=( "add two numbers and return the result. " ), inputSchema={ "type": "object", "required": ["a", "b"], "properties": { "a": { "type": "integer", "description": "The first number to add", }, "b": { "type": "integer", "description": "The second number to add", }, }, }, ) ] #--------------------------------------------------------------------------------------- # ALL Boilerplate code below #--------------------------------------------------------------------------------------- # Create the session manager with true stateless mode session_manager = StreamableHTTPSessionManager( app=app, event_store=None, json_response=True, stateless=True, ) 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...") # Create an ASGI application using the transport starlette_app = Starlette( debug=True, routes=[ Mount("/mcp", app=handle_streamable_http), ], lifespan=lifespan, ) # 3) Launch via Uvicorn uvicorn.run(starlette_app, host="0.0.0.0", port=port) return 0 if __name__ == "__main__": main()