Update classes to inherit from FastMCPBaseModel instead of BaseModel (#2739)

This commit is contained in:
Jeremiah Lowin 2025-12-26 08:33:23 -05:00 committed by GitHub
commit a39ab0734e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 47 additions and 30 deletions

View file

@ -68,7 +68,6 @@ class Message(pydantic.BaseModel):
self,
content: Any,
role: Literal["user", "assistant"] = "user",
**kwargs: Any,
):
"""Create Message with automatic serialization.
@ -88,7 +87,7 @@ class Message(pydantic.BaseModel):
serialized = pydantic_core.to_json(content, fallback=str).decode()
normalized_content = TextContent(type="text", text=serialized)
super().__init__(role=role, content=normalized_content, **kwargs)
super().__init__(role=role, content=normalized_content)
def to_mcp_prompt_message(self) -> PromptMessage:
"""Convert to MCP PromptMessage."""
@ -148,7 +147,6 @@ class PromptResult(pydantic.BaseModel):
messages: str | list[Message],
description: str | None = None,
meta: dict[str, Any] | None = None,
**kwargs: Any,
):
"""Create PromptResult.
@ -158,9 +156,7 @@ class PromptResult(pydantic.BaseModel):
meta: Optional metadata about the prompt result.
"""
normalized = self._normalize_messages(messages)
super().__init__(
messages=normalized, description=description, meta=meta, **kwargs
)
super().__init__(messages=normalized, description=description, meta=meta)
@staticmethod
def _normalize_messages(

View file

@ -29,9 +29,7 @@ from typing_extensions import Self
from fastmcp.server.dependencies import without_injected_parameters
from fastmcp.server.tasks.config import TaskConfig
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.types import (
get_fn_name,
)
from fastmcp.utilities.types import get_fn_name
class ResourceContent(pydantic.BaseModel):
@ -65,7 +63,6 @@ class ResourceContent(pydantic.BaseModel):
content: Any,
mime_type: str | None = None,
meta: dict[str, Any] | None = None,
**kwargs: Any,
):
"""Create ResourceContent with automatic serialization.
@ -88,9 +85,7 @@ class ResourceContent(pydantic.BaseModel):
normalized_content = pydantic_core.to_json(content, fallback=str).decode()
mime_type = mime_type or "application/json"
super().__init__(
content=normalized_content, mime_type=mime_type, meta=meta, **kwargs
)
super().__init__(content=normalized_content, mime_type=mime_type, meta=meta)
def to_mcp_resource_contents(
self, uri: AnyUrl | str
@ -162,7 +157,6 @@ class ResourceResult(pydantic.BaseModel):
self,
contents: str | bytes | list[ResourceContent],
meta: dict[str, Any] | None = None,
**kwargs: Any,
):
"""Create ResourceResult.
@ -171,7 +165,7 @@ class ResourceResult(pydantic.BaseModel):
meta: Optional metadata about the resource result.
"""
normalized = self._normalize_contents(contents)
super().__init__(contents=normalized, meta=meta, **kwargs)
super().__init__(contents=normalized, meta=meta)
@staticmethod
def _normalize_contents(

View file

@ -16,14 +16,14 @@ from key_value.aio.stores.memory import MemoryStore
from mcp.server.streamable_http import EventCallback, EventId, EventMessage, StreamId
from mcp.server.streamable_http import EventStore as SDKEventStore
from mcp.types import JSONRPCMessage
from pydantic import BaseModel
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import FastMCPBaseModel
logger = get_logger(__name__)
class EventEntry(BaseModel):
class EventEntry(FastMCPBaseModel):
"""Stored event entry."""
event_id: str
@ -31,7 +31,7 @@ class EventEntry(BaseModel):
message: dict | None # JSONRPCMessage serialized to dict
class StreamEventList(BaseModel):
class StreamEventList(FastMCPBaseModel):
"""List of event IDs for a stream."""
event_ids: list[str]

View file

@ -14,7 +14,7 @@ from key_value.aio.wrappers.statistics import StatisticsWrapper
from key_value.aio.wrappers.statistics.wrapper import (
KVStoreCollectionStatistics,
)
from pydantic import BaseModel, Field
from pydantic import Field
from typing_extensions import NotRequired, Self, override
from fastmcp.prompts.prompt import Message, Prompt, PromptResult
@ -22,6 +22,7 @@ from fastmcp.resources.resource import Resource, ResourceContent, ResourceResult
from fastmcp.server.middleware.middleware import CallNext, Middleware, MiddlewareContext
from fastmcp.tools.tool import Tool, ToolResult
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import FastMCPBaseModel
logger: Logger = get_logger(name=__name__)
@ -34,7 +35,7 @@ ONE_MB_IN_BYTES = 1024 * 1024
GLOBAL_KEY = "__global__"
class CachableResourceContent(BaseModel):
class CachableResourceContent(FastMCPBaseModel):
"""A wrapper for ResourceContent that can be cached."""
content: str | bytes
@ -42,7 +43,7 @@ class CachableResourceContent(BaseModel):
meta: dict[str, Any] | None = None
class CachableResourceResult(BaseModel):
class CachableResourceResult(FastMCPBaseModel):
"""A wrapper for ResourceResult that can be cached."""
contents: list[CachableResourceContent]
@ -75,7 +76,7 @@ class CachableResourceResult(BaseModel):
)
class CachableToolResult(BaseModel):
class CachableToolResult(FastMCPBaseModel):
content: list[mcp.types.ContentBlock]
structured_content: dict[str, Any] | None
meta: dict[str, Any] | None
@ -96,14 +97,14 @@ class CachableToolResult(BaseModel):
)
class CachableMessage(BaseModel):
class CachableMessage(FastMCPBaseModel):
"""A wrapper for Message that can be cached."""
role: str
content: mcp.types.TextContent | mcp.types.EmbeddedResource
class CachablePromptResult(BaseModel):
class CachablePromptResult(FastMCPBaseModel):
"""A wrapper for PromptResult that can be cached."""
messages: list[CachableMessage]
@ -168,7 +169,7 @@ class GetPromptSettings(SharedMethodSettings):
"""Configuration options for Prompt-related caching."""
class ResponseCachingStatistics(BaseModel):
class ResponseCachingStatistics(FastMCPBaseModel):
list_tools: KVStoreCollectionStatistics | None = Field(default=None)
list_resources: KVStoreCollectionStatistics | None = Field(default=None)
list_prompts: KVStoreCollectionStatistics | None = Field(default=None)

View file

@ -7,12 +7,13 @@ from collections.abc import Callable
from typing import Any
from mcp.types import Tool as SDKTool
from pydantic import BaseModel, ConfigDict
from pydantic import ConfigDict
from fastmcp.tools.tool import ParsedFunction
from fastmcp.utilities.types import FastMCPBaseModel
class SamplingTool(BaseModel):
class SamplingTool(FastMCPBaseModel):
"""A tool that can be used during LLM sampling.
SamplingTools bundle a tool's schema (name, description, parameters) with

View file

@ -117,9 +117,7 @@ class ToolResult(BaseModel):
)
super().__init__(
content=converted_content,
structured_content=structured_content,
meta=meta,
content=converted_content, structured_content=structured_content, meta=meta
)
def to_mcp_result(

View file

@ -432,3 +432,30 @@ class TestMCPServerConfig:
if field != "type"
)
assert config.deployment.transport == "http"
class TestMCPServerConfigRoundtrip:
"""Test that MCPServerConfig survives model_dump() -> reconstruct pattern.
This is used by the CLI to apply overrides immutably.
"""
def test_roundtrip_preserves_schema(self):
"""Ensure schema_ field survives dump/reconstruct cycle."""
config = MCPServerConfig(source=FileSystemSource(path="server.py"))
config_dict = config.model_dump()
reconstructed = MCPServerConfig(**config_dict)
assert reconstructed.schema_ == config.schema_
def test_roundtrip_with_all_fields(self):
"""Full config survives dump/reconstruct."""
config = MCPServerConfig(
source=FileSystemSource(path="server.py", entrypoint="app"),
environment=UVEnvironment(python="3.11"),
deployment=Deployment(transport="http", port=8080),
)
config_dict = config.model_dump()
reconstructed = MCPServerConfig(**config_dict)
assert reconstructed.source.path == "server.py"
assert reconstructed.environment.python == "3.11"
assert reconstructed.deployment.port == 8080