Skip to content

Commit 517b551

Browse files
committed
direct execution and display utilities
1 parent 9bf5160 commit 517b551

File tree

5 files changed

+256
-8
lines changed

5 files changed

+256
-8
lines changed

README.md

Lines changed: 88 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ to the `@tool` decorator.
338338
```python
339339
"""Example showing structured output with tools."""
340340

341+
from typing import TypedDict
342+
341343
from pydantic import BaseModel, Field
342344

343345
from mcp.server.fastmcp import FastMCP
@@ -365,12 +367,8 @@ def get_weather(city: str) -> WeatherData:
365367
condition="sunny",
366368
wind_speed=5.2,
367369
)
368-
```
369370

370-
_Full example: [examples/snippets/servers/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/structured_output.py)_
371-
<!-- /snippet-source -->
372371

373-
```python
374372
# Using TypedDict for simpler structures
375373
class LocationInfo(TypedDict):
376374
latitude: float
@@ -437,6 +435,11 @@ def get_temperature(city: str) -> float:
437435
# Returns: {"result": 22.5}
438436
```
439437

438+
_Full example: [examples/snippets/servers/structured_output.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/structured_output.py)_
439+
<!-- /snippet-source -->
440+
441+
442+
440443
### Prompts
441444

442445
Prompts are reusable templates that help LLMs interact with your server effectively:
@@ -814,21 +817,46 @@ uv run mcp install server.py -f .env
814817

815818
For advanced scenarios like custom deployments:
816819

820+
<!-- snippet-source examples/snippets/servers/direct_execution.py -->
817821
```python
822+
"""Example showing direct execution of an MCP server.
823+
824+
This is the simplest way to run an MCP server directly.
825+
cd to the `examples/snippets` directory and run:
826+
uv run direct-execution-server
827+
or
828+
python servers/direct_execution.py
829+
"""
830+
818831
from mcp.server.fastmcp import FastMCP
819832

820833
mcp = FastMCP("My App")
821834

822-
if __name__ == "__main__":
835+
836+
@mcp.tool()
837+
def hello(name: str = "World") -> str:
838+
"""Say hello to someone."""
839+
return f"Hello, {name}!"
840+
841+
842+
def main():
843+
"""Entry point for the direct execution server."""
823844
mcp.run()
845+
846+
847+
if __name__ == "__main__":
848+
main()
824849
```
825850

851+
_Full example: [examples/snippets/servers/direct_execution.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/direct_execution.py)_
852+
<!-- /snippet-source -->
853+
826854
Run it with:
827855

828856
```bash
829-
python server.py
857+
python servers/direct_execution.py
830858
# or
831-
uv run mcp run server.py
859+
uv run mcp run servers/direct_execution.py
832860
```
833861

834862
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():
12771305

12781306
When building MCP clients, the SDK provides utilities to help display human-readable names for tools, resources, and prompts:
12791307

1308+
<!-- snippet-source examples/snippets/clients/display_utilities.py -->
12801309
```python
1310+
"""Client display utilities example.
1311+
1312+
This example shows how to use the SDK's display utilities to show
1313+
human-readable names for tools, resources, and prompts.
1314+
1315+
cd to the `examples/snippets` directory and run:
1316+
uv run display-utilities-client
1317+
"""
1318+
1319+
import asyncio
1320+
import os
1321+
1322+
from mcp import ClientSession, StdioServerParameters
1323+
from mcp.client.stdio import stdio_client
12811324
from mcp.shared.metadata_utils import get_display_name
1282-
from mcp.client.session import ClientSession
1325+
1326+
# Create server parameters for stdio connection
1327+
server_params = StdioServerParameters(
1328+
command="uv", # Using uv to run the server
1329+
args=["run", "server", "fastmcp_quickstart", "stdio"],
1330+
env={"UV_INDEX": os.environ.get("UV_INDEX", "")},
1331+
)
12831332

12841333

12851334
async def display_tools(session: ClientSession):
@@ -1301,8 +1350,39 @@ async def display_resources(session: ClientSession):
13011350
for resource in resources_response.resources:
13021351
display_name = get_display_name(resource)
13031352
print(f"Resource: {display_name} ({resource.uri})")
1353+
1354+
templates_response = await session.list_resource_templates()
1355+
for template in templates_response.resourceTemplates:
1356+
display_name = get_display_name(template)
1357+
print(f"Resource Template: {display_name}")
1358+
1359+
1360+
async def run():
1361+
"""Run the display utilities example."""
1362+
async with stdio_client(server_params) as (read, write):
1363+
async with ClientSession(read, write) as session:
1364+
# Initialize the connection
1365+
await session.initialize()
1366+
1367+
print("=== Available Tools ===")
1368+
await display_tools(session)
1369+
1370+
print("\n=== Available Resources ===")
1371+
await display_resources(session)
1372+
1373+
1374+
def main():
1375+
"""Entry point for the display utilities client."""
1376+
asyncio.run(run())
1377+
1378+
1379+
if __name__ == "__main__":
1380+
main()
13041381
```
13051382

1383+
_Full example: [examples/snippets/clients/display_utilities.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/clients/display_utilities.py)_
1384+
<!-- /snippet-source -->
1385+
13061386
The `get_display_name()` function implements the proper precedence rules for displaying names:
13071387

13081388
- For tools: `title` > `annotations.title` > `name`
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"""Client display utilities example.
2+
3+
This example shows how to use the SDK's display utilities to show
4+
human-readable names for tools, resources, and prompts.
5+
6+
cd to the `examples/snippets` directory and run:
7+
uv run display-utilities-client
8+
"""
9+
10+
import asyncio
11+
import os
12+
13+
from mcp import ClientSession, StdioServerParameters
14+
from mcp.client.stdio import stdio_client
15+
from mcp.shared.metadata_utils import get_display_name
16+
17+
# Create server parameters for stdio connection
18+
server_params = StdioServerParameters(
19+
command="uv", # Using uv to run the server
20+
args=["run", "server", "fastmcp_quickstart", "stdio"],
21+
env={"UV_INDEX": os.environ.get("UV_INDEX", "")},
22+
)
23+
24+
25+
async def display_tools(session: ClientSession):
26+
"""Display available tools with human-readable names"""
27+
tools_response = await session.list_tools()
28+
29+
for tool in tools_response.tools:
30+
# get_display_name() returns the title if available, otherwise the name
31+
display_name = get_display_name(tool)
32+
print(f"Tool: {display_name}")
33+
if tool.description:
34+
print(f" {tool.description}")
35+
36+
37+
async def display_resources(session: ClientSession):
38+
"""Display available resources with human-readable names"""
39+
resources_response = await session.list_resources()
40+
41+
for resource in resources_response.resources:
42+
display_name = get_display_name(resource)
43+
print(f"Resource: {display_name} ({resource.uri})")
44+
45+
templates_response = await session.list_resource_templates()
46+
for template in templates_response.resourceTemplates:
47+
display_name = get_display_name(template)
48+
print(f"Resource Template: {display_name}")
49+
50+
51+
async def run():
52+
"""Run the display utilities example."""
53+
async with stdio_client(server_params) as (read, write):
54+
async with ClientSession(read, write) as session:
55+
# Initialize the connection
56+
await session.initialize()
57+
58+
print("=== Available Tools ===")
59+
await display_tools(session)
60+
61+
print("\n=== Available Resources ===")
62+
await display_resources(session)
63+
64+
65+
def main():
66+
"""Entry point for the display utilities client."""
67+
asyncio.run(run())
68+
69+
70+
if __name__ == "__main__":
71+
main()

examples/snippets/pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ packages = ["servers", "clients"]
1818
server = "servers:run_server"
1919
client = "clients.stdio_client:main"
2020
completion-client = "clients.completion_client:main"
21+
direct-execution-server = "servers.direct_execution:main"
22+
display-utilities-client = "clients.display_utilities:main"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""Example showing direct execution of an MCP server.
2+
3+
This is the simplest way to run an MCP server directly.
4+
cd to the `examples/snippets` directory and run:
5+
uv run direct-execution-server
6+
or
7+
python servers/direct_execution.py
8+
"""
9+
10+
from mcp.server.fastmcp import FastMCP
11+
12+
mcp = FastMCP("My App")
13+
14+
15+
@mcp.tool()
16+
def hello(name: str = "World") -> str:
17+
"""Say hello to someone."""
18+
return f"Hello, {name}!"
19+
20+
21+
def main():
22+
"""Entry point for the direct execution server."""
23+
mcp.run()
24+
25+
26+
if __name__ == "__main__":
27+
main()

examples/snippets/servers/structured_output.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Example showing structured output with tools."""
22

3+
from typing import TypedDict
4+
35
from pydantic import BaseModel, Field
46

57
from mcp.server.fastmcp import FastMCP
@@ -27,3 +29,69 @@ def get_weather(city: str) -> WeatherData:
2729
condition="sunny",
2830
wind_speed=5.2,
2931
)
32+
33+
34+
# Using TypedDict for simpler structures
35+
class LocationInfo(TypedDict):
36+
latitude: float
37+
longitude: float
38+
name: str
39+
40+
41+
@mcp.tool()
42+
def get_location(address: str) -> LocationInfo:
43+
"""Get location coordinates"""
44+
return LocationInfo(latitude=51.5074, longitude=-0.1278, name="London, UK")
45+
46+
47+
# Using dict[str, Any] for flexible schemas
48+
@mcp.tool()
49+
def get_statistics(data_type: str) -> dict[str, float]:
50+
"""Get various statistics"""
51+
return {"mean": 42.5, "median": 40.0, "std_dev": 5.2}
52+
53+
54+
# Ordinary classes with type hints work for structured output
55+
class UserProfile:
56+
name: str
57+
age: int
58+
email: str | None = None
59+
60+
def __init__(self, name: str, age: int, email: str | None = None):
61+
self.name = name
62+
self.age = age
63+
self.email = email
64+
65+
66+
@mcp.tool()
67+
def get_user(user_id: str) -> UserProfile:
68+
"""Get user profile - returns structured data"""
69+
return UserProfile(name="Alice", age=30, email="alice@example.com")
70+
71+
72+
# Classes WITHOUT type hints cannot be used for structured output
73+
class UntypedConfig:
74+
def __init__(self, setting1, setting2):
75+
self.setting1 = setting1
76+
self.setting2 = setting2
77+
78+
79+
@mcp.tool()
80+
def get_config() -> UntypedConfig:
81+
"""This returns unstructured output - no schema generated"""
82+
return UntypedConfig("value1", "value2")
83+
84+
85+
# Lists and other types are wrapped automatically
86+
@mcp.tool()
87+
def list_cities() -> list[str]:
88+
"""Get a list of cities"""
89+
return ["London", "Paris", "Tokyo"]
90+
# Returns: {"result": ["London", "Paris", "Tokyo"]}
91+
92+
93+
@mcp.tool()
94+
def get_temperature(city: str) -> float:
95+
"""Get temperature as a simple float"""
96+
return 22.5
97+
# Returns: {"result": 22.5}

0 commit comments

Comments
 (0)