Title: Propagate contextvars through anyio streams by aabmass · Pull Request #1996 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: Propagate contextvars through anyio streams by aabmass · Pull Request #1996 · modelcontextprotocol/python-sdk
X Title: Propagate contextvars through anyio streams by aabmass · Pull Request #1996 · modelcontextprotocol/python-sdk
Description: Fixes #1969 Part of #421 This PR propagates contextvars.Context through internal anyio memory streams and restores it in long running async tasks, so that they run with the correct context. Works for in memory, streamable http, and sse transports. For MCP clients, this captures the caller's active context and restores it before using the transport. For MCP servers, it captures the active context from the transport and restores it before using the Server's handlers Motivation and Context The goal is to make contextvars "work" with the MCP SDK as users expect. The main motivation is OpenTelemetry #421, but there are other use cases e.g. per-request auth, stdlib decimal, starlette-context, etc. How Has This Been Tested? Added several integration tests which are failing at main. I also manually tested e2e with the some of the samples by using OpenTelemetry httpx instrumentation (I can share screenshots). Tests failures at main ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ inline-snapshot ═══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════ ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Fix snapshots ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tests/test_context_propagation.py ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ @@ -83,7 +83,7 @@ │ │ │ │ result = await client.call_tool(name="my_tool") │ │ │ │ assert isinstance(result, types.CallToolResult) │ │ - assert result.content == snapshot([types.TextContent(text="client_value")]) │ │ + assert result.content == snapshot([types.TextContent(text="initial")]) │ │ │ │ │ │ class ContextVarMiddleware(BaseHTTPMiddleware): # pragma: lax no cover │ │ @@ -127,7 +127,7 @@ │ │ │ │ ): │ │ transport.client_header_value = "expected_value" │ │ result = await client.call_tool("my_tool") │ │ - assert result.content == snapshot([types.TextContent(text="from middleware CLIENT_HEADER=expected_value")]) │ │ + assert result.content == snapshot([types.TextContent(text="from middleware CLIENT_HEADER=initial")]) │ │ │ │ │ │ @pytest.mark.anyio │ │ @@ -142,7 +142,7 @@ │ │ │ │ ): │ │ with set_test_contextvar("client_value_list"): │ │ await client.list_tools() │ │ - assert transport.captured_context_var == snapshot("client_value_list") │ │ + assert transport.captured_context_var == snapshot("initial") │ │ │ │ with set_test_contextvar("client_value_call_tool"): │ │ await client.call_tool("my_tool") │ │ @@ -167,7 +167,7 @@ │ │ │ │ ) as client: │ │ transport.client_header_value = "expected_value" │ │ result = await client.call_tool("my_tool") │ │ - assert result.content == snapshot([types.TextContent(text="from middleware CLIENT_HEADER=expected_value")]) │ │ + assert result.content == snapshot([types.TextContent(text="from middleware CLIENT_HEADER=initial")]) │ │ │ │ │ │ @pytest.mark.anyio │ │ @@ -187,7 +187,7 @@ │ │ │ │ ) as client: │ │ with set_test_contextvar("client_value_list"): │ │ await client.list_tools() │ │ - assert transport.captured_context_var == snapshot("client_value_list") │ │ + assert transport.captured_context_var == snapshot("initial") │ │ │ │ with set_test_contextvar("client_value_call_tool"): │ │ await client.call_tool("my_tool") │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ These changes are not applied. Use --inline-snapshot=fix to apply them, or use the interactive mode with --inline-snapshot=review Breaking Changes No Types of changes Bug fix (non-breaking change which fixes an issue) New feature (non-breaking change which adds functionality) Breaking change (fix or feature that would cause existing functionality to change) Documentation update Checklist I have read the MCP Documentation My code follows the repository's style guidelines New and existing tests pass locally I have added appropriate error handling I have added or updated documentation as needed Additional context N/A
Open Graph Description: Fixes #1969 Part of #421 This PR propagates contextvars.Context through internal anyio memory streams and restores it in long running async tasks, so that they run with the correct context. Works f...
X Description: Fixes #1969 Part of #421 This PR propagates contextvars.Context through internal anyio memory streams and restores it in long running async tasks, so that they run with the correct context. Works f...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/pull/1996
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:0e7c3ea9-ce03-8bcd-e5f8-9ac2c1b51332 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | A41E:D1132:4252F8:600560:6A61E92B |
| html-safe-nonce | 25bc5fca862191a3e168003dc628bf04ebd72df44281ce3aab73cf4060258d2e |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBNDFFOkQxMTMyOjQyNTJGODo2MDA1NjA6NkE2MUU5MkIiLCJ2aXNpdG9yX2lkIjoiODk5NjI4ODY4MTI2NzYzMzA3IiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 787686d884ffa3f82cee02c6733e6e1572c786bb3980244937d3786772a817a6 |
| hovercard-subject-tag | pull_request:3247714680 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/modelcontextprotocol/python-sdk/pull/1996/files |
| twitter:image | https://avatars.githubusercontent.com/u/1510004?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/1510004?s=400&v=4 |
| og:image:alt | Fixes #1969 Part of #421 This PR propagates contextvars.Context through internal anyio memory streams and restores it in long running async tasks, so that they run with the correct context. Works f... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5dd1217fff37e297563b9d318ee02bf6378c8957c6195b77786bb8896fc9c95d |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/modelcontextprotocol/python-sdk git https://github.com/modelcontextprotocol/python-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 862584018 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/python-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 862584018 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/python-sdk |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 554667f6dda2a53c83687de64fabc457970f0f04 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width