Skip to content

Commit 73676a5

Browse files
committed
use small servers
1 parent 61ec764 commit 73676a5

File tree

13 files changed

+987
-893
lines changed

13 files changed

+987
-893
lines changed

README.md

Lines changed: 197 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
- [Context](#context)
3434
- [Completions](#completions)
3535
- [Elicitation](#elicitation)
36+
- [Sampling](#sampling)
37+
- [Logging and Notifications](#logging-and-notifications)
3638
- [Authentication](#authentication)
3739
- [Running Your Server](#running-your-server)
3840
- [Development Mode](#development-mode)
@@ -207,48 +209,57 @@ def query_db() -> str:
207209

208210
Resources are how you expose data to LLMs. They're similar to GET endpoints in a REST API - they provide data but shouldn't perform significant computation or have side effects:
209211

212+
<!-- snippet-source examples/snippets/servers/basic_resource.py -->
210213
```python
211214
from mcp.server.fastmcp import FastMCP
212215

213-
mcp = FastMCP("My App")
216+
mcp = FastMCP(name="Resource Example")
214217

215218

216-
@mcp.resource("config://app", title="Application Configuration")
217-
def get_config() -> str:
218-
"""Static configuration data"""
219-
return "App configuration here"
219+
@mcp.resource("file://documents/{name}")
220+
def read_document(name: str) -> str:
221+
"""Read a document by name."""
222+
# This would normally read from disk
223+
return f"Content of {name}"
220224

221225

222-
@mcp.resource("users://{user_id}/profile", title="User Profile")
223-
def get_user_profile(user_id: str) -> str:
224-
"""Dynamic user data"""
225-
return f"Profile data for user {user_id}"
226+
@mcp.resource("config://settings")
227+
def get_settings() -> str:
228+
"""Get application settings."""
229+
return """{
230+
"theme": "dark",
231+
"language": "en",
232+
"debug": false
233+
}"""
226234
```
235+
_Full example: [examples/snippets/servers/basic_resource.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_resource.py)_
236+
<!-- /snippet-source -->
227237

228238
### Tools
229239

230240
Tools let LLMs take actions through your server. Unlike resources, tools are expected to perform computation and have side effects:
231241

242+
<!-- snippet-source examples/snippets/servers/basic_tool.py -->
232243
```python
233-
import httpx
234244
from mcp.server.fastmcp import FastMCP
235245

236-
mcp = FastMCP("My App")
246+
mcp = FastMCP(name="Tool Example")
237247

238248

239-
@mcp.tool(title="BMI Calculator")
240-
def calculate_bmi(weight_kg: float, height_m: float) -> float:
241-
"""Calculate BMI given weight in kg and height in meters"""
242-
return weight_kg / (height_m**2)
249+
@mcp.tool(description="Add two numbers")
250+
def add(a: int, b: int) -> int:
251+
"""Add two numbers together."""
252+
return a + b
243253

244254

245-
@mcp.tool(title="Weather Fetcher")
246-
async def fetch_weather(city: str) -> str:
247-
"""Fetch current weather for a city"""
248-
async with httpx.AsyncClient() as client:
249-
response = await client.get(f"https://api.weather.com/{city}")
250-
return response.text
255+
@mcp.tool(description="Get weather for a city")
256+
def get_weather(city: str, unit: str = "celsius") -> str:
257+
"""Get weather for a city."""
258+
# This would normally call a weather API
259+
return f"Weather in {city}: 22°{unit[0].upper()}"
251260
```
261+
_Full example: [examples/snippets/servers/basic_tool.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_tool.py)_
262+
<!-- /snippet-source -->
252263

253264
#### Structured Output
254265

@@ -375,26 +386,26 @@ def get_temperature(city: str) -> float:
375386

376387
Prompts are reusable templates that help LLMs interact with your server effectively:
377388

389+
<!-- snippet-source examples/snippets/servers/basic_prompt.py -->
378390
```python
379391
from mcp.server.fastmcp import FastMCP
380-
from mcp.server.fastmcp.prompts import base
381392

382-
mcp = FastMCP("My App")
393+
mcp = FastMCP(name="Prompt Example")
383394

384395

385-
@mcp.prompt(title="Code Review")
386-
def review_code(code: str) -> str:
387-
return f"Please review this code:\n\n{code}"
396+
@mcp.prompt(description="Generate a summary")
397+
def summarize(text: str, max_words: int = 100) -> str:
398+
"""Create a summarization prompt."""
399+
return f"Summarize this text in {max_words} words:\n\n{text}"
388400

389401

390-
@mcp.prompt(title="Debug Assistant")
391-
def debug_error(error: str) -> list[base.Message]:
392-
return [
393-
base.UserMessage("I'm seeing this error:"),
394-
base.UserMessage(error),
395-
base.AssistantMessage("I'll help debug that. What have you tried so far?"),
396-
]
402+
@mcp.prompt(description="Explain a concept")
403+
def explain(concept: str, audience: str = "general") -> str:
404+
"""Create an explanation prompt."""
405+
return f"Explain {concept} for a {audience} audience"
397406
```
407+
_Full example: [examples/snippets/servers/basic_prompt.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/basic_prompt.py)_
408+
<!-- /snippet-source -->
398409

399410
### Images
400411

@@ -419,28 +430,30 @@ def create_thumbnail(image_path: str) -> Image:
419430

420431
The Context object gives your tools and resources access to MCP capabilities:
421432

422-
<!-- snippet-source examples/servers/everything/src/everything/server.py#L37-L54 -->
433+
<!-- snippet-source examples/snippets/servers/tool_progress.py -->
423434
```python
424-
# Tool with context for logging and progress
425-
@mcp.tool(description="A tool that demonstrates logging and progress", title="Progress Tool")
426-
async def tool_with_progress(message: str, ctx: Context, steps: int = 3) -> str:
427-
await ctx.info(f"Starting processing of '{message}' with {steps} steps")
428-
429-
# Send progress notifications
430-
for i in range(steps):
431-
progress_value = (i + 1) / steps
432-
await ctx.report_progress(
433-
progress=progress_value,
434-
total=1.0,
435-
message=f"Processing step {i + 1} of {steps}",
436-
)
437-
await ctx.debug(f"Completed step {i + 1}")
435+
from mcp.server.fastmcp import Context, FastMCP
436+
437+
mcp = FastMCP(name="Progress Example")
438438

439-
return f"Processed '{message}' in {steps} steps"
440439

441-
# Simple tool for basic functionality
440+
@mcp.tool(description="Demonstrates progress reporting")
441+
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
442+
"""Execute a task with progress updates."""
443+
await ctx.info(f"Starting: {task_name}")
444+
445+
for i in range(steps):
446+
progress = (i + 1) / steps
447+
await ctx.report_progress(
448+
progress=progress,
449+
total=1.0,
450+
message=f"Step {i + 1}/{steps}",
451+
)
452+
await ctx.debug(f"Completed step {i + 1}")
453+
454+
return f"Task '{task_name}' completed"
442455
```
443-
_Full example: [examples/servers/everything/src/everything/server.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/servers/everything/src/everything/server.py#L37-L54)_
456+
_Full example: [examples/snippets/servers/tool_progress.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/tool_progress.py)_
444457
<!-- /snippet-source -->
445458

446459
### Completions
@@ -473,8 +486,10 @@ async def use_completion(session: ClientSession):
473486
```
474487

475488
Server implementation:
489+
490+
<!-- snippet-source examples/snippets/servers/completion.py -->
476491
```python
477-
from mcp.server import Server
492+
from mcp.server.fastmcp import FastMCP
478493
from mcp.types import (
479494
Completion,
480495
CompletionArgument,
@@ -483,72 +498,167 @@ from mcp.types import (
483498
ResourceTemplateReference,
484499
)
485500

486-
server = Server("example-server")
501+
mcp = FastMCP(name="Example")
502+
487503

504+
@mcp.resource("github://repos/{owner}/{repo}")
505+
def github_repo(owner: str, repo: str) -> str:
506+
"""GitHub repository resource."""
507+
return f"Repository: {owner}/{repo}"
488508

489-
@server.completion()
509+
510+
@mcp.prompt(description="Code review prompt")
511+
def review_code(language: str, code: str) -> str:
512+
"""Generate a code review."""
513+
return f"Review this {language} code:\n{code}"
514+
515+
516+
@mcp.completion()
490517
async def handle_completion(
491518
ref: PromptReference | ResourceTemplateReference,
492519
argument: CompletionArgument,
493520
context: CompletionContext | None,
494521
) -> Completion | None:
522+
"""Provide completions for prompts and resources."""
523+
524+
# Complete programming languages for the prompt
525+
if isinstance(ref, PromptReference):
526+
if ref.name == "review_code" and argument.name == "language":
527+
languages = ["python", "javascript", "typescript", "go", "rust"]
528+
return Completion(
529+
values=[lang for lang in languages if lang.startswith(argument.value)],
530+
hasMore=False,
531+
)
532+
533+
# Complete repository names for GitHub resources
495534
if isinstance(ref, ResourceTemplateReference):
496535
if ref.uri == "github://repos/{owner}/{repo}" and argument.name == "repo":
497-
# Use context to provide owner-specific repos
498-
if context and context.arguments:
499-
owner = context.arguments.get("owner")
500-
if owner == "modelcontextprotocol":
501-
repos = ["python-sdk", "typescript-sdk", "specification"]
502-
# Filter based on partial input
503-
filtered = [r for r in repos if r.startswith(argument.value)]
504-
return Completion(values=filtered)
536+
if context and context.arguments and context.arguments.get("owner") == "modelcontextprotocol":
537+
repos = ["python-sdk", "typescript-sdk", "specification"]
538+
return Completion(values=repos, hasMore=False)
539+
505540
return None
506541
```
542+
_Full example: [examples/snippets/servers/completion.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/completion.py)_
543+
<!-- /snippet-source -->
507544
### Elicitation
508545

509546
Request additional information from users during tool execution:
510547

548+
<!-- snippet-source examples/snippets/servers/elicitation.py -->
511549
```python
512-
from mcp.server.fastmcp import FastMCP, Context
513-
from mcp.server.elicitation import (
514-
AcceptedElicitation,
515-
DeclinedElicitation,
516-
CancelledElicitation,
517-
)
518550
from pydantic import BaseModel, Field
519551

520-
mcp = FastMCP("Booking System")
552+
from mcp.server.fastmcp import Context, FastMCP
521553

554+
mcp = FastMCP(name="Elicitation Example")
522555

523-
@mcp.tool()
524-
async def book_table(date: str, party_size: int, ctx: Context) -> str:
525-
"""Book a table with confirmation"""
526556

527-
# Schema must only contain primitive types (str, int, float, bool)
528-
class ConfirmBooking(BaseModel):
529-
confirm: bool = Field(description="Confirm booking?")
530-
notes: str = Field(default="", description="Special requests")
557+
class BookingPreferences(BaseModel):
558+
"""Schema for collecting user preferences."""
531559

532-
result = await ctx.elicit(
533-
message=f"Confirm booking for {party_size} on {date}?", schema=ConfirmBooking
560+
checkAlternative: bool = Field(description="Would you like to check another date?")
561+
alternativeDate: str = Field(
562+
default="2024-12-26",
563+
description="Alternative date (YYYY-MM-DD)",
534564
)
535565

536-
match result:
537-
case AcceptedElicitation(data=data):
538-
if data.confirm:
539-
return f"Booked! Notes: {data.notes or 'None'}"
540-
return "Booking cancelled"
541-
case DeclinedElicitation():
542-
return "Booking declined"
543-
case CancelledElicitation():
544-
return "Booking cancelled"
566+
567+
@mcp.tool(description="Book a restaurant table")
568+
async def book_table(
569+
date: str,
570+
time: str,
571+
party_size: int,
572+
ctx: Context,
573+
) -> str:
574+
"""Book a table with date availability check."""
575+
# Check if date is available
576+
if date == "2024-12-25":
577+
# Date unavailable - ask user for alternative
578+
result = await ctx.elicit(
579+
message=(f"No tables available for {party_size} on {date}. " "Would you like to try another date?"),
580+
schema=BookingPreferences,
581+
)
582+
583+
if result.action == "accept" and result.data:
584+
if result.data.checkAlternative:
585+
return f"✅ Booked for {result.data.alternativeDate}"
586+
return "❌ No booking made"
587+
return "❌ Booking cancelled"
588+
589+
# Date available
590+
return f"✅ Booked for {date} at {time}"
545591
```
592+
_Full example: [examples/snippets/servers/elicitation.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/elicitation.py)_
593+
<!-- /snippet-source -->
546594

547595
The `elicit()` method returns an `ElicitationResult` with:
548596
- `action`: "accept", "decline", or "cancel"
549597
- `data`: The validated response (only when accepted)
550598
- `validation_error`: Any validation error message
551599

600+
### Sampling
601+
602+
Tools can interact with LLMs through sampling (generating text):
603+
604+
<!-- snippet-source examples/snippets/servers/sampling.py -->
605+
```python
606+
from mcp.server.fastmcp import Context, FastMCP
607+
from mcp.types import SamplingMessage, TextContent
608+
609+
mcp = FastMCP(name="Sampling Example")
610+
611+
612+
@mcp.tool(description="Uses sampling to generate content")
613+
async def generate_poem(topic: str, ctx: Context) -> str:
614+
"""Generate a poem using LLM sampling."""
615+
prompt = f"Write a short poem about {topic}"
616+
617+
result = await ctx.session.create_message(
618+
messages=[
619+
SamplingMessage(
620+
role="user",
621+
content=TextContent(type="text", text=prompt),
622+
)
623+
],
624+
max_tokens=100,
625+
)
626+
627+
if result.content.type == "text":
628+
return result.content.text
629+
return str(result.content)
630+
```
631+
_Full example: [examples/snippets/servers/sampling.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/sampling.py)_
632+
<!-- /snippet-source -->
633+
634+
### Logging and Notifications
635+
636+
Tools can send logs and notifications through the context:
637+
638+
<!-- snippet-source examples/snippets/servers/notifications.py -->
639+
```python
640+
from mcp.server.fastmcp import Context, FastMCP
641+
642+
mcp = FastMCP(name="Notifications Example")
643+
644+
645+
@mcp.tool(description="Demonstrates logging at different levels")
646+
async def process_data(data: str, ctx: Context) -> str:
647+
"""Process data with comprehensive logging."""
648+
# Different log levels
649+
await ctx.debug(f"Debug: Processing '{data}'")
650+
await ctx.info("Info: Starting processing")
651+
await ctx.warning("Warning: This is experimental")
652+
await ctx.error("Error: (This is just a demo)")
653+
654+
# Notify about resource changes
655+
await ctx.session.send_resource_list_changed()
656+
657+
return f"Processed: {data}"
658+
```
659+
_Full example: [examples/snippets/servers/notifications.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/notifications.py)_
660+
<!-- /snippet-source -->
661+
552662
### Authentication
553663

554664
Authentication can be used by servers that want to expose tools accessing protected resources.

0 commit comments

Comments
 (0)