From 517b551107e265587c9fc0084452f5d504dc0a62 Mon Sep 17 00:00:00 2001 From: ihrpr Date: Mon, 14 Jul 2025 14:48:14 +0100 Subject: [PATCH 1/2] direct execution and display utilities --- README.md | 96 +++++++++++++++++-- .../snippets/clients/display_utilities.py | 71 ++++++++++++++ examples/snippets/pyproject.toml | 2 + examples/snippets/servers/direct_execution.py | 27 ++++++ .../snippets/servers/structured_output.py | 68 +++++++++++++ 5 files changed, 256 insertions(+), 8 deletions(-) create mode 100644 examples/snippets/clients/display_utilities.py create mode 100644 examples/snippets/servers/direct_execution.py diff --git a/README.md b/README.md index 08ed251cc..585e9538c 100644 --- a/README.md +++ b/README.md @@ -338,6 +338,8 @@ to the `@tool` decorator. ```python """Example showing structured output with tools.""" +from typing import TypedDict + from pydantic import BaseModel, Field from mcp.server.fastmcp import FastMCP @@ -365,12 +367,8 @@ def get_weather(city: str) -> WeatherData: condition="sunny", wind_speed=5.2, ) -``` -_Full example: [examples/snippets/servers/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/structured_output.py)_ - -```python # Using TypedDict for simpler structures class LocationInfo(TypedDict): latitude: float @@ -437,6 +435,11 @@ def get_temperature(city: str) -> float: # Returns: {"result": 22.5} ``` +_Full example: [examples/snippets/servers/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/structured_output.py)_ + + + + ### Prompts Prompts are reusable templates that help LLMs interact with your server effectively: @@ -814,21 +817,46 @@ uv run mcp install server.py -f .env For advanced scenarios like custom deployments: + ```python +"""Example showing direct execution of an MCP server. + +This is the simplest way to run an MCP server directly. +cd to the `examples/snippets` directory and run: + uv run direct-execution-server + or + python servers/direct_execution.py +""" + from mcp.server.fastmcp import FastMCP mcp = FastMCP("My App") -if __name__ == "__main__": + +@mcp.tool() +def hello(name: str = "World") -> str: + """Say hello to someone.""" + return f"Hello, {name}!" + + +def main(): + """Entry point for the direct execution server.""" mcp.run() + + +if __name__ == "__main__": + main() ``` +_Full example: [examples/snippets/servers/direct_execution.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/direct_execution.py)_ + + Run it with: ```bash -python server.py +python servers/direct_execution.py # or -uv run mcp run server.py +uv run mcp run servers/direct_execution.py ``` Note that `uv run mcp run` or `uv run mcp dev` only supports server using FastMCP and not the low-level server variant. @@ -1277,9 +1305,30 @@ async def main(): When building MCP clients, the SDK provides utilities to help display human-readable names for tools, resources, and prompts: + ```python +"""Client display utilities example. + +This example shows how to use the SDK's display utilities to show +human-readable names for tools, resources, and prompts. + +cd to the `examples/snippets` directory and run: + uv run display-utilities-client +""" + +import asyncio +import os + +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client from mcp.shared.metadata_utils import get_display_name -from mcp.client.session import ClientSession + +# Create server parameters for stdio connection +server_params = StdioServerParameters( + command="uv", # Using uv to run the server + args=["run", "server", "fastmcp_quickstart", "stdio"], + env={"UV_INDEX": os.environ.get("UV_INDEX", "")}, +) async def display_tools(session: ClientSession): @@ -1301,8 +1350,39 @@ async def display_resources(session: ClientSession): for resource in resources_response.resources: display_name = get_display_name(resource) print(f"Resource: {display_name} ({resource.uri})") + + templates_response = await session.list_resource_templates() + for template in templates_response.resourceTemplates: + display_name = get_display_name(template) + print(f"Resource Template: {display_name}") + + +async def run(): + """Run the display utilities example.""" + async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write) as session: + # Initialize the connection + await session.initialize() + + print("=== Available Tools ===") + await display_tools(session) + + print("\n=== Available Resources ===") + await display_resources(session) + + +def main(): + """Entry point for the display utilities client.""" + asyncio.run(run()) + + +if __name__ == "__main__": + main() ``` +_Full example: [examples/snippets/clients/display_utilities.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/display_utilities.py)_ + + The `get_display_name()` function implements the proper precedence rules for displaying names: - For tools: `title` > `annotations.title` > `name` diff --git a/examples/snippets/clients/display_utilities.py b/examples/snippets/clients/display_utilities.py new file mode 100644 index 000000000..ee2280b3a --- /dev/null +++ b/examples/snippets/clients/display_utilities.py @@ -0,0 +1,71 @@ +"""Client display utilities example. + +This example shows how to use the SDK's display utilities to show +human-readable names for tools, resources, and prompts. + +cd to the `examples/snippets` directory and run: + uv run display-utilities-client +""" + +import asyncio +import os + +from mcp import ClientSession, StdioServerParameters +from mcp.client.stdio import stdio_client +from mcp.shared.metadata_utils import get_display_name + +# Create server parameters for stdio connection +server_params = StdioServerParameters( + command="uv", # Using uv to run the server + args=["run", "server", "fastmcp_quickstart", "stdio"], + env={"UV_INDEX": os.environ.get("UV_INDEX", "")}, +) + + +async def display_tools(session: ClientSession): + """Display available tools with human-readable names""" + tools_response = await session.list_tools() + + for tool in tools_response.tools: + # get_display_name() returns the title if available, otherwise the name + display_name = get_display_name(tool) + print(f"Tool: {display_name}") + if tool.description: + print(f" {tool.description}") + + +async def display_resources(session: ClientSession): + """Display available resources with human-readable names""" + resources_response = await session.list_resources() + + for resource in resources_response.resources: + display_name = get_display_name(resource) + print(f"Resource: {display_name} ({resource.uri})") + + templates_response = await session.list_resource_templates() + for template in templates_response.resourceTemplates: + display_name = get_display_name(template) + print(f"Resource Template: {display_name}") + + +async def run(): + """Run the display utilities example.""" + async with stdio_client(server_params) as (read, write): + async with ClientSession(read, write) as session: + # Initialize the connection + await session.initialize() + + print("=== Available Tools ===") + await display_tools(session) + + print("\n=== Available Resources ===") + await display_resources(session) + + +def main(): + """Entry point for the display utilities client.""" + asyncio.run(run()) + + +if __name__ == "__main__": + main() diff --git a/examples/snippets/pyproject.toml b/examples/snippets/pyproject.toml index e0ca9ba6b..7e6e032c7 100644 --- a/examples/snippets/pyproject.toml +++ b/examples/snippets/pyproject.toml @@ -18,3 +18,5 @@ packages = ["servers", "clients"] server = "servers:run_server" client = "clients.stdio_client:main" completion-client = "clients.completion_client:main" +direct-execution-server = "servers.direct_execution:main" +display-utilities-client = "clients.display_utilities:main" diff --git a/examples/snippets/servers/direct_execution.py b/examples/snippets/servers/direct_execution.py new file mode 100644 index 000000000..65a6fbbf3 --- /dev/null +++ b/examples/snippets/servers/direct_execution.py @@ -0,0 +1,27 @@ +"""Example showing direct execution of an MCP server. + +This is the simplest way to run an MCP server directly. +cd to the `examples/snippets` directory and run: + uv run direct-execution-server + or + python servers/direct_execution.py +""" + +from mcp.server.fastmcp import FastMCP + +mcp = FastMCP("My App") + + +@mcp.tool() +def hello(name: str = "World") -> str: + """Say hello to someone.""" + return f"Hello, {name}!" + + +def main(): + """Entry point for the direct execution server.""" + mcp.run() + + +if __name__ == "__main__": + main() diff --git a/examples/snippets/servers/structured_output.py b/examples/snippets/servers/structured_output.py index 4cce69d8c..263f6be51 100644 --- a/examples/snippets/servers/structured_output.py +++ b/examples/snippets/servers/structured_output.py @@ -1,5 +1,7 @@ """Example showing structured output with tools.""" +from typing import TypedDict + from pydantic import BaseModel, Field from mcp.server.fastmcp import FastMCP @@ -27,3 +29,69 @@ def get_weather(city: str) -> WeatherData: condition="sunny", wind_speed=5.2, ) + + +# Using TypedDict for simpler structures +class LocationInfo(TypedDict): + latitude: float + longitude: float + name: str + + +@mcp.tool() +def get_location(address: str) -> LocationInfo: + """Get location coordinates""" + return LocationInfo(latitude=51.5074, longitude=-0.1278, name="London, UK") + + +# Using dict[str, Any] for flexible schemas +@mcp.tool() +def get_statistics(data_type: str) -> dict[str, float]: + """Get various statistics""" + return {"mean": 42.5, "median": 40.0, "std_dev": 5.2} + + +# Ordinary classes with type hints work for structured output +class UserProfile: + name: str + age: int + email: str | None = None + + def __init__(self, name: str, age: int, email: str | None = None): + self.name = name + self.age = age + self.email = email + + +@mcp.tool() +def get_user(user_id: str) -> UserProfile: + """Get user profile - returns structured data""" + return UserProfile(name="Alice", age=30, email="alice@example.com") + + +# Classes WITHOUT type hints cannot be used for structured output +class UntypedConfig: + def __init__(self, setting1, setting2): + self.setting1 = setting1 + self.setting2 = setting2 + + +@mcp.tool() +def get_config() -> UntypedConfig: + """This returns unstructured output - no schema generated""" + return UntypedConfig("value1", "value2") + + +# Lists and other types are wrapped automatically +@mcp.tool() +def list_cities() -> list[str]: + """Get a list of cities""" + return ["London", "Paris", "Tokyo"] + # Returns: {"result": ["London", "Paris", "Tokyo"]} + + +@mcp.tool() +def get_temperature(city: str) -> float: + """Get temperature as a simple float""" + return 22.5 + # Returns: {"result": 22.5} From 410e7435f8c255375b2abf47632431fb3d8c1f6f Mon Sep 17 00:00:00 2001 From: ihrpr Date: Mon, 14 Jul 2025 15:05:22 +0100 Subject: [PATCH 2/2] CI --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 585e9538c..b4587c1a0 100644 --- a/README.md +++ b/README.md @@ -438,8 +438,6 @@ def get_temperature(city: str) -> float: _Full example: [examples/snippets/servers/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/structured_output.py)_ - - ### Prompts Prompts are reusable templates that help LLMs interact with your server effectively: