Skip to content

Commit c5c4ee7

Browse files
committed
docs(examples): add DNS rebinding protection to low-level transport examples
The low-level example servers use SseServerTransport and StreamableHTTPSessionManager directly, bypassing the auto-enable logic in sse_app()/streamable_http_app() that was added in v1.23.0 (GHSA-9h52-p55h-vw2f). Per the advisory guidance, low-level transport users should explicitly configure TransportSecuritySettings. These examples now demonstrate the correct pattern — the allowlist matches what the high-level API auto-configures for localhost binds. Github-Issue: #2269
1 parent 2c73a2a commit c5c4ee7

File tree

10 files changed

+71
-7
lines changed
  • docs/experimental
  • examples/servers
    • simple-pagination/mcp_simple_pagination
    • simple-prompt/mcp_simple_prompt
    • simple-resource/mcp_simple_resource
    • simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless
    • simple-streamablehttp/mcp_simple_streamablehttp
    • simple-task-interactive/mcp_simple_task_interactive
    • simple-task/mcp_simple_task
    • simple-tool/mcp_simple_tool
    • sse-polling-demo/mcp_sse_polling_demo

10 files changed

+71
-7
lines changed

docs/experimental/tasks-server.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ from starlette.routing import Mount
418418
from mcp.server import Server
419419
from mcp.server.experimental.task_context import ServerTaskContext
420420
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
421+
from mcp.server.transport_security import TransportSecuritySettings
421422
from mcp.types import (
422423
CallToolResult, CreateTaskResult, TextContent, Tool, ToolExecution, TASK_REQUIRED,
423424
)
@@ -463,7 +464,13 @@ async def handle_tool(name: str, arguments: dict) -> CallToolResult | CreateTask
463464

464465

465466
def create_app():
466-
session_manager = StreamableHTTPSessionManager(app=server)
467+
session_manager = StreamableHTTPSessionManager(
468+
app=server,
469+
security_settings=TransportSecuritySettings(
470+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
471+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
472+
),
473+
)
467474

468475
@asynccontextmanager
469476
async def lifespan(app: Starlette) -> AsyncIterator[None]:

examples/servers/simple-pagination/mcp_simple_pagination/server.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,18 @@ def main(port: int, transport: str) -> int:
163163

164164
if transport == "sse":
165165
from mcp.server.sse import SseServerTransport
166+
from mcp.server.transport_security import TransportSecuritySettings
166167
from starlette.applications import Starlette
167168
from starlette.responses import Response
168169
from starlette.routing import Mount, Route
169170

170-
sse = SseServerTransport("/messages/")
171+
sse = SseServerTransport(
172+
"/messages/",
173+
security_settings=TransportSecuritySettings(
174+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
175+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
176+
),
177+
)
171178

172179
async def handle_sse(request: Request):
173180
async with sse.connect_sse(request.scope, request.receive, request._send) as streams: # type: ignore[reportPrivateUsage]

examples/servers/simple-prompt/mcp_simple_prompt/server.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,18 @@ def main(port: int, transport: str) -> int:
8585

8686
if transport == "sse":
8787
from mcp.server.sse import SseServerTransport
88+
from mcp.server.transport_security import TransportSecuritySettings
8889
from starlette.applications import Starlette
8990
from starlette.responses import Response
9091
from starlette.routing import Mount, Route
9192

92-
sse = SseServerTransport("/messages/")
93+
sse = SseServerTransport(
94+
"/messages/",
95+
security_settings=TransportSecuritySettings(
96+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
97+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
98+
),
99+
)
93100

94101
async def handle_sse(request: Request):
95102
async with sse.connect_sse(request.scope, request.receive, request._send) as streams: # type: ignore[reportPrivateUsage]

examples/servers/simple-resource/mcp_simple_resource/server.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,18 @@ def main(port: int, transport: str) -> int:
7878

7979
if transport == "sse":
8080
from mcp.server.sse import SseServerTransport
81+
from mcp.server.transport_security import TransportSecuritySettings
8182
from starlette.applications import Starlette
8283
from starlette.responses import Response
8384
from starlette.routing import Mount, Route
8485

85-
sse = SseServerTransport("/messages/")
86+
sse = SseServerTransport(
87+
"/messages/",
88+
security_settings=TransportSecuritySettings(
89+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
90+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
91+
),
92+
)
8693

8794
async def handle_sse(request: Request):
8895
async with sse.connect_sse(request.scope, request.receive, request._send) as streams: # type: ignore[reportPrivateUsage]

examples/servers/simple-streamablehttp-stateless/mcp_simple_streamablehttp_stateless/server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from mcp import types
99
from mcp.server import Server, ServerRequestContext
1010
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
11+
from mcp.server.transport_security import TransportSecuritySettings
1112
from starlette.applications import Starlette
1213
from starlette.middleware.cors import CORSMiddleware
1314
from starlette.routing import Mount
@@ -110,6 +111,10 @@ def main(
110111
event_store=None,
111112
json_response=json_response,
112113
stateless=True,
114+
security_settings=TransportSecuritySettings(
115+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
116+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
117+
),
113118
)
114119

115120
async def handle_streamable_http(scope: Scope, receive: Receive, send: Send) -> None:

examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from mcp import types
88
from mcp.server import Server, ServerRequestContext
99
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
10+
from mcp.server.transport_security import TransportSecuritySettings
1011
from starlette.applications import Starlette
1112
from starlette.middleware.cors import CORSMiddleware
1213
from starlette.routing import Mount
@@ -132,6 +133,10 @@ def main(
132133
app=app,
133134
event_store=event_store, # Enable resumability
134135
json_response=json_response,
136+
security_settings=TransportSecuritySettings(
137+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
138+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
139+
),
135140
)
136141

137142
# ASGI handler for streamable HTTP connections

examples/servers/simple-task-interactive/mcp_simple_task_interactive/server.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from mcp.server import Server, ServerRequestContext
1717
from mcp.server.experimental.task_context import ServerTaskContext
1818
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
19+
from mcp.server.transport_security import TransportSecuritySettings
1920
from starlette.applications import Starlette
2021
from starlette.routing import Mount
2122

@@ -149,7 +150,13 @@ async def app_lifespan(app: Starlette) -> AsyncIterator[None]:
149150
@click.command()
150151
@click.option("--port", default=8000, help="Port to listen on")
151152
def main(port: int) -> int:
152-
session_manager = StreamableHTTPSessionManager(app=server)
153+
session_manager = StreamableHTTPSessionManager(
154+
app=server,
155+
security_settings=TransportSecuritySettings(
156+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
157+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
158+
),
159+
)
153160
starlette_app = create_app(session_manager)
154161
print(f"Starting server on http://localhost:{port}/mcp")
155162
uvicorn.run(starlette_app, host="127.0.0.1", port=port)

examples/servers/simple-task/mcp_simple_task/server.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from mcp.server import Server, ServerRequestContext
1111
from mcp.server.experimental.task_context import ServerTaskContext
1212
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
13+
from mcp.server.transport_security import TransportSecuritySettings
1314
from starlette.applications import Starlette
1415
from starlette.routing import Mount
1516

@@ -69,7 +70,13 @@ async def work(task: ServerTaskContext) -> types.CallToolResult:
6970
@click.command()
7071
@click.option("--port", default=8000, help="Port to listen on")
7172
def main(port: int) -> int:
72-
session_manager = StreamableHTTPSessionManager(app=server)
73+
session_manager = StreamableHTTPSessionManager(
74+
app=server,
75+
security_settings=TransportSecuritySettings(
76+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
77+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
78+
),
79+
)
7380

7481
@asynccontextmanager
7582
async def app_lifespan(app: Starlette) -> AsyncIterator[None]:

examples/servers/simple-tool/mcp_simple_tool/server.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,18 @@ def main(port: int, transport: str) -> int:
6767

6868
if transport == "sse":
6969
from mcp.server.sse import SseServerTransport
70+
from mcp.server.transport_security import TransportSecuritySettings
7071
from starlette.applications import Starlette
7172
from starlette.responses import Response
7273
from starlette.routing import Mount, Route
7374

74-
sse = SseServerTransport("/messages/")
75+
sse = SseServerTransport(
76+
"/messages/",
77+
security_settings=TransportSecuritySettings(
78+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
79+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
80+
),
81+
)
7582

7683
async def handle_sse(request: Request):
7784
async with sse.connect_sse(request.scope, request.receive, request._send) as streams: # type: ignore[reportPrivateUsage]

examples/servers/sse-polling-demo/mcp_sse_polling_demo/server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from mcp import types
2222
from mcp.server import Server, ServerRequestContext
2323
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
24+
from mcp.server.transport_security import TransportSecuritySettings
2425
from starlette.applications import Starlette
2526
from starlette.routing import Mount
2627
from starlette.types import Receive, Scope, Send
@@ -157,6 +158,10 @@ def main(port: int, log_level: str, retry_interval: int) -> int:
157158
app=app,
158159
event_store=event_store,
159160
retry_interval=retry_interval,
161+
security_settings=TransportSecuritySettings(
162+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
163+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
164+
),
160165
)
161166

162167
async def handle_streamable_http(scope: Scope, receive: Receive, send: Send) -> None:

0 commit comments

Comments
 (0)