Move components to own file; add resource

This commit is contained in:
Jeremiah Lowin 2025-06-10 09:31:23 -04:00
commit 02ea970e99
7 changed files with 47 additions and 48 deletions

View file

@ -72,7 +72,7 @@ For users concerned about stability in production environments, we recommend pin
Whenever possible, FastMCP will issue deprecation warnings when users attempt to use APIs that are either deprecated or destined for future removal. These warnings will be maintained for at least 1 minor version release, and may be maintained longer.
Note that the "public API" includes the core functionality of the `FastMCP` server and its methods. It does not include private methods or objects that are stored as private attributes, as we do not expect users to rely on those implementation details.
Note that the "public API" includes the public functionality of the `FastMCP` server, core FastMCP components like `Tool`, `Prompt`, `Resource`, and `ResourceTemplate`, and their respective public methods. It does not include private methods, utilities, or objects that are stored as private attributes, as we do not expect users to rely on those implementation details.
## Installing for Development

View file

@ -15,11 +15,11 @@ from pydantic import Field, TypeAdapter, validate_call
from fastmcp.exceptions import PromptError
from fastmcp.server.dependencies import get_context
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.json_schema import compress_schema
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import (
FastMCPBaseModel,
FastMCPComponent,
find_kwarg_by_type,
get_cached_typeadapter,
)

View file

@ -11,7 +11,6 @@ import pydantic_core
from mcp.types import Resource as MCPResource
from pydantic import (
AnyUrl,
BeforeValidator,
ConfigDict,
Field,
UrlConstraints,
@ -20,9 +19,8 @@ from pydantic import (
)
from fastmcp.server.dependencies import get_context
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.types import (
FastMCPBaseModel,
_convert_set_default_none,
find_kwarg_by_type,
)
@ -30,7 +28,7 @@ if TYPE_CHECKING:
pass
class Resource(FastMCPBaseModel, abc.ABC):
class Resource(FastMCPComponent, abc.ABC):
"""Base class for all resources."""
model_config = ConfigDict(validate_default=True)
@ -38,13 +36,6 @@ class Resource(FastMCPBaseModel, abc.ABC):
uri: Annotated[AnyUrl, UrlConstraints(host_required=False)] = Field(
default=..., description="URI of the resource"
)
name: str | None = Field(default=None, description="Name of the resource")
description: str | None = Field(
default=None, description="Description of the resource"
)
tags: Annotated[set[str], BeforeValidator(_convert_set_default_none)] = Field(
default_factory=set, description="Tags for the resource"
)
mime_type: str = Field(
default="text/plain",
description="MIME type of the resource content",

View file

@ -17,9 +17,9 @@ from pydantic import (
from fastmcp.resources.types import Resource
from fastmcp.server.dependencies import get_context
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.json_schema import compress_schema
from fastmcp.utilities.types import (
FastMCPComponent,
find_kwarg_by_type,
get_cached_typeadapter,
)

View file

@ -14,10 +14,10 @@ from pydantic import Field
import fastmcp
from fastmcp.server.dependencies import get_context
from fastmcp.utilities.components import FastMCPComponent
from fastmcp.utilities.json_schema import compress_schema
from fastmcp.utilities.logging import get_logger
from fastmcp.utilities.types import (
FastMCPComponent,
Image,
find_kwarg_by_type,
get_cached_typeadapter,

View file

@ -0,0 +1,39 @@
from collections.abc import Sequence
from typing import Annotated, TypeVar
from pydantic import BeforeValidator, Field
from fastmcp.utilities.types import FastMCPBaseModel
T = TypeVar("T")
def _convert_set_default_none(maybe_set: set[T] | Sequence[T] | None) -> set[T]:
"""Convert a sequence to a set, defaulting to an empty set if None."""
if maybe_set is None:
return set()
if isinstance(maybe_set, set):
return maybe_set
return set(maybe_set)
class FastMCPComponent(FastMCPBaseModel):
"""Base class for FastMCP tools, prompts, resources, and resource templates."""
name: str = Field(
description="The name of the component.",
)
description: str | None = Field(
default=None,
description="The description of the component.",
)
tags: Annotated[set[str], BeforeValidator(_convert_set_default_none)] = Field(
default_factory=set,
description="Tags for the component.",
)
def __eq__(self, other: object) -> bool:
if type(self) is not type(other):
return False
assert isinstance(other, type(self))
return self.model_dump() == other.model_dump()

View file

@ -2,55 +2,24 @@
import base64
import inspect
from collections.abc import Callable, Sequence
from collections.abc import Callable
from functools import lru_cache
from pathlib import Path
from types import UnionType
from typing import Annotated, TypeVar, Union, get_args, get_origin
from mcp.types import ImageContent
from pydantic import BaseModel, BeforeValidator, ConfigDict, Field, TypeAdapter
from pydantic import BaseModel, ConfigDict, TypeAdapter
T = TypeVar("T")
def _convert_set_default_none(maybe_set: set[T] | Sequence[T] | None) -> set[T]:
"""Convert a sequence to a set, defaulting to an empty set if None."""
if maybe_set is None:
return set()
if isinstance(maybe_set, set):
return maybe_set
return set(maybe_set)
class FastMCPBaseModel(BaseModel):
"""Base model for FastMCP models."""
model_config = ConfigDict(extra="forbid")
class FastMCPComponent(FastMCPBaseModel):
"""Base class for FastMCP tools, prompts, resources, and resource templates."""
name: str = Field(
description="The name of the component.",
)
description: str | None = Field(
default=None,
description="The description of the component.",
)
tags: Annotated[set[str], BeforeValidator(_convert_set_default_none)] = Field(
default_factory=set,
description="Tags for the component.",
)
def __eq__(self, other: object) -> bool:
if type(self) is not type(other):
return False
assert isinstance(other, type(self))
return self.model_dump() == other.model_dump()
@lru_cache(maxsize=5000)
def get_cached_typeadapter(cls: T) -> TypeAdapter[T]:
"""