Serving over Streamable HTTP

Model Context Protocol (MCP) · Practical · 12 min

HTTP is how you share one server with many users and connect it to hosted AI apps.

The same MCPServer object can serve over HTTP. Only the run() call changes:

mcp.run(transport="streamable-http", port=3001)

That starts a web server (uvicorn, a standard Python web server) and clients connect to http://127.0.0.1:3001/mcp. Defaults are host 127.0.0.1, port 8000, path /mcp. The mcp run server.py --transport streamable-http command does the same without editing code.

In SDK v2, transport options go to run(), not to MCPServer(...); the constructor describes what the server is, run() describes how it is served.

To add MCP to an existing web application, build an app object with mcp.streamable_http_app() and mount it. It is an ASGI app, the standard interface Python async web frameworks such as Starlette and FastAPI use, so it plugs in like any other sub-application.

Security basics before you expose it:

  • Locally, bind to 127.0.0.1, not 0.0.0.0 (which listens on every network interface).
  • Set an allowed hosts and origins list (TransportSecuritySettings). This blocks DNS rebinding, an attack where a malicious web page tricks your browser into sending requests to a server on your own machine.
  • Put it behind HTTPS and add authorization (covered later) before anyone else can reach it.

One server process serves both current clients and older ones that still use the 2025 handshake; you don't configure anything for that.

Example

from mcp.server import MCPServer
from mcp.server.transport_security import TransportSecuritySettings

mcp = MCPServer("Notes")


@mcp.tool()
def add_note(text: str) -> str:
    """Save a note."""
    return f"Saved: {text}"


# Option A: run directly -> http://127.0.0.1:3001/mcp
if __name__ == "__main__":
    mcp.run(transport="streamable-http", port=3001)

# Option B: an ASGI app for uvicorn or mounting in Starlette/FastAPI
security = TransportSecuritySettings(
    allowed_hosts=["mcp.example.com"],
    allowed_origins=["https://app.example.com"],
)
app = mcp.streamable_http_app(transport_security=security)
# uvicorn notes_http:app --host 127.0.0.1 --port 8000

Remember

  • mcp.run(transport='streamable-http') serves at /mcp on port 8000 by default
  • Transport options belong to run(), not the constructor
  • streamable_http_app() gives an ASGI app to mount
  • Bind to localhost and set allowed hosts/origins against DNS rebinding

Watch out for

  • Passing port= to MCPServer(...); in SDK v2 that is a TypeError. Pass it to run().

Check yourself

  1. Which protects a local HTTP MCP server from a malicious web page reaching it through your browser?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: py.sdk.modelcontextprotocol.io/run/