Spaces:
Running
Running
Merge pull request #888 from jlowin/protocol-typing
Browse files- src/fastmcp/client/client.py +2 -2
- src/fastmcp/prompts/prompt.py +2 -3
- src/fastmcp/server/context.py +2 -2
- src/fastmcp/server/openapi.py +2 -3
- src/fastmcp/server/proxy.py +5 -3
- src/fastmcp/server/server.py +6 -4
- src/fastmcp/tools/tool.py +6 -7
- src/fastmcp/tools/tool_manager.py +4 -3
- src/fastmcp/tools/tool_transform.py +3 -3
- src/fastmcp/utilities/types.py +2 -5
- tests/server/openapi/test_openapi.py +4 -0
- tests/tools/test_tool.py +7 -5
src/fastmcp/client/client.py
CHANGED
|
@@ -9,6 +9,7 @@ import httpx
|
|
| 9 |
import mcp.types
|
| 10 |
from exceptiongroup import catch
|
| 11 |
from mcp import ClientSession
|
|
|
|
| 12 |
from pydantic import AnyUrl
|
| 13 |
|
| 14 |
import fastmcp
|
|
@@ -29,7 +30,6 @@ from fastmcp.exceptions import ToolError
|
|
| 29 |
from fastmcp.server import FastMCP
|
| 30 |
from fastmcp.utilities.exceptions import get_catch_handlers
|
| 31 |
from fastmcp.utilities.mcp_config import MCPConfig
|
| 32 |
-
from fastmcp.utilities.types import MCPContent
|
| 33 |
|
| 34 |
from .transports import (
|
| 35 |
ClientTransportT,
|
|
@@ -659,7 +659,7 @@ class Client(Generic[ClientTransportT]):
|
|
| 659 |
arguments: dict[str, Any] | None = None,
|
| 660 |
timeout: datetime.timedelta | float | int | None = None,
|
| 661 |
progress_handler: ProgressHandler | None = None,
|
| 662 |
-
) -> list[
|
| 663 |
"""Call a tool on the server.
|
| 664 |
|
| 665 |
Unlike call_tool_mcp, this method raises a ToolError if the tool call results in an error.
|
|
|
|
| 9 |
import mcp.types
|
| 10 |
from exceptiongroup import catch
|
| 11 |
from mcp import ClientSession
|
| 12 |
+
from mcp.types import ContentBlock
|
| 13 |
from pydantic import AnyUrl
|
| 14 |
|
| 15 |
import fastmcp
|
|
|
|
| 30 |
from fastmcp.server import FastMCP
|
| 31 |
from fastmcp.utilities.exceptions import get_catch_handlers
|
| 32 |
from fastmcp.utilities.mcp_config import MCPConfig
|
|
|
|
| 33 |
|
| 34 |
from .transports import (
|
| 35 |
ClientTransportT,
|
|
|
|
| 659 |
arguments: dict[str, Any] | None = None,
|
| 660 |
timeout: datetime.timedelta | float | int | None = None,
|
| 661 |
progress_handler: ProgressHandler | None = None,
|
| 662 |
+
) -> list[ContentBlock]:
|
| 663 |
"""Call a tool on the server.
|
| 664 |
|
| 665 |
Unlike call_tool_mcp, this method raises a ToolError if the tool call results in an error.
|
src/fastmcp/prompts/prompt.py
CHANGED
|
@@ -8,9 +8,9 @@ from collections.abc import Awaitable, Callable, Sequence
|
|
| 8 |
from typing import TYPE_CHECKING, Any
|
| 9 |
|
| 10 |
import pydantic_core
|
|
|
|
| 11 |
from mcp.types import Prompt as MCPPrompt
|
| 12 |
from mcp.types import PromptArgument as MCPPromptArgument
|
| 13 |
-
from mcp.types import PromptMessage, Role, TextContent
|
| 14 |
from pydantic import Field, TypeAdapter, validate_call
|
| 15 |
|
| 16 |
from fastmcp.exceptions import PromptError
|
|
@@ -20,7 +20,6 @@ from fastmcp.utilities.json_schema import compress_schema
|
|
| 20 |
from fastmcp.utilities.logging import get_logger
|
| 21 |
from fastmcp.utilities.types import (
|
| 22 |
FastMCPBaseModel,
|
| 23 |
-
MCPContent,
|
| 24 |
find_kwarg_by_type,
|
| 25 |
get_cached_typeadapter,
|
| 26 |
)
|
|
@@ -33,7 +32,7 @@ logger = get_logger(__name__)
|
|
| 33 |
|
| 34 |
|
| 35 |
def Message(
|
| 36 |
-
content: str |
|
| 37 |
) -> PromptMessage:
|
| 38 |
"""A user-friendly constructor for PromptMessage."""
|
| 39 |
if isinstance(content, str):
|
|
|
|
| 8 |
from typing import TYPE_CHECKING, Any
|
| 9 |
|
| 10 |
import pydantic_core
|
| 11 |
+
from mcp.types import ContentBlock, PromptMessage, Role, TextContent
|
| 12 |
from mcp.types import Prompt as MCPPrompt
|
| 13 |
from mcp.types import PromptArgument as MCPPromptArgument
|
|
|
|
| 14 |
from pydantic import Field, TypeAdapter, validate_call
|
| 15 |
|
| 16 |
from fastmcp.exceptions import PromptError
|
|
|
|
| 20 |
from fastmcp.utilities.logging import get_logger
|
| 21 |
from fastmcp.utilities.types import (
|
| 22 |
FastMCPBaseModel,
|
|
|
|
| 23 |
find_kwarg_by_type,
|
| 24 |
get_cached_typeadapter,
|
| 25 |
)
|
|
|
|
| 32 |
|
| 33 |
|
| 34 |
def Message(
|
| 35 |
+
content: str | ContentBlock, role: Role | None = None, **kwargs: Any
|
| 36 |
) -> PromptMessage:
|
| 37 |
"""A user-friendly constructor for PromptMessage."""
|
| 38 |
if isinstance(content, str):
|
src/fastmcp/server/context.py
CHANGED
|
@@ -11,6 +11,7 @@ from mcp.server.lowlevel.helper_types import ReadResourceContents
|
|
| 11 |
from mcp.server.lowlevel.server import request_ctx
|
| 12 |
from mcp.shared.context import RequestContext
|
| 13 |
from mcp.types import (
|
|
|
|
| 14 |
CreateMessageResult,
|
| 15 |
ModelHint,
|
| 16 |
ModelPreferences,
|
|
@@ -25,7 +26,6 @@ import fastmcp.server.dependencies
|
|
| 25 |
from fastmcp import settings
|
| 26 |
from fastmcp.server.server import FastMCP
|
| 27 |
from fastmcp.utilities.logging import get_logger
|
| 28 |
-
from fastmcp.utilities.types import MCPContent
|
| 29 |
|
| 30 |
logger = get_logger(__name__)
|
| 31 |
|
|
@@ -243,7 +243,7 @@ class Context:
|
|
| 243 |
temperature: float | None = None,
|
| 244 |
max_tokens: int | None = None,
|
| 245 |
model_preferences: ModelPreferences | str | list[str] | None = None,
|
| 246 |
-
) ->
|
| 247 |
"""
|
| 248 |
Send a sampling request to the client and await the response.
|
| 249 |
|
|
|
|
| 11 |
from mcp.server.lowlevel.server import request_ctx
|
| 12 |
from mcp.shared.context import RequestContext
|
| 13 |
from mcp.types import (
|
| 14 |
+
ContentBlock,
|
| 15 |
CreateMessageResult,
|
| 16 |
ModelHint,
|
| 17 |
ModelPreferences,
|
|
|
|
| 26 |
from fastmcp import settings
|
| 27 |
from fastmcp.server.server import FastMCP
|
| 28 |
from fastmcp.utilities.logging import get_logger
|
|
|
|
| 29 |
|
| 30 |
logger = get_logger(__name__)
|
| 31 |
|
|
|
|
| 243 |
temperature: float | None = None,
|
| 244 |
max_tokens: int | None = None,
|
| 245 |
model_preferences: ModelPreferences | str | list[str] | None = None,
|
| 246 |
+
) -> ContentBlock:
|
| 247 |
"""
|
| 248 |
Send a sampling request to the client and await the response.
|
| 249 |
|
src/fastmcp/server/openapi.py
CHANGED
|
@@ -13,7 +13,7 @@ from re import Pattern
|
|
| 13 |
from typing import TYPE_CHECKING, Any, Literal
|
| 14 |
|
| 15 |
import httpx
|
| 16 |
-
from mcp.types import ToolAnnotations
|
| 17 |
from pydantic.networks import AnyUrl
|
| 18 |
|
| 19 |
import fastmcp
|
|
@@ -29,7 +29,6 @@ from fastmcp.utilities.openapi import (
|
|
| 29 |
_combine_schemas,
|
| 30 |
format_description_with_responses,
|
| 31 |
)
|
| 32 |
-
from fastmcp.utilities.types import MCPContent
|
| 33 |
|
| 34 |
if TYPE_CHECKING:
|
| 35 |
from fastmcp.server import Context
|
|
@@ -255,7 +254,7 @@ class OpenAPITool(Tool):
|
|
| 255 |
"""Custom representation to prevent recursion errors when printing."""
|
| 256 |
return f"OpenAPITool(name={self.name!r}, method={self._route.method}, path={self._route.path})"
|
| 257 |
|
| 258 |
-
async def run(self, arguments: dict[str, Any]) -> list[
|
| 259 |
"""Execute the HTTP request based on the route configuration."""
|
| 260 |
|
| 261 |
# Prepare URL
|
|
|
|
| 13 |
from typing import TYPE_CHECKING, Any, Literal
|
| 14 |
|
| 15 |
import httpx
|
| 16 |
+
from mcp.types import ContentBlock, ToolAnnotations
|
| 17 |
from pydantic.networks import AnyUrl
|
| 18 |
|
| 19 |
import fastmcp
|
|
|
|
| 29 |
_combine_schemas,
|
| 30 |
format_description_with_responses,
|
| 31 |
)
|
|
|
|
| 32 |
|
| 33 |
if TYPE_CHECKING:
|
| 34 |
from fastmcp.server import Context
|
|
|
|
| 254 |
"""Custom representation to prevent recursion errors when printing."""
|
| 255 |
return f"OpenAPITool(name={self.name!r}, method={self._route.method}, path={self._route.path})"
|
| 256 |
|
| 257 |
+
async def run(self, arguments: dict[str, Any]) -> list[ContentBlock]:
|
| 258 |
"""Execute the HTTP request based on the route configuration."""
|
| 259 |
|
| 260 |
# Prepare URL
|
src/fastmcp/server/proxy.py
CHANGED
|
@@ -8,6 +8,7 @@ from mcp.shared.exceptions import McpError
|
|
| 8 |
from mcp.types import (
|
| 9 |
METHOD_NOT_FOUND,
|
| 10 |
BlobResourceContents,
|
|
|
|
| 11 |
GetPromptResult,
|
| 12 |
TextResourceContents,
|
| 13 |
)
|
|
@@ -25,7 +26,6 @@ from fastmcp.server.server import FastMCP
|
|
| 25 |
from fastmcp.tools.tool import Tool
|
| 26 |
from fastmcp.tools.tool_manager import ToolManager
|
| 27 |
from fastmcp.utilities.logging import get_logger
|
| 28 |
-
from fastmcp.utilities.types import MCPContent
|
| 29 |
|
| 30 |
if TYPE_CHECKING:
|
| 31 |
from fastmcp.server import Context
|
|
@@ -67,7 +67,9 @@ class ProxyToolManager(ToolManager):
|
|
| 67 |
tools_dict = await self.get_tools()
|
| 68 |
return list(tools_dict.values())
|
| 69 |
|
| 70 |
-
async def call_tool(
|
|
|
|
|
|
|
| 71 |
"""Calls a tool, trying local/mounted first, then proxy if not found."""
|
| 72 |
try:
|
| 73 |
# First try local and mounted tools
|
|
@@ -230,7 +232,7 @@ class ProxyTool(Tool):
|
|
| 230 |
self,
|
| 231 |
arguments: dict[str, Any],
|
| 232 |
context: Context | None = None,
|
| 233 |
-
) -> list[
|
| 234 |
"""Executes the tool by making a call through the client."""
|
| 235 |
# This is where the remote execution logic lives.
|
| 236 |
async with self._client:
|
|
|
|
| 8 |
from mcp.types import (
|
| 9 |
METHOD_NOT_FOUND,
|
| 10 |
BlobResourceContents,
|
| 11 |
+
ContentBlock,
|
| 12 |
GetPromptResult,
|
| 13 |
TextResourceContents,
|
| 14 |
)
|
|
|
|
| 26 |
from fastmcp.tools.tool import Tool
|
| 27 |
from fastmcp.tools.tool_manager import ToolManager
|
| 28 |
from fastmcp.utilities.logging import get_logger
|
|
|
|
| 29 |
|
| 30 |
if TYPE_CHECKING:
|
| 31 |
from fastmcp.server import Context
|
|
|
|
| 67 |
tools_dict = await self.get_tools()
|
| 68 |
return list(tools_dict.values())
|
| 69 |
|
| 70 |
+
async def call_tool(
|
| 71 |
+
self, key: str, arguments: dict[str, Any]
|
| 72 |
+
) -> list[ContentBlock]:
|
| 73 |
"""Calls a tool, trying local/mounted first, then proxy if not found."""
|
| 74 |
try:
|
| 75 |
# First try local and mounted tools
|
|
|
|
| 232 |
self,
|
| 233 |
arguments: dict[str, Any],
|
| 234 |
context: Context | None = None,
|
| 235 |
+
) -> list[ContentBlock]:
|
| 236 |
"""Executes the tool by making a call through the client."""
|
| 237 |
# This is where the remote execution logic lives.
|
| 238 |
async with self._client:
|
src/fastmcp/server/server.py
CHANGED
|
@@ -27,6 +27,7 @@ from mcp.server.lowlevel.server import Server as MCPServer
|
|
| 27 |
from mcp.server.stdio import stdio_server
|
| 28 |
from mcp.types import (
|
| 29 |
AnyFunction,
|
|
|
|
| 30 |
GetPromptResult,
|
| 31 |
ToolAnnotations,
|
| 32 |
)
|
|
@@ -62,7 +63,6 @@ from fastmcp.utilities.cache import TimedCache
|
|
| 62 |
from fastmcp.utilities.components import FastMCPComponent
|
| 63 |
from fastmcp.utilities.logging import get_logger
|
| 64 |
from fastmcp.utilities.mcp_config import MCPConfig
|
| 65 |
-
from fastmcp.utilities.types import MCPContent
|
| 66 |
|
| 67 |
if TYPE_CHECKING:
|
| 68 |
from fastmcp.client import Client
|
|
@@ -586,7 +586,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 586 |
|
| 587 |
async def _mcp_call_tool(
|
| 588 |
self, key: str, arguments: dict[str, Any]
|
| 589 |
-
) -> list[
|
| 590 |
"""
|
| 591 |
Handle MCP 'callTool' requests.
|
| 592 |
|
|
@@ -609,14 +609,16 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 609 |
except NotFoundError:
|
| 610 |
raise NotFoundError(f"Unknown tool: {key}")
|
| 611 |
|
| 612 |
-
async def _call_tool(
|
|
|
|
|
|
|
| 613 |
"""
|
| 614 |
Applies this server's middleware and delegates the filtered call to the manager.
|
| 615 |
"""
|
| 616 |
|
| 617 |
async def _handler(
|
| 618 |
context: MiddlewareContext[mcp.types.CallToolRequestParams],
|
| 619 |
-
) -> list[
|
| 620 |
tool = await self._tool_manager.get_tool(context.message.name)
|
| 621 |
if not self._should_enable_component(tool):
|
| 622 |
raise NotFoundError(f"Unknown tool: {context.message.name!r}")
|
|
|
|
| 27 |
from mcp.server.stdio import stdio_server
|
| 28 |
from mcp.types import (
|
| 29 |
AnyFunction,
|
| 30 |
+
ContentBlock,
|
| 31 |
GetPromptResult,
|
| 32 |
ToolAnnotations,
|
| 33 |
)
|
|
|
|
| 63 |
from fastmcp.utilities.components import FastMCPComponent
|
| 64 |
from fastmcp.utilities.logging import get_logger
|
| 65 |
from fastmcp.utilities.mcp_config import MCPConfig
|
|
|
|
| 66 |
|
| 67 |
if TYPE_CHECKING:
|
| 68 |
from fastmcp.client import Client
|
|
|
|
| 586 |
|
| 587 |
async def _mcp_call_tool(
|
| 588 |
self, key: str, arguments: dict[str, Any]
|
| 589 |
+
) -> list[ContentBlock]:
|
| 590 |
"""
|
| 591 |
Handle MCP 'callTool' requests.
|
| 592 |
|
|
|
|
| 609 |
except NotFoundError:
|
| 610 |
raise NotFoundError(f"Unknown tool: {key}")
|
| 611 |
|
| 612 |
+
async def _call_tool(
|
| 613 |
+
self, key: str, arguments: dict[str, Any]
|
| 614 |
+
) -> list[ContentBlock]:
|
| 615 |
"""
|
| 616 |
Applies this server's middleware and delegates the filtered call to the manager.
|
| 617 |
"""
|
| 618 |
|
| 619 |
async def _handler(
|
| 620 |
context: MiddlewareContext[mcp.types.CallToolRequestParams],
|
| 621 |
+
) -> list[ContentBlock]:
|
| 622 |
tool = await self._tool_manager.get_tool(context.message.name)
|
| 623 |
if not self._should_enable_component(tool):
|
| 624 |
raise NotFoundError(f"Unknown tool: {context.message.name!r}")
|
src/fastmcp/tools/tool.py
CHANGED
|
@@ -7,7 +7,7 @@ from dataclasses import dataclass
|
|
| 7 |
from typing import TYPE_CHECKING, Any
|
| 8 |
|
| 9 |
import pydantic_core
|
| 10 |
-
from mcp.types import TextContent, ToolAnnotations
|
| 11 |
from mcp.types import Tool as MCPTool
|
| 12 |
from pydantic import Field
|
| 13 |
|
|
@@ -20,7 +20,6 @@ from fastmcp.utilities.types import (
|
|
| 20 |
Audio,
|
| 21 |
File,
|
| 22 |
Image,
|
| 23 |
-
MCPContent,
|
| 24 |
find_kwarg_by_type,
|
| 25 |
get_cached_typeadapter,
|
| 26 |
)
|
|
@@ -78,7 +77,7 @@ class Tool(FastMCPComponent):
|
|
| 78 |
enabled=enabled,
|
| 79 |
)
|
| 80 |
|
| 81 |
-
async def run(self, arguments: dict[str, Any]) -> list[
|
| 82 |
"""Run the tool with arguments."""
|
| 83 |
raise NotImplementedError("Subclasses must implement run()")
|
| 84 |
|
|
@@ -143,7 +142,7 @@ class FunctionTool(Tool):
|
|
| 143 |
enabled=enabled if enabled is not None else True,
|
| 144 |
)
|
| 145 |
|
| 146 |
-
async def run(self, arguments: dict[str, Any]) -> list[
|
| 147 |
"""Run the tool with arguments."""
|
| 148 |
from fastmcp.server.context import Context
|
| 149 |
|
|
@@ -264,12 +263,12 @@ def _convert_to_content(
|
|
| 264 |
result: Any,
|
| 265 |
serializer: Callable[[Any], str] | None = None,
|
| 266 |
_process_as_single_item: bool = False,
|
| 267 |
-
) -> list[
|
| 268 |
"""Convert a result to a sequence of content objects."""
|
| 269 |
if result is None:
|
| 270 |
return []
|
| 271 |
|
| 272 |
-
if isinstance(result,
|
| 273 |
return [result]
|
| 274 |
|
| 275 |
if isinstance(result, Image):
|
|
@@ -292,7 +291,7 @@ def _convert_to_content(
|
|
| 292 |
other_content = []
|
| 293 |
|
| 294 |
for item in result:
|
| 295 |
-
if isinstance(item,
|
| 296 |
mcp_types.append(_convert_to_content(item)[0])
|
| 297 |
else:
|
| 298 |
other_content.append(item)
|
|
|
|
| 7 |
from typing import TYPE_CHECKING, Any
|
| 8 |
|
| 9 |
import pydantic_core
|
| 10 |
+
from mcp.types import ContentBlock, TextContent, ToolAnnotations
|
| 11 |
from mcp.types import Tool as MCPTool
|
| 12 |
from pydantic import Field
|
| 13 |
|
|
|
|
| 20 |
Audio,
|
| 21 |
File,
|
| 22 |
Image,
|
|
|
|
| 23 |
find_kwarg_by_type,
|
| 24 |
get_cached_typeadapter,
|
| 25 |
)
|
|
|
|
| 77 |
enabled=enabled,
|
| 78 |
)
|
| 79 |
|
| 80 |
+
async def run(self, arguments: dict[str, Any]) -> list[ContentBlock]:
|
| 81 |
"""Run the tool with arguments."""
|
| 82 |
raise NotImplementedError("Subclasses must implement run()")
|
| 83 |
|
|
|
|
| 142 |
enabled=enabled if enabled is not None else True,
|
| 143 |
)
|
| 144 |
|
| 145 |
+
async def run(self, arguments: dict[str, Any]) -> list[ContentBlock]:
|
| 146 |
"""Run the tool with arguments."""
|
| 147 |
from fastmcp.server.context import Context
|
| 148 |
|
|
|
|
| 263 |
result: Any,
|
| 264 |
serializer: Callable[[Any], str] | None = None,
|
| 265 |
_process_as_single_item: bool = False,
|
| 266 |
+
) -> list[ContentBlock]:
|
| 267 |
"""Convert a result to a sequence of content objects."""
|
| 268 |
if result is None:
|
| 269 |
return []
|
| 270 |
|
| 271 |
+
if isinstance(result, ContentBlock):
|
| 272 |
return [result]
|
| 273 |
|
| 274 |
if isinstance(result, Image):
|
|
|
|
| 291 |
other_content = []
|
| 292 |
|
| 293 |
for item in result:
|
| 294 |
+
if isinstance(item, ContentBlock | Image | Audio | File):
|
| 295 |
mcp_types.append(_convert_to_content(item)[0])
|
| 296 |
else:
|
| 297 |
other_content.append(item)
|
src/fastmcp/tools/tool_manager.py
CHANGED
|
@@ -4,14 +4,13 @@ import warnings
|
|
| 4 |
from collections.abc import Callable
|
| 5 |
from typing import TYPE_CHECKING, Any
|
| 6 |
|
| 7 |
-
from mcp.types import ToolAnnotations
|
| 8 |
|
| 9 |
from fastmcp import settings
|
| 10 |
from fastmcp.exceptions import NotFoundError, ToolError
|
| 11 |
from fastmcp.settings import DuplicateBehavior
|
| 12 |
from fastmcp.tools.tool import Tool
|
| 13 |
from fastmcp.utilities.logging import get_logger
|
| 14 |
-
from fastmcp.utilities.types import MCPContent
|
| 15 |
|
| 16 |
if TYPE_CHECKING:
|
| 17 |
from fastmcp.server.server import MountedServer
|
|
@@ -170,7 +169,9 @@ class ToolManager:
|
|
| 170 |
else:
|
| 171 |
raise NotFoundError(f"Tool {key!r} not found")
|
| 172 |
|
| 173 |
-
async def call_tool(
|
|
|
|
|
|
|
| 174 |
"""
|
| 175 |
Internal API for servers: Finds and calls a tool, respecting the
|
| 176 |
filtered protocol path.
|
|
|
|
| 4 |
from collections.abc import Callable
|
| 5 |
from typing import TYPE_CHECKING, Any
|
| 6 |
|
| 7 |
+
from mcp.types import ContentBlock, ToolAnnotations
|
| 8 |
|
| 9 |
from fastmcp import settings
|
| 10 |
from fastmcp.exceptions import NotFoundError, ToolError
|
| 11 |
from fastmcp.settings import DuplicateBehavior
|
| 12 |
from fastmcp.tools.tool import Tool
|
| 13 |
from fastmcp.utilities.logging import get_logger
|
|
|
|
| 14 |
|
| 15 |
if TYPE_CHECKING:
|
| 16 |
from fastmcp.server.server import MountedServer
|
|
|
|
| 169 |
else:
|
| 170 |
raise NotFoundError(f"Tool {key!r} not found")
|
| 171 |
|
| 172 |
+
async def call_tool(
|
| 173 |
+
self, key: str, arguments: dict[str, Any]
|
| 174 |
+
) -> list[ContentBlock]:
|
| 175 |
"""
|
| 176 |
Internal API for servers: Finds and calls a tool, respecting the
|
| 177 |
filtered protocol path.
|
src/fastmcp/tools/tool_transform.py
CHANGED
|
@@ -7,12 +7,12 @@ from dataclasses import dataclass
|
|
| 7 |
from types import EllipsisType
|
| 8 |
from typing import Any, Literal
|
| 9 |
|
| 10 |
-
from mcp.types import ToolAnnotations
|
| 11 |
from pydantic import ConfigDict
|
| 12 |
|
| 13 |
from fastmcp.tools.tool import ParsedFunction, Tool
|
| 14 |
from fastmcp.utilities.logging import get_logger
|
| 15 |
-
from fastmcp.utilities.types import
|
| 16 |
|
| 17 |
logger = get_logger(__name__)
|
| 18 |
|
|
@@ -202,7 +202,7 @@ class TransformedTool(Tool):
|
|
| 202 |
forwarding_fn: Callable[..., Any] # Always present, handles arg transformation
|
| 203 |
transform_args: dict[str, ArgTransform]
|
| 204 |
|
| 205 |
-
async def run(self, arguments: dict[str, Any]) -> list[
|
| 206 |
"""Run the tool with context set for forward() functions.
|
| 207 |
|
| 208 |
This method executes the tool's function while setting up the context
|
|
|
|
| 7 |
from types import EllipsisType
|
| 8 |
from typing import Any, Literal
|
| 9 |
|
| 10 |
+
from mcp.types import ContentBlock, ToolAnnotations
|
| 11 |
from pydantic import ConfigDict
|
| 12 |
|
| 13 |
from fastmcp.tools.tool import ParsedFunction, Tool
|
| 14 |
from fastmcp.utilities.logging import get_logger
|
| 15 |
+
from fastmcp.utilities.types import get_cached_typeadapter
|
| 16 |
|
| 17 |
logger = get_logger(__name__)
|
| 18 |
|
|
|
|
| 202 |
forwarding_fn: Callable[..., Any] # Always present, handles arg transformation
|
| 203 |
transform_args: dict[str, ArgTransform]
|
| 204 |
|
| 205 |
+
async def run(self, arguments: dict[str, Any]) -> list[ContentBlock]:
|
| 206 |
"""Run the tool with context set for forward() functions.
|
| 207 |
|
| 208 |
This method executes the tool's function while setting up the context
|
src/fastmcp/utilities/types.py
CHANGED
|
@@ -7,7 +7,7 @@ from collections.abc import Callable
|
|
| 7 |
from functools import lru_cache
|
| 8 |
from pathlib import Path
|
| 9 |
from types import UnionType
|
| 10 |
-
from typing import Annotated,
|
| 11 |
|
| 12 |
from mcp.types import (
|
| 13 |
Annotations,
|
|
@@ -15,15 +15,12 @@ from mcp.types import (
|
|
| 15 |
BlobResourceContents,
|
| 16 |
EmbeddedResource,
|
| 17 |
ImageContent,
|
| 18 |
-
|
| 19 |
-
TextResourceContents, # Added import
|
| 20 |
)
|
| 21 |
from pydantic import AnyUrl, BaseModel, ConfigDict, TypeAdapter, UrlConstraints
|
| 22 |
|
| 23 |
T = TypeVar("T")
|
| 24 |
|
| 25 |
-
MCPContent: TypeAlias = TextContent | ImageContent | AudioContent | EmbeddedResource
|
| 26 |
-
|
| 27 |
|
| 28 |
class FastMCPBaseModel(BaseModel):
|
| 29 |
"""Base model for FastMCP models."""
|
|
|
|
| 7 |
from functools import lru_cache
|
| 8 |
from pathlib import Path
|
| 9 |
from types import UnionType
|
| 10 |
+
from typing import Annotated, TypeVar, Union, get_args, get_origin
|
| 11 |
|
| 12 |
from mcp.types import (
|
| 13 |
Annotations,
|
|
|
|
| 15 |
BlobResourceContents,
|
| 16 |
EmbeddedResource,
|
| 17 |
ImageContent,
|
| 18 |
+
TextResourceContents,
|
|
|
|
| 19 |
)
|
| 20 |
from pydantic import AnyUrl, BaseModel, ConfigDict, TypeAdapter, UrlConstraints
|
| 21 |
|
| 22 |
T = TypeVar("T")
|
| 23 |
|
|
|
|
|
|
|
| 24 |
|
| 25 |
class FastMCPBaseModel(BaseModel):
|
| 26 |
"""Base model for FastMCP models."""
|
tests/server/openapi/test_openapi.py
CHANGED
|
@@ -223,6 +223,8 @@ class TestTools:
|
|
| 223 |
|
| 224 |
assert tools[0].model_dump() == dict(
|
| 225 |
name="create_user_users_post",
|
|
|
|
|
|
|
| 226 |
annotations=None,
|
| 227 |
description=IsStr(regex=r"^Create a new user\..*$", regex_flags=re.DOTALL),
|
| 228 |
inputSchema={
|
|
@@ -236,6 +238,8 @@ class TestTools:
|
|
| 236 |
)
|
| 237 |
assert tools[1].model_dump() == dict(
|
| 238 |
name="update_user_name_users",
|
|
|
|
|
|
|
| 239 |
annotations=None,
|
| 240 |
description=IsStr(
|
| 241 |
regex=r"^Update a user's name\..*$", regex_flags=re.DOTALL
|
|
|
|
| 223 |
|
| 224 |
assert tools[0].model_dump() == dict(
|
| 225 |
name="create_user_users_post",
|
| 226 |
+
meta=None,
|
| 227 |
+
title=None,
|
| 228 |
annotations=None,
|
| 229 |
description=IsStr(regex=r"^Create a new user\..*$", regex_flags=re.DOTALL),
|
| 230 |
inputSchema={
|
|
|
|
| 238 |
)
|
| 239 |
assert tools[1].model_dump() == dict(
|
| 240 |
name="update_user_name_users",
|
| 241 |
+
meta=None,
|
| 242 |
+
title=None,
|
| 243 |
annotations=None,
|
| 244 |
description=IsStr(
|
| 245 |
regex=r"^Update a user's name\..*$", regex_flags=re.DOTALL
|
tests/tools/test_tool.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
|
|
|
|
|
| 1 |
import pytest
|
| 2 |
from mcp.types import (
|
| 3 |
AudioContent,
|
|
@@ -696,7 +698,7 @@ class TestConvertResultToContent:
|
|
| 696 |
assert len(result) == 1
|
| 697 |
assert isinstance(result[0], TextContent)
|
| 698 |
# Should fall back to default serializer (pydantic_core.to_json)
|
| 699 |
-
assert result[0].text ==
|
| 700 |
assert "Error serializing tool result" in caplog.text
|
| 701 |
|
| 702 |
def test_process_as_single_item_flag(self):
|
|
@@ -714,7 +716,7 @@ class TestConvertResultToContent:
|
|
| 714 |
assert len(result) == 1
|
| 715 |
assert isinstance(result[0], TextContent)
|
| 716 |
|
| 717 |
-
assert (
|
| 718 |
-
|
| 719 |
-
|
| 720 |
-
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
|
| 3 |
import pytest
|
| 4 |
from mcp.types import (
|
| 5 |
AudioContent,
|
|
|
|
| 698 |
assert len(result) == 1
|
| 699 |
assert isinstance(result[0], TextContent)
|
| 700 |
# Should fall back to default serializer (pydantic_core.to_json)
|
| 701 |
+
assert json.loads(result[0].text) == {"a": 1}
|
| 702 |
assert "Error serializing tool result" in caplog.text
|
| 703 |
|
| 704 |
def test_process_as_single_item_flag(self):
|
|
|
|
| 716 |
assert len(result) == 1
|
| 717 |
assert isinstance(result[0], TextContent)
|
| 718 |
|
| 719 |
+
assert json.loads(result[0].text) == [
|
| 720 |
+
1,
|
| 721 |
+
{"type": "text", "text": "hello", "annotations": None, "_meta": None},
|
| 722 |
+
]
|