feat: add tool metadata in FastMCP.tool decorator (#1463)

Co-authored-by: Max Isbey <224885523+maxisbey@users.noreply.github.com>
This commit is contained in:
Mat Leonard 2025-10-17 04:18:34 -07:00 • committed by GitHub
commit 98f82485bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 213 additions and 8 deletions

View file

@ -4,7 +4,14 @@ from __future__ import annotations as _annotations
import inspect
import re
from collections.abc import AsyncIterator, Awaitable, Callable, Collection, Iterable, Sequence
from collections.abc import (
AsyncIterator,
Awaitable,
Callable,
Collection,
Iterable,
Sequence,
)
from contextlib import AbstractAsyncContextManager, asynccontextmanager
from typing import Any, Generic, Literal
@ -22,10 +29,21 @@ from starlette.routing import Mount, Route
from starlette.types import Receive, Scope, Send
from mcp.server.auth.middleware.auth_context import AuthContextMiddleware
from mcp.server.auth.middleware.bearer_auth import BearerAuthBackend, RequireAuthMiddleware
from mcp.server.auth.provider import OAuthAuthorizationServerProvider, ProviderTokenVerifier, TokenVerifier
from mcp.server.auth.middleware.bearer_auth import (
BearerAuthBackend,
RequireAuthMiddleware,
)
from mcp.server.auth.provider import (
OAuthAuthorizationServerProvider,
ProviderTokenVerifier,
TokenVerifier,
)
from mcp.server.auth.settings import AuthSettings
from mcp.server.elicitation import ElicitationResult, ElicitSchemaModelT, elicit_with_validation
from mcp.server.elicitation import (
ElicitationResult,
ElicitSchemaModelT,
elicit_with_validation,
)
from mcp.server.fastmcp.exceptions import ResourceError
from mcp.server.fastmcp.prompts import Prompt, PromptManager
from mcp.server.fastmcp.resources import FunctionResource, Resource, ResourceManager
@ -112,7 +130,9 @@ def lifespan_wrapper(
lifespan: Callable[[FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]],
) -> Callable[[MCPServer[LifespanResultT, Request]], AbstractAsyncContextManager[LifespanResultT]]:
@asynccontextmanager
async def wrap(_: MCPServer[LifespanResultT, Request]) -> AsyncIterator[LifespanResultT]:
async def wrap(
_: MCPServer[LifespanResultT, Request],
) -> AsyncIterator[LifespanResultT]:
async with lifespan(app) as context:
yield context
@ -126,7 +146,7 @@ class FastMCP(Generic[LifespanResultT]):
instructions: str | None = None,
website_url: str | None = None,
icons: list[Icon] | None = None,
auth_server_provider: OAuthAuthorizationServerProvider[Any, Any, Any] | None = None,
auth_server_provider: (OAuthAuthorizationServerProvider[Any, Any, Any] | None) = None,
token_verifier: TokenVerifier | None = None,
event_store: EventStore | None = None,
*,
@ -145,7 +165,7 @@ class FastMCP(Generic[LifespanResultT]):
warn_on_duplicate_tools: bool = True,
warn_on_duplicate_prompts: bool = True,
dependencies: Collection[str] = (),
lifespan: Callable[[FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]] | None = None,
lifespan: (Callable[[FastMCP[LifespanResultT]], AbstractAsyncContextManager[LifespanResultT]] | None) = None,
auth: AuthSettings | None = None,
transport_security: TransportSecuritySettings | None = None,
):
@ -290,6 +310,7 @@ class FastMCP(Generic[LifespanResultT]):
outputSchema=info.output_schema,
annotations=info.annotations,
icons=info.icons,
_meta=info.meta,
)
for info in tools
]
@ -365,6 +386,7 @@ class FastMCP(Generic[LifespanResultT]):
description: str | None = None,
annotations: ToolAnnotations | None = None,
icons: list[Icon] | None = None,
meta: dict[str, Any] | None = None,
structured_output: bool | None = None,
) -> None:
"""Add a tool to the server.
@ -390,6 +412,7 @@ class FastMCP(Generic[LifespanResultT]):
description=description,
annotations=annotations,
icons=icons,
meta=meta,
structured_output=structured_output,
)
@ -411,6 +434,7 @@ class FastMCP(Generic[LifespanResultT]):
description: str | None = None,
annotations: ToolAnnotations | None = None,
icons: list[Icon] | None = None,
meta: dict[str, Any] | None = None,
structured_output: bool | None = None,
) -> Callable[[AnyFunction], AnyFunction]:
"""Decorator to register a tool.
@ -458,6 +482,7 @@ class FastMCP(Generic[LifespanResultT]):
description=description,
annotations=annotations,
icons=icons,
meta=meta,
structured_output=structured_output,
)
return fn
@ -1169,7 +1194,10 @@ class Context(BaseModel, Generic[ServerSessionT, LifespanContextT, RequestT]):
"""
return await elicit_with_validation(
session=self.request_context.session, message=message, schema=schema, related_request_id=self.request_id
session=self.request_context.session,
message=message,
schema=schema,
related_request_id=self.request_id,
)
async def log(

View file

@ -34,6 +34,7 @@ class Tool(BaseModel):
context_kwarg: str | None = Field(None, description="Name of the kwarg that should receive context")
annotations: ToolAnnotations | None = Field(None, description="Optional annotations for the tool")
icons: list[Icon] | None = Field(default=None, description="Optional list of icons for this tool")
meta: dict[str, Any] | None = Field(default=None, description="Optional metadata for this tool")
@cached_property
def output_schema(self) -> dict[str, Any] | None:
@ -49,6 +50,7 @@ class Tool(BaseModel):
context_kwarg: str | None = None,
annotations: ToolAnnotations | None = None,
icons: list[Icon] | None = None,
meta: dict[str, Any] | None = None,
structured_output: bool | None = None,
) -> Tool:
"""Create a Tool from a function."""
@ -81,6 +83,7 @@ class Tool(BaseModel):
context_kwarg=context_kwarg,
annotations=annotations,
icons=icons,
meta=meta,
)
async def run(

View file

@ -50,6 +50,7 @@ class ToolManager:
description: str | None = None,
annotations: ToolAnnotations | None = None,
icons: list[Icon] | None = None,
meta: dict[str, Any] | None = None,
structured_output: bool | None = None,
) -> Tool:
"""Add a tool to the server."""
@ -60,6 +61,7 @@ class ToolManager:
description=description,
annotations=annotations,
icons=icons,
meta=meta,
structured_output=structured_output,
)
existing = self._tools.get(tool.name)