Skip to content

Commit 12da0c9

Browse files
committed
feat: hosting on single-tenant platforms such Amazon Bedrock AgentCore Runtime
1 parent ca34666 commit 12da0c9

File tree

2 files changed

+27
-0
lines changed

2 files changed

+27
-0
lines changed

src/mcp/server/fastmcp/server.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ class Settings(BaseSettings, Generic[LifespanResultT]):
8484
json_response: bool
8585
stateless_http: bool
8686
"""Define if the server should create a new transport per request."""
87+
single_tenant: bool
88+
"""Define if the server should use only one transport for all requests."""
8789

8890
# resource settings
8991
warn_on_duplicate_resources: bool
@@ -139,6 +141,7 @@ def __init__(
139141
streamable_http_path: str = "/mcp",
140142
json_response: bool = False,
141143
stateless_http: bool = False,
144+
single_tenant: bool = False,
142145
warn_on_duplicate_resources: bool = True,
143146
warn_on_duplicate_tools: bool = True,
144147
warn_on_duplicate_prompts: bool = True,
@@ -158,6 +161,7 @@ def __init__(
158161
streamable_http_path=streamable_http_path,
159162
json_response=json_response,
160163
stateless_http=stateless_http,
164+
single_tenant=single_tenant,
161165
warn_on_duplicate_resources=warn_on_duplicate_resources,
162166
warn_on_duplicate_tools=warn_on_duplicate_tools,
163167
warn_on_duplicate_prompts=warn_on_duplicate_prompts,
@@ -868,6 +872,7 @@ def streamable_http_app(self) -> Starlette:
868872
event_store=self._event_store,
869873
json_response=self.settings.json_response,
870874
stateless=self.settings.stateless_http, # Use the stateless setting
875+
single_tenant=self.settings.single_tenant,
871876
security_settings=self.settings.transport_security,
872877
)
873878

src/mcp/server/streamable_http_manager.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ class StreamableHTTPSessionManager:
5151
json_response: Whether to use JSON responses instead of SSE streams
5252
stateless: If True, creates a completely fresh transport for each request
5353
with no session tracking or state persistence between requests.
54+
single_tenant: If True, only one transport will be created to process the entire
55+
every request, regardless of the MCP session id. This is useful for
56+
hosting platforms where the MCP server is launched in a single-tenant box.
5457
"""
5558

5659
def __init__(
@@ -59,12 +62,18 @@ def __init__(
5962
event_store: EventStore | None = None,
6063
json_response: bool = False,
6164
stateless: bool = False,
65+
single_tenant: bool = False,
6266
security_settings: TransportSecuritySettings | None = None,
6367
):
6468
self.app = app
6569
self.event_store = event_store
6670
self.json_response = json_response
6771
self.stateless = stateless
72+
self.single_tenant = single_tenant
73+
if self.stateless and self.single_tenant:
74+
# A single-tenant server must be stateful, but stateful server does not
75+
# have to be single tenant.
76+
raise ValueError("A single-tenant server must stateful.")
6877
self.security_settings = security_settings
6978

7079
# Session tracking (only used if not stateless)
@@ -209,6 +218,19 @@ async def _handle_stateful_request(
209218
request = Request(scope, receive)
210219
request_mcp_session_id = request.headers.get(MCP_SESSION_ID_HEADER)
211220

221+
if self.single_tenant and self._server_instances:
222+
# being single_tenant means that there is only one ASGI server for the entire application.
223+
# that server is used to process all the mcp requests because the hosting platform
224+
# is already distributing the request to the box where the single-tenant mcp server runs.
225+
assert len(self._server_instances) == 1
226+
# hosting platforms might exposes a different mcp session ID hence
227+
# we take the first key of server instances as the mcp session id
228+
request_mcp_session_id = next(iter(self._server_instances.keys()))
229+
headers = dict(scope["headers"])
230+
# Also need to reset the incoming request mcp session id to this existing session id
231+
headers[MCP_SESSION_ID_HEADER.encode("latin-1")] = request_mcp_session_id.encode("latin-1")
232+
scope["headers"] = list(headers.items())
233+
212234
# Existing session case
213235
if request_mcp_session_id is not None and request_mcp_session_id in self._server_instances:
214236
transport = self._server_instances[request_mcp_session_id]

0 commit comments

Comments
 (0)