Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion docs/experimental/tasks-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,7 @@ from starlette.routing import Mount
from mcp.server import Server
from mcp.server.experimental.task_context import ServerTaskContext
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from mcp.server.transport_security import TransportSecuritySettings
from mcp.types import (
CallToolResult, CreateTaskResult, TextContent, Tool, ToolExecution, TASK_REQUIRED,
)
Expand Down Expand Up @@ -463,7 +464,13 @@ async def handle_tool(name: str, arguments: dict) -> CallToolResult | CreateTask


def create_app():
session_manager = StreamableHTTPSessionManager(app=server)
session_manager = StreamableHTTPSessionManager(
app=server,
security_settings=TransportSecuritySettings(
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
),
)
Comment thread
maxisbey marked this conversation as resolved.
Outdated

@asynccontextmanager
async def lifespan(app: Starlette) -> AsyncIterator[None]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,18 @@ def main(port: int, transport: str) -> int:

if transport == "sse":
Comment thread
maxisbey marked this conversation as resolved.
Outdated
from mcp.server.sse import SseServerTransport
from mcp.server.transport_security import TransportSecuritySettings
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Mount, Route

sse = SseServerTransport("/messages/")
sse = SseServerTransport(
"/messages/",
security_settings=TransportSecuritySettings(
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
),
)

async def handle_sse(request: Request):
async with sse.connect_sse(request.scope, request.receive, request._send) as streams: # type: ignore[reportPrivateUsage]
Expand Down
9 changes: 8 additions & 1 deletion examples/servers/simple-prompt/mcp_simple_prompt/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,18 @@ def main(port: int, transport: str) -> int:

if transport == "sse":
Comment thread
maxisbey marked this conversation as resolved.
Outdated
from mcp.server.sse import SseServerTransport
from mcp.server.transport_security import TransportSecuritySettings
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Mount, Route

sse = SseServerTransport("/messages/")
sse = SseServerTransport(
"/messages/",
security_settings=TransportSecuritySettings(
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
),
)

async def handle_sse(request: Request):
async with sse.connect_sse(request.scope, request.receive, request._send) as streams: # type: ignore[reportPrivateUsage]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,18 @@ def main(port: int, transport: str) -> int:

if transport == "sse":
Comment thread
maxisbey marked this conversation as resolved.
Outdated
from mcp.server.sse import SseServerTransport
from mcp.server.transport_security import TransportSecuritySettings
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Mount, Route

sse = SseServerTransport("/messages/")
sse = SseServerTransport(
"/messages/",
security_settings=TransportSecuritySettings(
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
),
)

async def handle_sse(request: Request):
async with sse.connect_sse(request.scope, request.receive, request._send) as streams: # type: ignore[reportPrivateUsage]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from mcp import types
from mcp.server import Server, ServerRequestContext
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from mcp.server.transport_security import TransportSecuritySettings
from starlette.applications import Starlette
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import Mount
Expand Down Expand Up @@ -110,6 +111,10 @@ def main(
event_store=None,
json_response=json_response,
stateless=True,
security_settings=TransportSecuritySettings(
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
),
)

async def handle_streamable_http(scope: Scope, receive: Receive, send: Send) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from mcp import types
from mcp.server import Server, ServerRequestContext
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from mcp.server.transport_security import TransportSecuritySettings
from starlette.applications import Starlette
from starlette.middleware.cors import CORSMiddleware
from starlette.routing import Mount
Expand Down Expand Up @@ -132,6 +133,10 @@ def main(
app=app,
event_store=event_store, # Enable resumability
json_response=json_response,
security_settings=TransportSecuritySettings(
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
),
)

# ASGI handler for streamable HTTP connections
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,13 @@
- ServerTaskContext.elicit() and ServerTaskContext.create_message() queue requests properly
"""

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import Any

import click
import uvicorn
from mcp import types
from mcp.server import Server, ServerRequestContext
from mcp.server.experimental.task_context import ServerTaskContext
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from starlette.applications import Starlette
from starlette.routing import Mount


async def handle_list_tools(
Expand Down Expand Up @@ -134,23 +129,10 @@ async def handle_call_tool(
server.experimental.enable_tasks()


def create_app(session_manager: StreamableHTTPSessionManager) -> Starlette:
@asynccontextmanager
async def app_lifespan(app: Starlette) -> AsyncIterator[None]:
async with session_manager.run():
yield

return Starlette(
routes=[Mount("/mcp", app=session_manager.handle_request)],
lifespan=app_lifespan,
)


@click.command()
@click.option("--port", default=8000, help="Port to listen on")
def main(port: int) -> int:
session_manager = StreamableHTTPSessionManager(app=server)
starlette_app = create_app(session_manager)
starlette_app = server.streamable_http_app()
print(f"Starting server on http://localhost:{port}/mcp")
uvicorn.run(starlette_app, host="127.0.0.1", port=port)
return 0
18 changes: 1 addition & 17 deletions examples/servers/simple-task/mcp_simple_task/server.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
"""Simple task server demonstrating MCP tasks over streamable HTTP."""

from collections.abc import AsyncIterator
from contextlib import asynccontextmanager

import anyio
import click
import uvicorn
from mcp import types
from mcp.server import Server, ServerRequestContext
from mcp.server.experimental.task_context import ServerTaskContext
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from starlette.applications import Starlette
from starlette.routing import Mount


async def handle_list_tools(
Expand Down Expand Up @@ -69,17 +63,7 @@ async def work(task: ServerTaskContext) -> types.CallToolResult:
@click.command()
@click.option("--port", default=8000, help="Port to listen on")
def main(port: int) -> int:
session_manager = StreamableHTTPSessionManager(app=server)

@asynccontextmanager
async def app_lifespan(app: Starlette) -> AsyncIterator[None]:
async with session_manager.run():
yield

starlette_app = Starlette(
routes=[Mount("/mcp", app=session_manager.handle_request)],
lifespan=app_lifespan,
)
starlette_app = server.streamable_http_app()
Comment thread
maxisbey marked this conversation as resolved.

print(f"Starting server on http://localhost:{port}/mcp")
uvicorn.run(starlette_app, host="127.0.0.1", port=port)
Expand Down
9 changes: 8 additions & 1 deletion examples/servers/simple-tool/mcp_simple_tool/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,18 @@ def main(port: int, transport: str) -> int:

if transport == "sse":
Comment thread
maxisbey marked this conversation as resolved.
Outdated
from mcp.server.sse import SseServerTransport
from mcp.server.transport_security import TransportSecuritySettings
from starlette.applications import Starlette
from starlette.responses import Response
from starlette.routing import Mount, Route

sse = SseServerTransport("/messages/")
sse = SseServerTransport(
"/messages/",
security_settings=TransportSecuritySettings(
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
),
)

async def handle_sse(request: Request):
async with sse.connect_sse(request.scope, request.receive, request._send) as streams: # type: ignore[reportPrivateUsage]
Expand Down
38 changes: 5 additions & 33 deletions examples/servers/sse-polling-demo/mcp_sse_polling_demo/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,13 @@
uv run mcp-sse-polling-demo --port 3000
"""

import contextlib
import logging
from collections.abc import AsyncIterator

import anyio
import click
import uvicorn
from mcp import types
from mcp.server import Server, ServerRequestContext
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from starlette.applications import Starlette
from starlette.routing import Mount
from starlette.types import Receive, Scope, Send

from .event_store import InMemoryEventStore

Expand Down Expand Up @@ -149,37 +144,14 @@ def main(port: int, log_level: str, retry_interval: int) -> int:
on_call_tool=handle_call_tool,
)

# Create event store for resumability
event_store = InMemoryEventStore()

# Create session manager with event store and retry interval
session_manager = StreamableHTTPSessionManager(
app=app,
event_store=event_store,
starlette_app = app.streamable_http_app(
event_store=InMemoryEventStore(),
retry_interval=retry_interval,
)

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(starlette_app: Starlette) -> AsyncIterator[None]:
async with session_manager.run():
logger.info(f"SSE Polling Demo server started on port {port}")
logger.info("Try: POST /mcp with tools/call for 'process_batch'")
yield
logger.info("Server shutting down...")

starlette_app = Starlette(
debug=True,
routes=[
Mount("/mcp", app=handle_streamable_http),
],
lifespan=lifespan,
)

import uvicorn

logger.info(f"SSE Polling Demo server starting on port {port}")
logger.info("Try: POST /mcp with tools/call for 'process_batch'")
uvicorn.run(starlette_app, host="127.0.0.1", port=port)
return 0

Expand Down
Loading