Jeremiah Lowin commited on
Commit
910c765
·
unverified ·
2 Parent(s): 5603a974f48aed

Merge pull request #881 from jlowin/dev-20250619-173841

Browse files
src/fastmcp/server/context.py CHANGED
@@ -178,6 +178,37 @@ class Context:
178
  """Get the unique ID for this request."""
179
  return str(self.request_context.request_id)
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  @property
182
  def session(self):
183
  """Access to the underlying session for advanced usage."""
 
178
  """Get the unique ID for this request."""
179
  return str(self.request_context.request_id)
180
 
181
+ @property
182
+ def session_id(self) -> str | None:
183
+ """Get the MCP session ID for HTTP transports.
184
+
185
+ Returns the session ID that can be used as a key for session-based
186
+ data storage (e.g., Redis) to share data between tool calls within
187
+ the same client session.
188
+
189
+ Returns:
190
+ The session ID for HTTP transports (SSE, StreamableHTTP), or None
191
+ for stdio and in-memory transports which don't use session IDs.
192
+
193
+ Example:
194
+ ```python
195
+ @server.tool
196
+ def store_data(data: dict, ctx: Context) -> str:
197
+ if session_id := ctx.session_id:
198
+ redis_client.set(f"session:{session_id}:data", json.dumps(data))
199
+ return f"Data stored for session {session_id}"
200
+ return "No session ID available (stdio/memory transport)"
201
+ ```
202
+ """
203
+ try:
204
+ from fastmcp.server.dependencies import get_http_headers
205
+
206
+ headers = get_http_headers(include_all=True)
207
+ return headers.get("mcp-session-id")
208
+ except RuntimeError:
209
+ # No HTTP context available (stdio/in-memory transport)
210
+ return None
211
+
212
  @property
213
  def session(self):
214
  """Access to the underlying session for advanced usage."""
tests/server/test_context.py CHANGED
@@ -86,3 +86,40 @@ class TestParseModelPreferences:
86
  def test_parse_model_preferences_invalid_type(self, context):
87
  with pytest.raises(ValueError):
88
  context._parse_model_preferences(123)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  def test_parse_model_preferences_invalid_type(self, context):
87
  with pytest.raises(ValueError):
88
  context._parse_model_preferences(123)
89
+
90
+
91
+ class TestSessionId:
92
+ def test_session_id_with_http_headers(self, context):
93
+ """Test that session_id returns the value from mcp-session-id header."""
94
+ mock_headers = {"mcp-session-id": "test-session-123"}
95
+
96
+ with patch(
97
+ "fastmcp.server.dependencies.get_http_headers", return_value=mock_headers
98
+ ):
99
+ assert context.session_id == "test-session-123"
100
+
101
+ def test_session_id_without_http_headers(self, context):
102
+ """Test that session_id returns None when no HTTP headers are available."""
103
+ with patch(
104
+ "fastmcp.server.dependencies.get_http_headers",
105
+ side_effect=RuntimeError("No active HTTP request found."),
106
+ ):
107
+ assert context.session_id is None
108
+
109
+ def test_session_id_with_missing_header(self, context):
110
+ """Test that session_id returns None when mcp-session-id header is missing."""
111
+ mock_headers = {"other-header": "value"}
112
+
113
+ with patch(
114
+ "fastmcp.server.dependencies.get_http_headers", return_value=mock_headers
115
+ ):
116
+ assert context.session_id is None
117
+
118
+ def test_session_id_with_empty_header(self, context):
119
+ """Test that session_id returns None when mcp-session-id header is empty."""
120
+ mock_headers = {"mcp-session-id": ""}
121
+
122
+ with patch(
123
+ "fastmcp.server.dependencies.get_http_headers", return_value=mock_headers
124
+ ):
125
+ assert context.session_id == "" # Empty string is still returned as-is