From fc004d5345323386ee4cf48e7e047cf5dbba01d3 Mon Sep 17 00:00:00 2001 From: Andrey Fedorov Date: Fri, 31 Jul 2026 14:46:18 -0400 Subject: [PATCH] feat(mcp)!: migrate to MCP SDK 2.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SDK 2.0 removes `mcp.server.fastmcp`. Port the adapter to the renamed high-level API: FastMCP -> MCPServer (mcp.server.mcpserver) fastmcp.exceptions.ToolError -> mcpserver.exceptions.ToolError stateless_http/json_response/ -> streamable_http_app() kwargs transport_security, host, port mcp._mcp_server.version = ... -> MCPServer(version=...) Two things this cleans up. The private `_mcp_server` reach-in for serverInfo.version is gone — 2.0 takes `version=` on the constructor and plumbs it to the handshake, so the comment explaining the reach-in goes with it. And `http_app()` no longer reads the endpoint path back off server state; it takes `streamable_http_path` as an argument, which is also what let the slash-agnostic test drop its FASTMCP_-env caveat. `transport_security` must now be passed to `streamable_http_app()` explicitly: 2.0 enables DNS-rebinding protection by default, and without it every request on a hosted domain answers 421 "Invalid Host header". Covered by the existing HTTP transport tests. Behaviour is unchanged — same tools, same resources, same response shapes, both `/mcp` and `/mcp/` still served directly. The one user-visible loss is the SDK's undocumented FASTMCP_* environment variables; --host/--port keep their 127.0.0.1:8000 defaults. New transitive dependencies: mcp-types, opentelemetry-api, truststore, and httpx2/httpcore2 (Pydantic's httpx fork). Floors pydantic at 2.12. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 9 +++++ dev/api_v3_plan.md | 2 +- dev/architecture.md | 6 ++-- dev/deployment.md | 2 +- dev/developer_guide.md | 4 +-- pyproject.toml | 7 ++-- src/idc_api/mcp/server.py | 60 +++++++++++++++---------------- tests/conftest.py | 11 +++--- tests/test_mcp.py | 23 ++++++------ uv.lock | 76 +++++++++++++++++++++++++++++++++------ 10 files changed, 129 insertions(+), 71 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 209804d..7fe350e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,15 @@ Refactors, CI, and formatting land in the git history, not here. ## [Unreleased] +### Changed + +- The MCP server now requires **MCP SDK 2.0** (`mcp>=2.0.0`). Tools, resources, the `/mcp` + endpoint and every response shape are unchanged — this is an SDK-internal rename + (`FastMCP` → `MCPServer`). One config surface goes away: the SDK's undocumented + `FASTMCP_HOST` / `FASTMCP_PORT` / `FASTMCP_STREAMABLE_HTTP_PATH` environment variables no + longer have any effect. Use `idc-mcp --http --host … --port …`, which is what the deployment + has always passed; the defaults (`127.0.0.1:8000`) are unchanged. + ### Fixed - The `/v3/viewer-url` OpenAPI examples (the values Swagger UI's "Try it out" pre-fills) used a StudyInstanceUID and SeriesInstanceUID that are not present in IDC, so running the example returned a `not_found` error instead of a viewer link. Both now use resolvable UIDs. diff --git a/dev/api_v3_plan.md b/dev/api_v3_plan.md index 679f94b..3fa0c7c 100644 --- a/dev/api_v3_plan.md +++ b/dev/api_v3_plan.md @@ -218,7 +218,7 @@ general MCP guidance to treat all tool inputs as untrusted and apply defense in ## Tech stack & deployment - **Python 3.11+**, **FastAPI** + uvicorn/gunicorn, **Pydantic v2**. FastAPI gives OpenAPI docs + generated client SDKs for free (serves the non-Python/web-integration audience). -- **MCP:** the official `mcp` Python SDK (FastMCP server API). Hand-author tools over +- **MCP:** the official `mcp` Python SDK (`MCPServer` server API). Hand-author tools over `core/` rather than auto-converting REST routes — auto-conversion yields generic, poorly-described tools. Same image serves remote MCP (HTTP) next to REST; local MCP runs via **stdio** from a `pip install idc-mcp` / `uvx` entrypoint. diff --git a/dev/architecture.md b/dev/architecture.md index ee477af..18ed3ec 100644 --- a/dev/architecture.md +++ b/dev/architecture.md @@ -25,7 +25,7 @@ for *how to work in it* see [`dev/developer_guide.md`](developer_guide.md). ┌──────────────────────┐ ┌──────────────────────┐ │ REST adapter │ │ MCP adapter │ │ src/idc_api/rest │ │ src/idc_api/mcp │ - │ FastAPI routes │ │ FastMCP tools + │ + │ FastAPI routes │ │ MCPServer tools + │ │ │ │ resources │ └──────────┬───────────┘ └───────────┬──────────┘ │ (thin: validate + delegate) │ @@ -186,7 +186,7 @@ bytes through the service, and identical behavior local or hosted. (A local-only endpoint/tool existed pre-3.0.0 and was removed in beta; see CHANGELOG.) The hosted HTTP transport is configured **stateless** (`stateless_http=True`, -`json_response=True` on the `FastMCP(...)` constructor) so each request is self-contained and +`json_response=True` on the `streamable_http_app(...)` call) so each request is self-contained and the service autoscales across instances like the REST API — no sticky sessions. This is safe because the server exposes only client-initiated tools + static resources (none of the server→client features that require a persistent session). The flags affect only the HTTP @@ -212,7 +212,7 @@ rationale. |---|---|---| | Language/runtime | Python 3.11+ (dev on 3.12) | matches idc-index | | Web framework | FastAPI + uvicorn | free OpenAPI/Swagger + generated SDKs | -| MCP | official `mcp` SDK (FastMCP) | hand-authored tools, not auto-converted routes | +| MCP | official `mcp` SDK (`MCPServer`) | hand-authored tools, not auto-converted routes | | Query engine | DuckDB over Parquet | ms latency, no GCP/auth, matches idc-index | | Models/validation | Pydantic v2 | one contract for both adapters | | Packaging | `uv` + lockfile, hatchling src-layout | reproducible installs | diff --git a/dev/deployment.md b/dev/deployment.md index f14690a..479e5b3 100644 --- a/dev/deployment.md +++ b/dev/deployment.md @@ -581,7 +581,7 @@ autoscales like REST. Caveats: - **Retrieval is manifests/URLs only** on every transport — the server never transfers files; callers download directly from the public S3/GCS buckets (`idc` CLI / s5cmd). -- **Both `…/mcp` and `…/mcp/` are served directly** — no redirect either way. FastMCP registers one +- **Both `…/mcp` and `…/mcp/` are served directly** — no redirect either way. The SDK registers one exact-path route at `/mcp`, so out of the box Starlette 307s `/mcp/` onto it; `http_app()` in [mcp/server.py](../src/idc_api/mcp/server.py) registers the trailing-slash form as a real route and turns `redirect_slashes` off, because making an RPC client replay its POST body across a diff --git a/dev/developer_guide.md b/dev/developer_guide.md index 953bee6..851b823 100644 --- a/dev/developer_guide.md +++ b/dev/developer_guide.md @@ -57,7 +57,7 @@ uv run --directory . pytest tests/test_backend_guards.py -q # one file | [test_clinical.py](../tests/test_clinical.py) | Clinical tables registered under the `clinical` schema, hidden from `list_tables`, discoverable/readable, joinable to `index` | Fixtures live in [tests/conftest.py](../tests/conftest.py): `ctx` (the core -`AppContext`), `client` (FastAPI `TestClient`), and `parse_mcp` (normalizes a FastMCP +`AppContext`), `client` (FastAPI `TestClient`), and `parse_mcp` (normalizes an MCP `call_tool` return into plain Python). ### Continuous integration @@ -93,7 +93,7 @@ src/idc_api/ duckdb_backend.py # read-only DuckDB over idc-index Parquet services/ # discovery, cohort, query, clinical, manifest, viewer, citations, licenses rest/app.py # FastAPI app + routes - mcp/server.py # FastMCP tools + resources + entrypoint + mcp/server.py # MCPServer tools + resources + entrypoint tests/ # pytest suite ``` diff --git a/pyproject.toml b/pyproject.toml index ee59836..cd52a4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,11 +19,8 @@ dependencies = [ # REST adapter "fastapi>=0.139.1", "uvicorn[standard]>=0.51.0", - # MCP adapter (official SDK; bundles FastMCP server API). - # Capped below 2.0: that release removes `mcp.server.fastmcp` entirely (FastMCP -> - # MCPServer) and moves transport config off the constructor, so the adapter does not - # import under it. Migration is a deliberate PR, not a dependency bump. - "mcp>=1.28.1,<2", + # MCP adapter (official SDK; bundles the MCPServer high-level API) + "mcp>=2.0.0", ] [project.optional-dependencies] diff --git a/src/idc_api/mcp/server.py b/src/idc_api/mcp/server.py index e5cffde..8b673a1 100644 --- a/src/idc_api/mcp/server.py +++ b/src/idc_api/mcp/server.py @@ -25,8 +25,8 @@ import time from typing import Any -from mcp.server.fastmcp import FastMCP -from mcp.server.fastmcp.exceptions import ToolError +from mcp.server.mcpserver import MCPServer +from mcp.server.mcpserver.exceptions import ToolError from mcp.server.transport_security import TransportSecuritySettings from starlette.applications import Starlette from starlette.routing import Route @@ -67,7 +67,9 @@ # stateless_http=True / json_response=True make the hosted (streamable-http) transport -# horizontally scalable on Cloud Run. +# horizontally scalable on Cloud Run. Both are passed to streamable_http_app() in http_app() +# below — as of MCP SDK 2.0 the transport settings live on that call rather than on the server +# object, so stdio mode never constructs them at all. # # Why: MCP's streamable-HTTP transport is normally *session-oriented* — the server keeps each # client's session state in the memory of the process that handled `initialize`, and every @@ -109,25 +111,18 @@ def _server_version() -> str: Base is the installed package version; when a deploy sets IDC_API_BUILD (e.g. a short git SHA) it is appended as a PEP 440 local segment so the string moves on every redeploy — this is how a caller confirms which build a hosted instance is actually running. Without setting - `version=`, FastMCP would fall back to the MCP SDK's own version, which says nothing about - this server. Shared with the REST adapter (which reports the same string via /v3/version and + `version=`, the SDK would fall back to its own version, which says nothing about this + server. Shared with the REST adapter (which reports the same string via /v3/version and /v3/openapi.json) through core.version. """ return core_version.server_version() -mcp = FastMCP( +mcp = MCPServer( "IDC (Imaging Data Commons)", instructions=INSTRUCTIONS, - stateless_http=True, - json_response=True, - transport_security=_transport_security(), + version=_server_version(), ) -# FastMCP doesn't forward a version to the low-level server; left unset, the initialize -# handshake reports the MCP SDK's own version (meaningless for tracking this server). Set it on -# the underlying server so serverInfo.version reflects our build. No public accessor exists in -# this SDK version, hence the _mcp_server reach-in. -mcp._mcp_server.version = _server_version() ctx = AppContext() @@ -573,11 +568,11 @@ def schema_resource(table: str) -> str: # --- entrypoint --------------------------------------------------------------------------- -def http_app(server: FastMCP | None = None) -> Starlette: +def http_app(server: MCPServer | None = None, streamable_http_path: str = "/mcp") -> Starlette: """Starlette app for the hosted (streamable-http) transport. - Same app FastMCP builds, with one fix: serve the endpoint at both ``/mcp`` and ``/mcp/`` - rather than redirecting between them. FastMCP registers a single *exact-path* Route at + Same app the SDK builds, with one fix: serve the endpoint at both ``/mcp`` and ``/mcp/`` + rather than redirecting between them. The SDK registers a single *exact-path* Route at ``streamable_http_path`` (default ``/mcp``), so Starlette's ``redirect_slashes`` answers a request for ``/mcp/`` with a 307 to ``/mcp``. That redirect is correct HTTP but a poor fit for an RPC endpoint fronted by a load balancer: it makes every ``/mcp/`` client re-issue its @@ -586,18 +581,25 @@ def http_app(server: FastMCP | None = None) -> Starlette: LB's ``/mcp``, ``/mcp/*`` rule) uses is the path that gets served. Slash-agnostic in the configured path: whether ``streamable_http_path`` is ``/mcp`` or - ``/mcp/``, FastMCP registers whichever spelling it was given and we add the other. + ``/mcp/``, the SDK registers whichever spelling it was given and we add the other. + + ``transport_security`` must be passed here explicitly: the SDK enables DNS-rebinding + protection by default, which answers every request on a hosted domain with HTTP 421. """ server = server or mcp - app = server.streamable_http_app() - configured = server.settings.streamable_http_path + app = server.streamable_http_app( + streamable_http_path=streamable_http_path, + stateless_http=True, + json_response=True, + transport_security=_transport_security(), + ) # "/mcp" and "/mcp/" are the same endpoint; a configured "/" has no distinct twin. - base = configured.rstrip("/") + base = streamable_http_path.rstrip("/") spellings = [base, f"{base}/"] if base else ["/"] found = {r.path: r for r in app.router.routes if isinstance(r, Route) and r.path in spellings} if not found: raise RuntimeError( - f"FastMCP registered no route at {configured!r} — the SDK's routing changed and " + f"the SDK registered no route at {streamable_http_path!r} — its routing changed and " "http_app() can no longer find the endpoint to alias." ) # methods=None on the source Route: the endpoint is an ASGI app, so it matches every method @@ -624,24 +626,22 @@ def main() -> None: action="store_true", help="Serve over streamable-http (hosted/shared) instead of stdio (local).", ) - parser.add_argument("--host", default=None) - parser.add_argument("--port", type=int, default=None) + # Defaults match what the SDK's own settings used to supply; as of MCP SDK 2.0 host/port are + # no longer server state, so they live here. + parser.add_argument("--host", default="127.0.0.1") + parser.add_argument("--port", type=int, default=8000) args = parser.parse_args() if args.http: import uvicorn - if args.host: - mcp.settings.host = args.host - if args.port: - mcp.settings.port = args.port # Equivalent to mcp.run(transport="streamable-http"), but serving http_app() so the # trailing-slash route above is present. uvicorn's proxy_headers default (with # FORWARDED_ALLOW_IPS from the environment) is unchanged. uvicorn.run( http_app(), - host=mcp.settings.host, - port=mcp.settings.port, + host=args.host, + port=args.port, log_level=mcp.settings.log_level.lower(), ) else: diff --git a/tests/conftest.py b/tests/conftest.py index 78fb091..4476fe6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -24,11 +24,12 @@ def client(): def mcp_json(result) -> object: - """Normalize a FastMCP ``call_tool`` return into plain Python (dict/list).""" - if isinstance(result, dict): - # Structured-output dict; FastMCP wraps bare list/scalar returns under "result". - return result.get("result", result) - for block in result: # Sequence[ContentBlock] + """Normalize an MCP ``call_tool`` return (``CallToolResult``) into plain Python.""" + structured = result.structured_content + if structured is not None: + # The SDK wraps bare list/scalar returns under "result"; dict returns come through as-is. + return structured.get("result", structured) + for block in result.content: # Sequence[ContentBlock] text = getattr(block, "text", None) if text is not None: return json.loads(text) diff --git a/tests/test_mcp.py b/tests/test_mcp.py index cc4b562..749b2e9 100644 --- a/tests/test_mcp.py +++ b/tests/test_mcp.py @@ -70,7 +70,7 @@ async def test_run_sql(server, parse_mcp): async def test_run_sql_error_carries_engine_hint(server): # A SQL mistake must return DuckDB's self-correction hints (candidate columns, # "Did you mean") to the agent — not the guard's generic "Internal error" fallback. - from mcp.server.fastmcp.exceptions import ToolError + from mcp.server.mcpserver.exceptions import ToolError with pytest.raises(ToolError) as exc: await server.call_tool("run_sql", {"sql": "SELECT no_such_column FROM index"}) @@ -80,7 +80,7 @@ async def test_run_sql_error_carries_engine_hint(server): async def test_error_is_clean(server): - from mcp.server.fastmcp.exceptions import ToolError + from mcp.server.mcpserver.exceptions import ToolError with pytest.raises(ToolError) as exc: await server.call_tool("get_collection", {"collection_id": "__nope__"}) @@ -90,21 +90,20 @@ async def test_error_is_clean(server): def test_server_version_is_our_build_not_sdk_fallback(server): """initialize must advertise our package version, not the MCP SDK's own version. - The low-level server defaults version to None, which makes the handshake echo the `mcp` - SDK version — useless for tracking this server. We set it explicitly; guard that wiring - (and the private _mcp_server reach-in it depends on) against SDK changes. + Left unset, the handshake echoes the `mcp` SDK version — useless for tracking this server. + We pass it to the constructor; guard that wiring against SDK changes. """ from importlib.metadata import version from idc_api.mcp.server import _server_version - advertised = server._mcp_server.version + advertised = server.version assert advertised, "serverInfo.version unset → SDK-version fallback" assert advertised == _server_version() assert advertised.startswith(version("idc-api")) assert advertised != version("mcp") # the value the initialize handshake actually returns - opts = server._mcp_server.create_initialization_options() + opts = server._lowlevel_server.create_initialization_options() assert opts.server_version == advertised @@ -131,7 +130,7 @@ async def test_resources(server): def test_http_app_serves_both_slash_forms_without_redirect(): - """`/mcp` and `/mcp/` both answer directly. FastMCP alone 307s the trailing-slash form to + """`/mcp` and `/mcp/` both answer directly. The SDK alone 307s the trailing-slash form to the bare one, which forces clients and proxies to replay the POST body.""" from fastapi.testclient import TestClient @@ -164,15 +163,13 @@ def test_http_app_serves_both_slash_forms_without_redirect(): @pytest.mark.parametrize("configured", ["/mcp", "/mcp/"]) def test_http_app_is_slash_agnostic_in_configured_path(configured): - """Both spellings are routed whichever one FastMCP was configured with. Only reachable by - editing the FastMCP(...) call — FASTMCP_STREAMABLE_HTTP_PATH cannot reach it, since - FastMCP.__init__ always passes streamable_http_path= explicitly and that outranks env.""" - from mcp.server.fastmcp import FastMCP + """Both spellings are routed whichever one the endpoint path is configured with.""" + from mcp.server.mcpserver import MCPServer from starlette.routing import Route from idc_api.mcp.server import http_app - app = http_app(FastMCP("probe", streamable_http_path=configured)) + app = http_app(MCPServer("probe"), streamable_http_path=configured) paths = {r.path for r in app.router.routes if isinstance(r, Route)} assert {"/mcp", "/mcp/"} <= paths assert app.router.redirect_slashes is False diff --git a/uv.lock b/uv.lock index 1d93139..657f915 100644 --- a/uv.lock +++ b/uv.lock @@ -447,6 +447,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, ] +[[package]] +name = "httpcore2" +version = "2.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h11" }, + { name = "truststore" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/39/a8/20ed1ed79cbc2ecdf5301c0968ab7c85547212e2a7bd126ddd2d986e206e/httpcore2-2.9.1.tar.gz", hash = "sha256:4d8acbf8b306f48c9d6046591fd5ba4037d1b1b1000d140fc2c3eab1e9a0c0e2", size = 67089, upload-time = "2026-07-24T09:21:03.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/fb/46c52b781975c335a2bcf1072c7bbc007cbdc8d674217f5ee1daba2c848b/httpcore2-2.9.1-py3-none-any.whl", hash = "sha256:6182472379e855fe4221246a2bb7ecede403bc61c6798062ae1787d051ccde26", size = 82809, upload-time = "2026-07-24T09:21:01.178Z" }, +] + [[package]] name = "httptools" version = "0.8.0" @@ -506,12 +519,19 @@ wheels = [ ] [[package]] -name = "httpx-sse" -version = "0.4.3" +name = "httpx2" +version = "2.9.1" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +dependencies = [ + { name = "anyio" }, + { name = "httpcore2" }, + { name = "idna" }, + { name = "truststore" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/21/14/38128fbafd7e0ed41d874df6c9a653d47c2d111cfe59e2b4ac95161b4abd/httpx2-2.9.1.tar.gz", hash = "sha256:1932a768737e3666291582833da748cc4e563c337cf96706fccc04fa6e58764a", size = 95458, upload-time = "2026-07-24T09:21:04.972Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, + { url = "https://files.pythonhosted.org/packages/13/b8/cfd91c4ab9134d386d48f0b6ac662ff3d4be6efdee59ee1c67ebc3c0487c/httpx2-2.9.1-py3-none-any.whl", hash = "sha256:1820fe14a9ab1107bfeff39259987429450b070ec0ff38cc87eb0d8c97fdc71a", size = 91191, upload-time = "2026-07-24T09:21:02.6Z" }, ] [[package]] @@ -546,7 +566,7 @@ requires-dist = [ { name = "fastapi", specifier = ">=0.139.1" }, { name = "httpx", marker = "extra == 'dev'", specifier = ">=0.27" }, { name = "idc-index", specifier = ">=0.12.4" }, - { name = "mcp", specifier = ">=1.28.1,<2" }, + { name = "mcp", specifier = ">=2.0.0" }, { name = "pip-audit", marker = "extra == 'dev'", specifier = ">=2.7" }, { name = "pyarrow", specifier = ">=25.0.0" }, { name = "pydantic", specifier = ">=2.7" }, @@ -660,15 +680,15 @@ wheels = [ [[package]] name = "mcp" -version = "1.28.1" +version = "2.0.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "anyio" }, - { name = "httpx" }, - { name = "httpx-sse" }, + { name = "httpx2" }, { name = "jsonschema" }, + { name = "mcp-types" }, + { name = "opentelemetry-api" }, { name = "pydantic" }, - { name = "pydantic-settings" }, { name = "pyjwt", extra = ["crypto"] }, { name = "python-multipart" }, { name = "pywin32", marker = "sys_platform == 'win32'" }, @@ -678,9 +698,22 @@ dependencies = [ { name = "typing-inspection" }, { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6e/77/9450b8f251a13affb6281997d0523c4615f8a8b35d0b21ff30db3a5aac9d/mcp-1.28.1.tar.gz", hash = "sha256:d51e36a5f5644faea4f85ea649bfffa6bc6c26770d42798ad6a3de3d2ba69683", size = 638501, upload-time = "2026-06-26T12:57:29.093Z" } +sdist = { url = "https://files.pythonhosted.org/packages/74/33/32d4dff2c95bb5d897c3ef4c83649a08996b17b58f0a326d2495d4c81179/mcp-2.0.0.tar.gz", hash = "sha256:0f440e735c13ece8bb19bc62cf0b86f4313448432fbb77d35e14034f4e050728", size = 1662284, upload-time = "2026-07-28T13:45:32.346Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e2/5e/d118fce19f87a2e7d8101c35c8ae0ec289098a4df0ff244cec23e415aca0/mcp-1.28.1-py3-none-any.whl", hash = "sha256:2726bca5e7193f61c5dde8b12500a6de2d9acf6d1a1c0be9e8c2e706437991df", size = 222620, upload-time = "2026-06-26T12:57:27.218Z" }, + { url = "https://files.pythonhosted.org/packages/67/72/7d7897418912c1d12e87556630dfb7bf0eac71160e9bef8b447960804ee3/mcp-2.0.0-py3-none-any.whl", hash = "sha256:1cb4c75d2d2c7b8c1d756355e5d82a39f2822cc7f13e22a2051d7ca3592349d6", size = 349980, upload-time = "2026-07-28T13:45:28.853Z" }, +] + +[[package]] +name = "mcp-types" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bb/56/9b8e1c152f61f6c6b07c4b5896c88c7d0ae90bac6ee6306f852fcc5c1eb0/mcp_types-2.0.0.tar.gz", hash = "sha256:d7d939b9285c9961ae8866ba75ef85da34d12bafe276efbf4eb6a131786d8379", size = 66632, upload-time = "2026-07-28T13:45:33.804Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f5/4c/c78d78c3d52b0ac594ad7cc8ef5972adfe070e3597a8a4c6ce0cd39196ea/mcp_types-2.0.0-py3-none-any.whl", hash = "sha256:6b2de797ca2797f568b79529e1b25948e34de511bcc0bd82fef1039a6d1b8eb0", size = 69649, upload-time = "2026-07-28T13:45:30.713Z" }, ] [[package]] @@ -834,6 +867,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/15/ce/e5ec180bc41812edcd8daeb8639d205622c0e8c02259d8ab25a0201b3c2a/numpy-2.4.6-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", size = 12504263, upload-time = "2026-05-18T23:37:09.715Z" }, ] +[[package]] +name = "opentelemetry-api" +version = "1.44.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ee/8b/aa9e2d8b8dfa7c946f7dec5d1f8f6ba8eca062f43509a06bdb5ce93d26c0/opentelemetry_api-1.44.0.tar.gz", hash = "sha256:67647e5e9566edcf421166fdf022b3537f818635daa852b289e34604dc6fb33a", size = 72406, upload-time = "2026-07-16T15:25:32.678Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ca/6f/a04e900f465ff3221ccc395522503e2d10e79fa21f2723c8e177aae1e0d1/opentelemetry_api-1.44.0-py3-none-any.whl", hash = "sha256:94b98c893a91b88657eaac1e3ba89618cdb85be6918196705354f34728b2cdef", size = 60018, upload-time = "2026-07-16T15:25:11.657Z" }, +] + [[package]] name = "packageurl-python" version = "0.17.6" @@ -1716,6 +1761,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/eb/75/1a0392bcc21c44dcdf87b3cf2d137e7829be2c083a1e38d44efca3d57a16/tqdm-4.68.2-py3-none-any.whl", hash = "sha256:d4240441fb5353290b87d6a85968c9decc131a99b8c7faa28269d829de669ede", size = 78578, upload-time = "2026-06-09T13:26:40.731Z" }, ] +[[package]] +name = "truststore" +version = "0.10.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/a3/1585216310e344e8102c22482f6060c7a6ea0322b63e026372e6dcefcfd6/truststore-0.10.4.tar.gz", hash = "sha256:9d91bd436463ad5e4ee4aba766628dd6cd7010cf3e2461756b3303710eebc301", size = 26169, upload-time = "2025-08-12T18:49:02.73Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/19/97/56608b2249fe206a67cd573bc93cd9896e1efb9e98bce9c163bcdc704b88/truststore-0.10.4-py3-none-any.whl", hash = "sha256:adaeaecf1cbb5f4de3b1959b42d41f6fab57b2b1666adb59e89cb0b53361d981", size = 18660, upload-time = "2025-08-12T18:49:01.46Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0"