Split protocol types into a standalone mcp-types package (#2973)

This commit is contained in:
Marcelo Trylesinski 2026-06-25 19:18:38 +02:00 • committed by GitHub
commit 0ee7f1b293
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
211 changed files with 1040 additions and 867 deletions

View file

@ -36,9 +36,10 @@ from typing import Any, cast
from urllib.parse import parse_qs, urlparse
import httpx
import mcp_types as types
from mcp_types.version import MODERN_PROTOCOL_VERSIONS
from pydantic import AnyUrl
from mcp import types
from mcp.client.auth import OAuthClientProvider, TokenStorage
from mcp.client.auth.extensions.client_credentials import (
ClientCredentialsOAuthProvider,
@ -49,7 +50,6 @@ from mcp.client.client import Client
from mcp.client.context import ClientRequestContext
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import AuthorizationCodeResult, OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
from mcp.shared.version import MODERN_PROTOCOL_VERSIONS
# Set up logging to stderr (stdout is for conformance test output)
logging.basicConfig(

View file

@ -27,7 +27,9 @@ jobs:
run: uv python install 3.12
- name: Build
run: uv build
run: |
uv build --package mcp
uv build --package mcp-types
- name: Upload artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1

View file

@ -419,10 +419,10 @@ For full control over tool responses including the `_meta` field (for passing da
from typing import Annotated
from mcp_types import CallToolResult, TextContent
from pydantic import BaseModel
from mcp.server.mcpserver import MCPServer
from mcp.types import CallToolResult, TextContent
mcp = MCPServer("CallToolResult Example")
@ -739,9 +739,10 @@ uv run completion-client
import asyncio
import os
from mcp_types import PromptReference, ResourceTemplateReference
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.types import PromptReference, ResourceTemplateReference
# Create server parameters for stdio connection
server_params = StdioServerParameters(
@ -828,11 +829,11 @@ like OAuth flows, credential collection, or payment processing.
import uuid
from mcp_types import ElicitRequestURLParams
from pydantic import BaseModel, Field
from mcp.server.mcpserver import Context, MCPServer
from mcp.shared.exceptions import UrlElicitationRequiredError
from mcp.types import ElicitRequestURLParams
mcp = MCPServer(name="Elicitation Example")
@ -937,8 +938,9 @@ Tools can interact with LLMs through sampling (generating text):
<!-- snippet-source examples/snippets/servers/sampling.py -->
```python
from mcp_types import SamplingMessage, TextContent
from mcp.server.mcpserver import Context, MCPServer
from mcp.types import SamplingMessage, TextContent
mcp = MCPServer(name="Sampling Example")
@ -1647,8 +1649,9 @@ from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import TypedDict
import mcp_types as types
import mcp.server.stdio
from mcp import types
from mcp.server import Server, ServerRequestContext
@ -1759,8 +1762,9 @@ uv run examples/snippets/servers/lowlevel/basic.py
import asyncio
import mcp_types as types
import mcp.server.stdio
from mcp import types
from mcp.server import Server, ServerRequestContext
@ -1836,8 +1840,9 @@ uv run examples/snippets/servers/lowlevel/structured_output.py
import asyncio
import json
import mcp_types as types
import mcp.server.stdio
from mcp import types
from mcp.server import Server, ServerRequestContext
@ -1928,8 +1933,9 @@ uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py
import asyncio
import mcp_types as types
import mcp.server.stdio
from mcp import types
from mcp.server import Server, ServerRequestContext
@ -1999,7 +2005,8 @@ For servers that need to handle large datasets, the low-level server provides pa
```python
"""Example of implementing pagination with the low-level MCP server."""
from mcp import types
import mcp_types as types
from mcp.server import Server, ServerRequestContext
# Sample data to paginate
@ -2045,9 +2052,10 @@ _Full example: [examples/snippets/servers/pagination_example.py](https://github.
import asyncio
from mcp_types import PaginatedRequestParams, Resource
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
from mcp.types import PaginatedRequestParams, Resource
async def list_all_resources() -> None:
@ -2107,7 +2115,9 @@ uv run client
import asyncio
import os
from mcp import ClientSession, StdioServerParameters, types
import mcp_types as types
from mcp import ClientSession, StdioServerParameters
from mcp.client.context import ClientRequestContext
from mcp.client.stdio import stdio_client
@ -2417,7 +2427,9 @@ When calling tools through MCP, the `CallToolResult` object contains the tool's
import asyncio
from mcp import ClientSession, StdioServerParameters, types
import mcp_types as types
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

View file

@ -209,9 +209,38 @@ named `mcp.os.win32.utilities` (was `client.stdio.win32`).
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.
### `mcp.types` moved to the `mcp-types` package
The protocol wire types now live in a standalone distribution, `mcp-types`, imported as
`mcp_types`. It depends only on `pydantic`, so code that just needs to (de)serialize MCP
traffic can install it without the full SDK. The `mcp` package depends on `mcp-types` and
continues to re-export the type names at the top level, so `from mcp import Tool` is
unchanged. Only the `mcp.types` submodule and `mcp.shared.version` were removed.
**Why:** keeping the wire types in their own package lets tooling and lightweight clients
depend on the protocol schema without pulling in `httpx`, `starlette`, `uvicorn`, and the
rest of the server/transport stack.
**Before (v1):**
```python
from mcp.types import Tool, CallToolResult
from mcp.shared.version import LATEST_PROTOCOL_VERSION
```
**After (v2):**
```python
from mcp_types import Tool, CallToolResult
from mcp_types.version import LATEST_PROTOCOL_VERSION
# Top-level re-exports are unchanged:
from mcp import Tool, CallToolResult
```
### Removed type aliases and classes
The following deprecated type aliases and classes have been removed from `mcp.types`:
The following deprecated type aliases and classes have been removed from `mcp_types`:
| Removed | Replacement |
|---------|-------------|
@ -231,13 +260,13 @@ from mcp.types import Content, ResourceReference, Cursor
**After (v2):**
```python
from mcp.types import ContentBlock, ResourceTemplateReference
from mcp_types import ContentBlock, ResourceTemplateReference
# Use `str` instead of `Cursor` for pagination cursors
```
### Field names changed from camelCase to snake_case
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.
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.
**Before (v1):**
@ -287,7 +316,7 @@ Results returned from server handlers are now validated against the negotiated p
### Client validates inbound traffic against the protocol schema
`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).
`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).
### `args` parameter removed from `ClientSessionGroup.call_tool()`
@ -326,7 +355,7 @@ result = await session.list_tools(cursor="next_page_token")
**After (v2):**
```python
from mcp.types import PaginatedRequestParams
from mcp_types import PaginatedRequestParams
result = await session.list_resources(params=PaginatedRequestParams(cursor="next_page_token"))
result = await session.list_tools(params=PaginatedRequestParams(cursor="next_page_token"))
@ -415,7 +444,7 @@ raise McpError(ErrorData(code=INVALID_REQUEST, message="bad input"))
```python
from mcp.shared.exceptions import MCPError
from mcp.types import INVALID_REQUEST
from mcp_types import INVALID_REQUEST
raise MCPError(INVALID_REQUEST, "bad input")
# or, if you already have an ErrorData:
@ -590,7 +619,7 @@ In v2, the lowlevel `Server` supports arbitrary request handlers directly via `a
```python
from mcp.server import ServerRequestContext
from mcp.types import EmptyResult, SetLevelRequestParams, SubscribeRequestParams
from mcp_types import EmptyResult, SetLevelRequestParams, SubscribeRequestParams
async def handle_set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult:
@ -663,7 +692,7 @@ actual_notification = notification.root
**After (v2):**
```python
from mcp.types import client_request_adapter, server_notification_adapter
from mcp_types import client_request_adapter, server_notification_adapter
# Using TypeAdapter.validate_python()
request = client_request_adapter.validate_python(data)
@ -701,7 +730,7 @@ await session.send_request(PingRequest(), EmptyResult)
| `ServerResult` | `server_result_adapter` |
| `JSONRPCMessage` | `jsonrpc_message_adapter` |
All adapters are exported from `mcp.types`.
All adapters are exported from `mcp_types`.
### `RequestParams.Meta` replaced with `RequestParamsMeta` TypedDict
@ -881,7 +910,7 @@ resource = Resource(name="test", uri=AnyUrl("users/me")) # Would fail validatio
**After (v2):**
```python
from mcp.types import Resource
from mcp_types import Resource
# Plain strings accepted
resource = Resource(name="test", uri="users/me") # Works
@ -1038,7 +1067,7 @@ async def handle_call_tool(name: str, arguments: dict):
```python
from mcp.server import Server, ServerRequestContext
from mcp.types import (
from mcp_types import (
CallToolRequestParams,
CallToolResult,
ListToolsResult,
@ -1087,13 +1116,13 @@ All handlers receive `ctx: ServerRequestContext` as the first argument. The seco
| `@server.progress_notification()` | `on_progress` | `ProgressNotificationParams` | `None` |
| — | `on_roots_list_changed` | `NotificationParams \| None` | `None` |
All `params` and return types are importable from `mcp.types`.
All `params` and return types are importable from `mcp_types`.
**Notification handlers:**
```python
from mcp.server import Server, ServerRequestContext
from mcp.types import ProgressNotificationParams
from mcp_types import ProgressNotificationParams
async def handle_progress(ctx: ServerRequestContext, params: ProgressNotificationParams) -> None:
@ -1218,7 +1247,7 @@ async def handle_call_tool(name: str, arguments: dict):
```python
from mcp.server import ServerRequestContext
from mcp.types import CallToolRequestParams, CallToolResult, TextContent
from mcp_types import CallToolRequestParams, CallToolResult, TextContent
async def handle_call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> CallToolResult:
@ -1290,7 +1319,7 @@ Behavior changes:
### Experimental Tasks support removed
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.
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.
Tasks are expected to return as a separate MCP extension in a future release.
@ -1358,7 +1387,7 @@ In v1, MCP protocol types were configured with `extra="allow"`: unknown fields p
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:
```python
from mcp.types import CallToolRequestParams
from mcp_types import CallToolRequestParams
params = CallToolRequestParams(
name="my_tool",
@ -1404,7 +1433,7 @@ Under OIDC, omitting `application_type` defaults to `"web"`, which an authorizat
### 2025-11-25 and 2026-07-28 protocol fields modeled
`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.
`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.
### `streamable_http_app()` available on lowlevel Server
@ -1412,7 +1441,7 @@ The `streamable_http_app()` method is now available directly on the lowlevel `Se
```python
from mcp.server import Server, ServerRequestContext
from mcp.types import ListToolsResult, PaginatedRequestParams
from mcp_types import ListToolsResult, PaginatedRequestParams
async def handle_list_tools(ctx: ServerRequestContext, params: PaginatedRequestParams | None) -> ListToolsResult:

View file

@ -44,7 +44,7 @@ To run the below test, you'll need to install the following dependencies:
import pytest
from inline_snapshot import snapshot
from mcp import Client
from mcp.types import CallToolResult, TextContent
from mcp_types import CallToolResult, TextContent
from server import app

View file

@ -2,10 +2,10 @@
from typing import Annotated
from mcp_types import CallToolResult, TextContent
from pydantic import BaseModel
from mcp.server.mcpserver import MCPServer
from mcp.types import CallToolResult, TextContent
mcp = MCPServer("Echo Server")

View file

@ -15,7 +15,7 @@ from mcp.server.mcpserver import Context, MCPServer
from mcp.server.mcpserver.prompts.base import UserMessage
from mcp.server.streamable_http import EventCallback, EventMessage, EventStore
from mcp.shared.exceptions import MCPError
from mcp.types import (
from mcp_types import (
AudioContent,
Completion,
CompletionArgument,
@ -33,7 +33,7 @@ from mcp.types import (
TextResourceContents,
UnsubscribeRequestParams,
)
from mcp.types.jsonrpc import MISSING_REQUIRED_CLIENT_CAPABILITY
from mcp_types.jsonrpc import MISSING_REQUIRED_CLIENT_CAPABILITY
from pydantic import BaseModel, Field
logger = logging.getLogger(__name__)

View file

@ -8,7 +8,7 @@ from typing import TypeVar
import anyio
import click
from mcp import types
import mcp_types as types
from mcp.server import Server, ServerRequestContext
T = TypeVar("T")

View file

@ -1,6 +1,6 @@
import anyio
import click
from mcp import types
import mcp_types as types
from mcp.server import Server, ServerRequestContext

View file

@ -22,7 +22,7 @@ Using the MCP client, you can retrieve resources like this using the STDIO trans
```python
import asyncio
from mcp.types import AnyUrl
from pydantic import AnyUrl
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client

View file

@ -2,7 +2,7 @@ from urllib.parse import urlparse
import anyio
import click
from mcp import types
import mcp_types as types
from mcp.server import Server, ServerRequestContext
SAMPLE_RESOURCES = {

View file

@ -2,8 +2,8 @@ import logging
import anyio
import click
import mcp_types as types
import uvicorn
from mcp import types
from mcp.server import Server, ServerRequestContext
from starlette.middleware.cors import CORSMiddleware

View file

@ -10,7 +10,7 @@ from dataclasses import dataclass
from uuid import uuid4
from mcp.server.streamable_http import EventCallback, EventId, EventMessage, EventStore, StreamId
from mcp.types import JSONRPCMessage
from mcp_types import JSONRPCMessage
logger = logging.getLogger(__name__)

View file

@ -2,8 +2,8 @@ import logging
import anyio
import click
import mcp_types as types
import uvicorn
from mcp import types
from mcp.server import Server, ServerRequestContext
from starlette.middleware.cors import CORSMiddleware

View file

@ -1,6 +1,6 @@
import anyio
import click
from mcp import types
import mcp_types as types
from mcp.server import Server, ServerRequestContext
from mcp.shared._httpx_utils import create_mcp_http_client

View file

@ -10,7 +10,7 @@ from dataclasses import dataclass
from uuid import uuid4
from mcp.server.streamable_http import EventCallback, EventId, EventMessage, EventStore, StreamId
from mcp.types import JSONRPCMessage
from mcp_types import JSONRPCMessage
logger = logging.getLogger(__name__)

View file

@ -16,8 +16,8 @@ import logging
import anyio
import click
import mcp_types as types
import uvicorn
from mcp import types
from mcp.server import Server, ServerRequestContext
from .event_store import InMemoryEventStore

View file

@ -10,8 +10,9 @@ import json
import random
from datetime import datetime
import mcp_types as types
import mcp.server.stdio
from mcp import types
from mcp.server import Server, ServerRequestContext

View file

@ -5,9 +5,10 @@ uv run completion-client
import asyncio
import os
from mcp_types import PromptReference, ResourceTemplateReference
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
from mcp.types import PromptReference, ResourceTemplateReference
# Create server parameters for stdio connection
server_params = StdioServerParameters(

View file

@ -2,9 +2,10 @@
import asyncio
from mcp_types import PaginatedRequestParams, Resource
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
from mcp.types import PaginatedRequestParams, Resource
async def list_all_resources() -> None:

Some files were not shown because too many files have changed in this diff Show more