diff --git a/docs/getting-started/installation.mdx b/docs/getting-started/installation.mdx index aaff866e3..4e59b3557 100644 --- a/docs/getting-started/installation.mdx +++ b/docs/getting-started/installation.mdx @@ -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 diff --git a/src/fastmcp/prompts/prompt.py b/src/fastmcp/prompts/prompt.py index 630502617..63b276745 100644 --- a/src/fastmcp/prompts/prompt.py +++ b/src/fastmcp/prompts/prompt.py @@ -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, ) diff --git a/src/fastmcp/resources/resource.py b/src/fastmcp/resources/resource.py index afcc4f953..8155ccaa4 100644 --- a/src/fastmcp/resources/resource.py +++ b/src/fastmcp/resources/resource.py @@ -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", diff --git a/src/fastmcp/resources/template.py b/src/fastmcp/resources/template.py index 8720640d5..b0da1d74a 100644 --- a/src/fastmcp/resources/template.py +++ b/src/fastmcp/resources/template.py @@ -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, ) diff --git a/src/fastmcp/tools/tool.py b/src/fastmcp/tools/tool.py index e5068246f..0859cac99 100644 --- a/src/fastmcp/tools/tool.py +++ b/src/fastmcp/tools/tool.py @@ -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, diff --git a/src/fastmcp/utilities/components.py b/src/fastmcp/utilities/components.py new file mode 100644 index 000000000..e09374735 --- /dev/null +++ b/src/fastmcp/utilities/components.py @@ -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() diff --git a/src/fastmcp/utilities/types.py b/src/fastmcp/utilities/types.py index 3cbeeb0db..5c5e6f0e8 100644 --- a/src/fastmcp/utilities/types.py +++ b/src/fastmcp/utilities/types.py @@ -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]: """