Skip to content

Commit 8bb37b3

Browse files
committed
Split protocol types into a standalone mcp-types package
Extract the wire types from `src/mcp/types/` into a separate distribution, `mcp-types` (imported as `mcp_types`), wired into the uv workspace. The new package depends only on pydantic, so tooling and lightweight clients can (de)serialize MCP traffic without the full server/transport stack. `mcp.shared.version` moves to `mcp_types.version` (a pure leaf with no `mcp` deps), which lets `mcp_types` depend on nothing in `mcp`. The `mcp.types` submodule and `mcp.shared.version` are removed; `mcp` re-exports the type names at the top level, so `from mcp import Tool` is unchanged. All importers are rewritten to `mcp_types`. Documented in docs/migration.md.
1 parent 1b1abf6 commit 8bb37b3

210 files changed

Lines changed: 1016 additions & 843 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/actions/conformance/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
from urllib.parse import parse_qs, urlparse
3737

3838
import httpx
39+
from mcp_types.version import MODERN_PROTOCOL_VERSIONS
3940
from pydantic import AnyUrl
4041

4142
from mcp import types
@@ -49,7 +50,6 @@
4950
from mcp.client.context import ClientRequestContext
5051
from mcp.client.streamable_http import streamable_http_client
5152
from mcp.shared.auth import AuthorizationCodeResult, OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
52-
from mcp.shared.version import MODERN_PROTOCOL_VERSIONS
5353

5454
# Set up logging to stderr (stdout is for conformance test output)
5555
logging.basicConfig(

README.v2.md

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -419,10 +419,10 @@ For full control over tool responses including the `_meta` field (for passing da
419419

420420
from typing import Annotated
421421

422+
from mcp_types import CallToolResult, TextContent
422423
from pydantic import BaseModel
423424

424425
from mcp.server.mcpserver import MCPServer
425-
from mcp.types import CallToolResult, TextContent
426426

427427
mcp = MCPServer("CallToolResult Example")
428428

@@ -739,9 +739,10 @@ uv run completion-client
739739
import asyncio
740740
import os
741741

742+
from mcp_types import PromptReference, ResourceTemplateReference
743+
742744
from mcp import ClientSession, StdioServerParameters
743745
from mcp.client.stdio import stdio_client
744-
from mcp.types import PromptReference, ResourceTemplateReference
745746

746747
# Create server parameters for stdio connection
747748
server_params = StdioServerParameters(
@@ -828,11 +829,11 @@ like OAuth flows, credential collection, or payment processing.
828829

829830
import uuid
830831

832+
from mcp_types import ElicitRequestURLParams
831833
from pydantic import BaseModel, Field
832834

833835
from mcp.server.mcpserver import Context, MCPServer
834836
from mcp.shared.exceptions import UrlElicitationRequiredError
835-
from mcp.types import ElicitRequestURLParams
836837

837838
mcp = MCPServer(name="Elicitation Example")
838839

@@ -937,8 +938,9 @@ Tools can interact with LLMs through sampling (generating text):
937938

938939
<!-- snippet-source examples/snippets/servers/sampling.py -->
939940
```python
941+
from mcp_types import SamplingMessage, TextContent
942+
940943
from mcp.server.mcpserver import Context, MCPServer
941-
from mcp.types import SamplingMessage, TextContent
942944

943945
mcp = MCPServer(name="Sampling Example")
944946

@@ -1647,8 +1649,9 @@ from collections.abc import AsyncIterator
16471649
from contextlib import asynccontextmanager
16481650
from typing import TypedDict
16491651

1652+
import mcp_types as types
1653+
16501654
import mcp.server.stdio
1651-
from mcp import types
16521655
from mcp.server import Server, ServerRequestContext
16531656

16541657

@@ -1759,8 +1762,9 @@ uv run examples/snippets/servers/lowlevel/basic.py
17591762

17601763
import asyncio
17611764

1765+
import mcp_types as types
1766+
17621767
import mcp.server.stdio
1763-
from mcp import types
17641768
from mcp.server import Server, ServerRequestContext
17651769

17661770

@@ -1836,8 +1840,9 @@ uv run examples/snippets/servers/lowlevel/structured_output.py
18361840
import asyncio
18371841
import json
18381842

1843+
import mcp_types as types
1844+
18391845
import mcp.server.stdio
1840-
from mcp import types
18411846
from mcp.server import Server, ServerRequestContext
18421847

18431848

@@ -1928,8 +1933,9 @@ uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py
19281933

19291934
import asyncio
19301935

1936+
import mcp_types as types
1937+
19311938
import mcp.server.stdio
1932-
from mcp import types
19331939
from mcp.server import Server, ServerRequestContext
19341940

19351941

@@ -1999,7 +2005,8 @@ For servers that need to handle large datasets, the low-level server provides pa
19992005
```python
20002006
"""Example of implementing pagination with the low-level MCP server."""
20012007

2002-
from mcp import types
2008+
import mcp_types as types
2009+
20032010
from mcp.server import Server, ServerRequestContext
20042011

20052012
# Sample data to paginate
@@ -2045,9 +2052,10 @@ _Full example: [examples/snippets/servers/pagination_example.py](https://github.
20452052

20462053
import asyncio
20472054

2055+
from mcp_types import PaginatedRequestParams, Resource
2056+
20482057
from mcp.client.session import ClientSession
20492058
from mcp.client.stdio import StdioServerParameters, stdio_client
2050-
from mcp.types import PaginatedRequestParams, Resource
20512059

20522060

20532061
async def list_all_resources() -> None:
@@ -2107,7 +2115,9 @@ uv run client
21072115
import asyncio
21082116
import os
21092117

2110-
from mcp import ClientSession, StdioServerParameters, types
2118+
import mcp_types as types
2119+
2120+
from mcp import ClientSession, StdioServerParameters
21112121
from mcp.client.context import ClientRequestContext
21122122
from mcp.client.stdio import stdio_client
21132123

@@ -2417,7 +2427,9 @@ When calling tools through MCP, the `CallToolResult` object contains the tool's
24172427

24182428
import asyncio
24192429

2420-
from mcp import ClientSession, StdioServerParameters, types
2430+
import mcp_types as types
2431+
2432+
from mcp import ClientSession, StdioServerParameters
24212433
from mcp.client.stdio import stdio_client
24222434

24232435

docs/migration.md

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -209,9 +209,38 @@ named `mcp.os.win32.utilities` (was `client.stdio.win32`).
209209

210210
The WebSocket transport has been removed: `mcp.client.websocket.websocket_client`, `mcp.server.websocket.websocket_server`, and the `ws` optional dependency extra (`mcp[ws]`) no longer exist. WebSocket was never part of the MCP specification. Use the streamable HTTP transport instead (`mcp.client.streamable_http.streamable_http_client` on the client, `streamable_http_app()` on the server), which supports bidirectional communication with server-to-client streaming over standard HTTP.
211211

212+
### `mcp.types` moved to the `mcp-types` package
213+
214+
The protocol wire types now live in a standalone distribution, `mcp-types`, imported as
215+
`mcp_types`. It depends only on `pydantic`, so code that just needs to (de)serialize MCP
216+
traffic can install it without the full SDK. The `mcp` package depends on `mcp-types` and
217+
continues to re-export the type names at the top level, so `from mcp import Tool` is
218+
unchanged. Only the `mcp.types` submodule and `mcp.shared.version` were removed.
219+
220+
**Why:** keeping the wire types in their own package lets tooling and lightweight clients
221+
depend on the protocol schema without pulling in `httpx`, `starlette`, `uvicorn`, and the
222+
rest of the server/transport stack.
223+
224+
**Before (v1):**
225+
226+
```python
227+
from mcp.types import Tool, CallToolResult
228+
from mcp.shared.version import LATEST_PROTOCOL_VERSION
229+
```
230+
231+
**After (v2):**
232+
233+
```python
234+
from mcp_types import Tool, CallToolResult
235+
from mcp_types.version import LATEST_PROTOCOL_VERSION
236+
237+
# Top-level re-exports are unchanged:
238+
from mcp import Tool, CallToolResult
239+
```
240+
212241
### Removed type aliases and classes
213242

214-
The following deprecated type aliases and classes have been removed from `mcp.types`:
243+
The following deprecated type aliases and classes have been removed from `mcp_types`:
215244

216245
| Removed | Replacement |
217246
|---------|-------------|
@@ -231,13 +260,13 @@ from mcp.types import Content, ResourceReference, Cursor
231260
**After (v2):**
232261

233262
```python
234-
from mcp.types import ContentBlock, ResourceTemplateReference
263+
from mcp_types import ContentBlock, ResourceTemplateReference
235264
# Use `str` instead of `Cursor` for pagination cursors
236265
```
237266

238267
### Field names changed from camelCase to snake_case
239268

240-
All Pydantic model fields in `mcp.types` now use snake_case names for Python attribute access. The JSON wire format is unchanged — serialization still uses camelCase via Pydantic aliases.
269+
All Pydantic model fields in `mcp_types` now use snake_case names for Python attribute access. The JSON wire format is unchanged — serialization still uses camelCase via Pydantic aliases.
241270

242271
**Before (v1):**
243272

@@ -287,7 +316,7 @@ Results returned from server handlers are now validated against the negotiated p
287316

288317
### Client validates inbound traffic against the protocol schema
289318

290-
`ClientSession` now validates server requests, notifications, and results against the negotiated protocol version's schema before parsing them into `mcp.types` models. Spec-invalid server output that the previous monolith parse tolerated may now raise `pydantic.ValidationError` from `list_tools()`, `call_tool()`, and similar calls. `_meta` remains the sanctioned place for result extras (and `experimental` for capability extras).
319+
`ClientSession` now validates server requests, notifications, and results against the negotiated protocol version's schema before parsing them into `mcp_types` models. Spec-invalid server output that the previous monolith parse tolerated may now raise `pydantic.ValidationError` from `list_tools()`, `call_tool()`, and similar calls. `_meta` remains the sanctioned place for result extras (and `experimental` for capability extras).
291320

292321
### `args` parameter removed from `ClientSessionGroup.call_tool()`
293322

@@ -326,7 +355,7 @@ result = await session.list_tools(cursor="next_page_token")
326355
**After (v2):**
327356

328357
```python
329-
from mcp.types import PaginatedRequestParams
358+
from mcp_types import PaginatedRequestParams
330359

331360
result = await session.list_resources(params=PaginatedRequestParams(cursor="next_page_token"))
332361
result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token"))
@@ -402,7 +431,7 @@ The constructor signature also changed — it now takes `code`, `message`, and o
402431

403432
```python
404433
from mcp.shared.exceptions import McpError
405-
from mcp.types import ErrorData, INVALID_REQUEST
434+
from mcp_types import ErrorData, INVALID_REQUEST
406435

407436
raise McpError(ErrorData(code=INVALID_REQUEST, message="bad input"))
408437
```
@@ -411,7 +440,7 @@ raise McpError(ErrorData(code=INVALID_REQUEST, message="bad input"))
411440

412441
```python
413442
from mcp.shared.exceptions import MCPError
414-
from mcp.types import INVALID_REQUEST
443+
from mcp_types import INVALID_REQUEST
415444

416445
raise MCPError(INVALID_REQUEST, "bad input")
417446
# or, if you already have an ErrorData:
@@ -586,7 +615,7 @@ In v2, the lowlevel `Server` supports arbitrary request handlers directly via `a
586615

587616
```python
588617
from mcp.server import ServerRequestContext
589-
from mcp.types import EmptyResult, SetLevelRequestParams, SubscribeRequestParams
618+
from mcp_types import EmptyResult, SetLevelRequestParams, SubscribeRequestParams
590619

591620

592621
async def handle_set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult:
@@ -646,7 +675,7 @@ This means you can no longer access `.root` on these types or use `model_validat
646675
**Before (v1):**
647676

648677
```python
649-
from mcp.types import ClientRequest, ServerNotification
678+
from mcp_types import ClientRequest, ServerNotification
650679

651680
# Using RootModel.model_validate()
652681
request = ClientRequest.model_validate(data)
@@ -659,7 +688,7 @@ actual_notification = notification.root
659688
**After (v2):**
660689

661690
```python
662-
from mcp.types import client_request_adapter, server_notification_adapter
691+
from mcp_types import client_request_adapter, server_notification_adapter
663692

664693
# Using TypeAdapter.validate_python()
665694
request = client_request_adapter.validate_python(data)
@@ -697,7 +726,7 @@ await session.send_request(PingRequest(), EmptyResult)
697726
| `ServerResult` | `server_result_adapter` |
698727
| `JSONRPCMessage` | `jsonrpc_message_adapter` |
699728

700-
All adapters are exported from `mcp.types`.
729+
All adapters are exported from `mcp_types`.
701730

702731
### `RequestParams.Meta` replaced with `RequestParamsMeta` TypedDict
703732

@@ -868,7 +897,7 @@ The `uri` field on resource-related types now uses `str` instead of Pydantic's `
868897

869898
```python
870899
from pydantic import AnyUrl
871-
from mcp.types import Resource
900+
from mcp_types import Resource
872901

873902
# Required wrapping in AnyUrl
874903
resource = Resource(name="test", uri=AnyUrl("users/me")) # Would fail validation
@@ -877,7 +906,7 @@ resource = Resource(name="test", uri=AnyUrl("users/me")) # Would fail validatio
877906
**After (v2):**
878907

879908
```python
880-
from mcp.types import Resource
909+
from mcp_types import Resource
881910

882911
# Plain strings accepted
883912
resource = Resource(name="test", uri="users/me") # Works
@@ -953,7 +982,7 @@ The public `server.request_handlers` and `server.notification_handlers` dictiona
953982

954983
```python
955984
# Before (v1) — direct dict access
956-
from mcp.types import ListToolsRequest
985+
from mcp_types import ListToolsRequest
957986

958987
if ListToolsRequest in server.request_handlers:
959988
...
@@ -1034,7 +1063,7 @@ async def handle_call_tool(name: str, arguments: dict):
10341063

10351064
```python
10361065
from mcp.server import Server, ServerRequestContext
1037-
from mcp.types import (
1066+
from mcp_types import (
10381067
CallToolRequestParams,
10391068
CallToolResult,
10401069
ListToolsResult,
@@ -1083,13 +1112,13 @@ All handlers receive `ctx: ServerRequestContext` as the first argument. The seco
10831112
| `@server.progress_notification()` | `on_progress` | `ProgressNotificationParams` | `None` |
10841113
|| `on_roots_list_changed` | `NotificationParams \| None` | `None` |
10851114

1086-
All `params` and return types are importable from `mcp.types`.
1115+
All `params` and return types are importable from `mcp_types`.
10871116

10881117
**Notification handlers:**
10891118

10901119
```python
10911120
from mcp.server import Server, ServerRequestContext
1092-
from mcp.types import ProgressNotificationParams
1121+
from mcp_types import ProgressNotificationParams
10931122

10941123

10951124
async def handle_progress(ctx: ServerRequestContext, params: ProgressNotificationParams) -> None:
@@ -1214,7 +1243,7 @@ async def handle_call_tool(name: str, arguments: dict):
12141243

12151244
```python
12161245
from mcp.server import ServerRequestContext
1217-
from mcp.types import CallToolRequestParams, CallToolResult, TextContent
1246+
from mcp_types import CallToolRequestParams, CallToolResult, TextContent
12181247

12191248

12201249
async def handle_call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
@@ -1286,7 +1315,7 @@ Behavior changes:
12861315

12871316
### Experimental Tasks support removed
12881317

1289-
Tasks (SEP-1686) have been removed from the MCP specification and are no longer part of this SDK. The `mcp.client.experimental`, `mcp.server.experimental`, `mcp.shared.experimental`, and `mcp.server.lowlevel.experimental` modules have been removed, along with the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. The corresponding `Task*` types remain in `mcp.types` as types-only definitions.
1318+
Tasks (SEP-1686) have been removed from the MCP specification and are no longer part of this SDK. The `mcp.client.experimental`, `mcp.server.experimental`, `mcp.shared.experimental`, and `mcp.server.lowlevel.experimental` modules have been removed, along with the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. The corresponding `Task*` types remain in `mcp_types` as types-only definitions.
12901319

12911320
Tasks are expected to return as a separate MCP extension in a future release.
12921321

@@ -1354,7 +1383,7 @@ In v1, MCP protocol types were configured with `extra="allow"`: unknown fields p
13541383
In v2, MCP types silently ignore extra fields. Unknown constructor keyword arguments and unknown keys in wire data are dropped during validation — no error is raised, and the values do not round-trip:
13551384

13561385
```python
1357-
from mcp.types import CallToolRequestParams
1386+
from mcp_types import CallToolRequestParams
13581387

13591388
params = CallToolRequestParams(
13601389
name="my_tool",
@@ -1400,15 +1429,15 @@ Under OIDC, omitting `application_type` defaults to `"web"`, which an authorizat
14001429

14011430
### 2025-11-25 and 2026-07-28 protocol fields modeled
14021431

1403-
`mcp.types` models the 2025-11-25 and 2026-07-28 protocol fields (e.g. `resultType`, `ttlMs`/`cacheScope` on cacheable results, `inputResponses`/`requestState` on retried requests), so inbound payloads carrying these keys parse into typed fields and round-trip. `ttlMs`/`cacheScope` default to `0`/`"private"` (immediately stale, not shared-cacheable); `resultType` defaults to `"complete"` on concrete results (`None` on `EmptyResult`); the server strips all of them from the wire at pre-2026 versions.
1432+
`mcp_types` models the 2025-11-25 and 2026-07-28 protocol fields (e.g. `resultType`, `ttlMs`/`cacheScope` on cacheable results, `inputResponses`/`requestState` on retried requests), so inbound payloads carrying these keys parse into typed fields and round-trip. `ttlMs`/`cacheScope` default to `0`/`"private"` (immediately stale, not shared-cacheable); `resultType` defaults to `"complete"` on concrete results (`None` on `EmptyResult`); the server strips all of them from the wire at pre-2026 versions.
14041433

14051434
### `streamable_http_app()` available on lowlevel Server
14061435

14071436
The `streamable_http_app()` method is now available directly on the lowlevel `Server` class, not just `MCPServer`. This allows using the streamable HTTP transport without the MCPServer wrapper.
14081437

14091438
```python
14101439
from mcp.server import Server, ServerRequestContext
1411-
from mcp.types import ListToolsResult, PaginatedRequestParams
1440+
from mcp_types import ListToolsResult, PaginatedRequestParams
14121441

14131442

14141443
async def handle_list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult:

docs/testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ To run the below test, you'll need to install the following dependencies:
4444
import pytest
4545
from inline_snapshot import snapshot
4646
from mcp import Client
47-
from mcp.types import CallToolResult, TextContent
47+
from mcp_types import CallToolResult, TextContent
4848

4949
from server import app
5050

examples/mcpserver/direct_call_tool_result_return.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
from typing import Annotated
44

5+
from mcp_types import CallToolResult, TextContent
56
from pydantic import BaseModel
67

78
from mcp.server.mcpserver import MCPServer
8-
from mcp.types import CallToolResult, TextContent
99

1010
mcp = MCPServer("Echo Server")
1111

0 commit comments

Comments
 (0)