FastMCP

# Changes to Implement Prompt Arguments accepting dict[str,Any] # Proposal: Enhanced Prompt Arguments in Model Context Protocol ## Background The Model Context Protocol (MCP) currently defines two main mechanisms for parameter passing: 1. Tools: Support typed arguments with JSON Schema validation 2. Prompts: Support string-only arguments without schema validation This creates an inconsistency in the specification, particularly given the security and validation requirements stated for prompts: - Servers SHOULD validate prompt arguments before processing - Implementations MUST carefully validate inputs/outputs to prevent injection attacks - Both parties SHOULD respect capability negotiation It is overly complex and likely not backward compatible to change prompt arguments to use a schema as tools do. However, it is possible to make a small compatible change to allow more complex inputs and enable reasonable model validation with a simple backwards compatible change. That change is specifically to allow the less strict dict[str, Any] for arguments rather than the currently strict dict[str, str]. As Any includes str this change is fully backwards compatible. There are limitations to this change as far as passing argument schema to the client (you cannot directly do that), but with some 'rules' regarding how to define function arguments it is possible to build clients which can discover the arguments model of a server prompt at runtime. ## Example You can check the changes with various new examples using the provided example client, prompt_complex_data.py ```text examples/fastmcp/echo_prompt.py examples/fastmcp/echo_prompt_args.py examples/fastmcp/edge_case_prompts.py examples/fastmcp/prompt_complex_data.py ``` ## Changed files ```text modified: pyproject.toml modified: src/mcp/client/session.py modified: src/mcp/server/fastmcp/prompts/base.py modified: src/mcp/server/fastmcp/server.py modified: src/mcp/server/lowlevel/server.py modified: src/mcp/types.py ``` ### pyproject.toml ```text git diff pyproject.toml diff --git a/pyproject.toml b/pyproject.toml index 31a5494..9237f59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,7 +25,7 @@ dependencies = [ "anyio>=4.5", "httpx>=0.27", "httpx-sse>=0.4", - "pydantic>=2.10.1,<3.0.0", + "pydantic[email,mail]>=2.10.1,<3.0.0", "starlette>=0.27", "sse-starlette>=1.6.1", "pydantic-settings>=2.6.1", ``` ### src/mcp/client/session.py ```text git diff src/mcp/client/session.py diff --git a/src/mcp/client/session.py b/src/mcp/client/session.py index 27ca74d..87f240e 100644 --- a/src/mcp/client/session.py +++ b/src/mcp/client/session.py @@ -1,4 +1,5 @@ from datetime import timedelta +from typing import Any from anyio.streams.memory import MemoryObjectReceiveStream, MemoryObjectSendStream from pydantic import AnyUrl @@ -182,7 +183,7 @@ class ClientSession( ) async def get_prompt( - self, name: str, arguments: dict[str, str] | None = None + self, name: str, arguments: dict[str, Any] | None = None ) -> types.GetPromptResult: """Send a prompts/get request.""" return await self.send_request( ``` ### src/mcp/server/fastmcp/prompts/base.py ```text git diff src/mcp/server/fastmcp/prompts/base.py diff --git a/src/mcp/server/fastmcp/prompts/base.py b/src/mcp/server/fastmcp/prompts/base.py index 0df3d2f..e956928 100644 --- a/src/mcp/server/fastmcp/prompts/base.py +++ b/src/mcp/server/fastmcp/prompts/base.py @@ -61,6 +61,9 @@ class PromptArgument(BaseModel): required: bool = Field( default=False, description="Whether the argument is required" ) + type: str | None = Field( + default=None, description="Type of the argument" + ) class Prompt(BaseModel): @@ -108,6 +111,7 @@ class Prompt(BaseModel): name=param_name, description=param.get("description"), required=required, + type=param.get("type") # hotfix ) ) ``` ### src/mcp/server/fastmcp/server.py ```text diff --git a/src/mcp/server/fastmcp/prompts/base.py b/src/mcp/server/fastmcp/prompts/base.py index 0df3d2f..e956928 100644 --- a/src/mcp/server/fastmcp/prompts/base.py +++ b/src/mcp/server/fastmcp/prompts/base.py @@ -61,6 +61,9 @@ class PromptArgument(BaseModel): required: bool = Field( default=False, description="Whether the argument is required" ) + type: str | None = Field( + default=None, description="Type of the argument" + ) class Prompt(BaseModel): @@ -108,6 +111,7 @@ class Prompt(BaseModel): name=param_name, description=param.get("description"), required=required, + type=param.get("type") # hotfix ) ) (fullpygis) deepnpisgah@MacPro2013 python-sdk % git diff src/mcp/server/fastmcp/server.py diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index 571c7c2..b9a6822 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -471,6 +471,7 @@ class FastMCP: name=arg.name, description=arg.description, required=arg.required, + type=arg.type # add to get type on client ) for arg in (prompt.arguments or []) ], ``` ### src/mcp/server/lowlevel/server.py ```text diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index 32ea279..69ce59f 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -207,7 +207,7 @@ class Server: def get_prompt(self): def decorator( func: Callable[ - [str, dict[str, str] | None], Awaitable[types.GetPromptResult] + [str, dict[str, Any] | None], Awaitable[types.GetPromptResult] ], ): logger.debug("Registering handler for GetPromptRequest") ``` ### src/mcp/types.py diff --git a/src/mcp/types.py b/src/mcp/types.py index 4e1628c..2030b7f 100644 --- a/src/mcp/types.py +++ b/src/mcp/types.py @@ -534,6 +534,8 @@ class PromptArgument(BaseModel): """A human-readable description of the argument.""" required: bool | None = None """Whether this argument must be provided.""" + type: str | None = None + """Type of the argument.""" model_config = ConfigDict(extra="allow") @@ -560,7 +562,7 @@ class GetPromptRequestParams(RequestParams): name: str """The name of the prompt or prompt template.""" - arguments: dict[str, str] | None = None + arguments: dict[str, Any] | None = None """Arguments to use for templating the prompt.""" model_config = ConfigDict(extra="allow")