Skip to content

Commit 3dfb2d7

Browse files
committed
feat(auth): enforce set-once semantics on ServerSession.tenant_id
Make the tenant_id setter raise ValueError if attempting to change to a different value once already set. This prevents accidental tenant reassignment which could be a security issue. Setting to the same value is allowed (idempotent).
1 parent ec564e7 commit 3dfb2d7

2 files changed

Lines changed: 32 additions & 13 deletions

File tree

src/mcp/server/session.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,18 @@ def tenant_id(self) -> str | None:
116116

117117
@tenant_id.setter
118118
def tenant_id(self, value: str | None) -> None:
119-
"""Set the tenant_id for this session."""
119+
"""Set the tenant_id for this session (set-once).
120+
121+
Once a session is bound to a tenant, the tenant_id cannot be changed.
122+
This prevents accidental tenant reassignment which could be a security issue.
123+
124+
Raises:
125+
ValueError: If tenant_id is already set to a different value.
126+
"""
127+
if self._tenant_id is not None and value != self._tenant_id:
128+
raise ValueError(
129+
f"Cannot change tenant_id from '{self._tenant_id}' to '{value}': session is already bound to a tenant"
130+
)
120131
self._tenant_id = value
121132

122133
@property

tests/server/test_multi_tenancy_session.py

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def test_server_request_context_inherits_tenant_id_from_base():
117117

118118
@pytest.mark.anyio
119119
async def test_server_session_tenant_id_property(init_options: InitializationOptions):
120-
"""Test ServerSession tenant_id property and setter."""
120+
"""Test ServerSession tenant_id property with set-once semantics."""
121121
server_to_client_send, server_to_client_recv = anyio.create_memory_object_stream[SessionMessage](1)
122122
client_to_server_send, client_to_server_recv = anyio.create_memory_object_stream[SessionMessage | Exception](1)
123123

@@ -134,13 +134,20 @@ async def test_server_session_tenant_id_property(init_options: InitializationOpt
134134
session.tenant_id = "tenant-123"
135135
assert session.tenant_id == "tenant-123"
136136

137-
# Can change tenant_id
138-
session.tenant_id = "tenant-456"
139-
assert session.tenant_id == "tenant-456"
137+
# Setting to the same value is allowed
138+
session.tenant_id = "tenant-123"
139+
assert session.tenant_id == "tenant-123"
140140

141-
# Can reset to None
142-
session.tenant_id = None
143-
assert session.tenant_id is None
141+
# Cannot change to a different value
142+
with pytest.raises(ValueError, match="Cannot change tenant_id"):
143+
session.tenant_id = "tenant-456"
144+
145+
# Cannot reset to None once set
146+
with pytest.raises(ValueError, match="Cannot change tenant_id"):
147+
session.tenant_id = None
148+
149+
# Original value is preserved
150+
assert session.tenant_id == "tenant-123"
144151

145152

146153
def test_get_tenant_id_from_auth_context():
@@ -317,12 +324,13 @@ async def test_server_session_isolation_between_instances(init_options: Initiali
317324
assert session1.tenant_id == "tenant-alpha"
318325
assert session2.tenant_id == "tenant-beta"
319326

320-
# Modify one session's tenant_id
321-
session1.tenant_id = "tenant-gamma"
327+
# Attempting to change one session's tenant_id raises
328+
with pytest.raises(ValueError, match="Cannot change tenant_id"):
329+
session1.tenant_id = "tenant-gamma"
322330

323-
# Verify the other session is unaffected
324-
assert session1.tenant_id == "tenant-gamma"
325-
assert session2.tenant_id == "tenant-beta" # Still beta, not gamma
331+
# Both sessions retain their original values
332+
assert session1.tenant_id == "tenant-alpha"
333+
assert session2.tenant_id == "tenant-beta"
326334

327335

328336
@pytest.mark.anyio

0 commit comments

Comments
 (0)