chore: import upstream snapshot with attribution
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 12:10:27 +08:00
commit 49b9bb6724
992 changed files with 161690 additions and 0 deletions
+37
View File
@@ -0,0 +1,37 @@
"""MCP Snippets.
This package contains simple examples of MCP server features.
Each server demonstrates a single feature and can be run as a standalone server.
To run a server, use the command:
uv run server basic_tool sse
"""
import importlib
import sys
from typing import Literal, cast
def run_server():
"""Run a server by name with optional transport.
Usage: server <server-name> [transport]
Example: server basic_tool sse
"""
if len(sys.argv) < 2:
print("Usage: server <server-name> [transport]")
print("Available servers: basic_tool, basic_resource, basic_prompt, tool_progress,")
print(" sampling, elicitation, completion, notifications,")
print(" mcpserver_quickstart, structured_output, images")
print("Available transports: stdio (default), sse, streamable-http")
sys.exit(1)
server_name = sys.argv[1]
transport = sys.argv[2] if len(sys.argv) > 2 else "stdio"
try:
module = importlib.import_module(f".{server_name}", package=__name__)
module.mcp.run(cast(Literal["stdio", "sse", "streamable-http"], transport))
except ImportError:
print(f"Error: Server '{server_name}' not found")
sys.exit(1)
+18
View File
@@ -0,0 +1,18 @@
from mcp.server.mcpserver import MCPServer
from mcp.server.mcpserver.prompts import base
mcp = MCPServer(name="Prompt Example")
@mcp.prompt(title="Code Review")
def review_code(code: str) -> str:
return f"Please review this code:\n\n{code}"
@mcp.prompt(title="Debug Assistant")
def debug_error(error: str) -> list[base.Message]:
return [
base.UserMessage("I'm seeing this error:"),
base.UserMessage(error),
base.AssistantMessage("I'll help debug that. What have you tried so far?"),
]
@@ -0,0 +1,20 @@
from mcp.server.mcpserver import MCPServer
mcp = MCPServer(name="Resource Example")
@mcp.resource("file://documents/{name}")
def read_document(name: str) -> str:
"""Read a document by name."""
# This would normally read from disk
return f"Content of {name}"
@mcp.resource("config://settings")
def get_settings() -> str:
"""Get application settings."""
return """{
"theme": "dark",
"language": "en",
"debug": false
}"""
+16
View File
@@ -0,0 +1,16 @@
from mcp.server.mcpserver import MCPServer
mcp = MCPServer(name="Tool Example")
@mcp.tool()
def sum(a: int, b: int) -> int:
"""Add two numbers together."""
return a + b
@mcp.tool()
def get_weather(city: str, unit: str = "celsius") -> str:
"""Get weather for a city."""
# This would normally call a weather API
return f"Weather in {city}: 22degrees{unit[0].upper()}"
+50
View File
@@ -0,0 +1,50 @@
from mcp_types import (
Completion,
CompletionArgument,
CompletionContext,
PromptReference,
ResourceTemplateReference,
)
from mcp.server.mcpserver import MCPServer
mcp = MCPServer(name="Example")
@mcp.resource("github://repos/{owner}/{repo}")
def github_repo(owner: str, repo: str) -> str:
"""GitHub repository resource."""
return f"Repository: {owner}/{repo}"
@mcp.prompt(description="Code review prompt")
def review_code(language: str, code: str) -> str:
"""Generate a code review."""
return f"Review this {language} code:\n{code}"
@mcp.completion()
async def handle_completion(
ref: PromptReference | ResourceTemplateReference,
argument: CompletionArgument,
context: CompletionContext | None,
) -> Completion | None:
"""Provide completions for prompts and resources."""
# Complete programming languages for the prompt
if isinstance(ref, PromptReference):
if ref.name == "review_code" and argument.name == "language":
languages = ["python", "javascript", "typescript", "go", "rust"]
return Completion(
values=[lang for lang in languages if lang.startswith(argument.value)],
has_more=False,
)
# Complete repository names for GitHub resources
if isinstance(ref, ResourceTemplateReference):
if ref.uri == "github://repos/{owner}/{repo}" and argument.name == "repo":
if context and context.arguments and context.arguments.get("owner") == "modelcontextprotocol":
repos = ["python-sdk", "typescript-sdk", "specification"]
return Completion(values=repos, has_more=False)
return None
@@ -0,0 +1,42 @@
"""Example showing direct CallToolResult return for advanced control."""
from typing import Annotated
from mcp_types import CallToolResult, TextContent
from pydantic import BaseModel
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("CallToolResult Example")
class ValidationModel(BaseModel):
"""Model for validating structured output."""
status: str
data: dict[str, int]
@mcp.tool()
def advanced_tool() -> CallToolResult:
"""Return CallToolResult directly for full control including _meta field."""
return CallToolResult(
content=[TextContent(type="text", text="Response visible to the model")],
_meta={"hidden": "data for client applications only"},
)
@mcp.tool()
def validated_tool() -> Annotated[CallToolResult, ValidationModel]:
"""Return CallToolResult with structured output validation."""
return CallToolResult(
content=[TextContent(type="text", text="Validated response")],
structured_content={"status": "success", "data": {"result": 42}},
_meta={"internal": "metadata"},
)
@mcp.tool()
def empty_result_tool() -> CallToolResult:
"""For empty results, return CallToolResult with empty content."""
return CallToolResult(content=[])
@@ -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.mcpserver import MCPServer
mcp = MCPServer("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()
+98
View File
@@ -0,0 +1,98 @@
"""Elicitation examples demonstrating form and URL mode elicitation.
Form mode elicitation collects structured, non-sensitive data through a schema.
URL mode elicitation directs users to external URLs for sensitive operations
like OAuth flows, credential collection, or payment processing.
"""
import uuid
from mcp_types import ElicitRequestURLParams
from pydantic import BaseModel, Field
from mcp.server.mcpserver import Context, MCPServer
from mcp.shared.exceptions import UrlElicitationRequiredError
mcp = MCPServer(name="Elicitation Example")
class BookingPreferences(BaseModel):
"""Schema for collecting user preferences."""
checkAlternative: bool = Field(description="Would you like to check another date?")
alternativeDate: str = Field(
default="2024-12-26",
description="Alternative date (YYYY-MM-DD)",
)
@mcp.tool()
async def book_table(date: str, time: str, party_size: int, ctx: Context) -> str:
"""Book a table with date availability check.
This demonstrates form mode elicitation for collecting non-sensitive user input.
"""
# Check if date is available
if date == "2024-12-25":
# Date unavailable - ask user for alternative
result = await ctx.elicit(
message=(f"No tables available for {party_size} on {date}. Would you like to try another date?"),
schema=BookingPreferences,
)
if result.action == "accept" and result.data:
if result.data.checkAlternative:
return f"[SUCCESS] Booked for {result.data.alternativeDate}"
return "[CANCELLED] No booking made"
return "[CANCELLED] Booking cancelled"
# Date available
return f"[SUCCESS] Booked for {date} at {time}"
@mcp.tool()
async def secure_payment(amount: float, ctx: Context) -> str:
"""Process a secure payment requiring URL confirmation.
This demonstrates URL mode elicitation using ctx.elicit_url() for
operations that require out-of-band user interaction.
"""
elicitation_id = str(uuid.uuid4())
result = await ctx.elicit_url(
message=f"Please confirm payment of ${amount:.2f}",
url=f"https://payments.example.com/confirm?amount={amount}&id={elicitation_id}",
elicitation_id=elicitation_id,
)
if result.action == "accept":
# In a real app, the payment confirmation would happen out-of-band
# and you'd verify the payment status from your backend
return f"Payment of ${amount:.2f} initiated - check your browser to complete"
elif result.action == "decline":
return "Payment declined by user"
return "Payment cancelled"
@mcp.tool()
async def connect_service(service_name: str, ctx: Context) -> str:
"""Connect to a third-party service requiring OAuth authorization.
This demonstrates the "throw error" pattern using UrlElicitationRequiredError.
Use this pattern when the tool cannot proceed without user authorization.
"""
elicitation_id = str(uuid.uuid4())
# Raise UrlElicitationRequiredError to signal that the client must complete
# a URL elicitation before this request can be processed.
# The MCP framework will convert this to a -32042 error response.
raise UrlElicitationRequiredError(
[
ElicitRequestURLParams(
mode="url",
message=f"Authorization required to connect to {service_name}",
url=f"https://{service_name}.example.com/oauth/authorize?elicit={elicitation_id}",
elicitation_id=elicitation_id,
)
]
)
@@ -0,0 +1,103 @@
"""Authorization-server side of SEP-990 (enterprise IdP policy controls).
An authorization server enables the Identity Assertion Authorization Grant by setting
`identity_assertion_enabled=True` and implementing `exchange_identity_assertion` on its provider.
The client presents the IdP-issued ID-JAG using the RFC 7523 jwt-bearer grant; the provider
validates the assertion and mints an MCP access token.
Validating the ID-JAG is the provider's responsibility and is only stubbed here. A real
implementation MUST, per RFC 7523 §3 and SEP-990 §5.1:
- verify the JWT signature, `iss`, and `exp`, and that `typ` is `oauth-id-jag+jwt`;
- require `aud` to identify this authorization server;
- require the ID-JAG's `client_id` claim to match the authenticated client;
- audience-restrict the issued token to the resource named in the ID-JAG's `resource` claim
(NOT the client-supplied `params.resource`);
- derive the granted scopes from the ID-JAG and policy.
`_decode_and_validate_id_jag` below raises `NotImplementedError` so this snippet fails closed and
forces a real implementation. Wire the returned routes into a Starlette app with
`create_auth_routes(..., identity_assertion_enabled=True)`, or set
`AuthSettings(identity_assertion_enabled=True)` with `MCPServer`/`Server`.
"""
import secrets
import time
from dataclasses import dataclass
from mcp.server.auth.provider import (
AccessToken,
AuthorizationCode,
IdentityAssertionParams,
OAuthAuthorizationServerProvider,
RefreshToken,
)
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
@dataclass
class IdJagClaims:
"""The trusted claims extracted from a validated ID-JAG."""
subject: str # the end user the ID-JAG was issued for
client_id: str # the ID-JAG `client_id` claim; §5.1 requires it to match the authenticated client
resource: str # the MCP server the issued token must be audience-restricted to
scopes: list[str]
class IdentityAssertionProvider(OAuthAuthorizationServerProvider[AuthorizationCode, RefreshToken, AccessToken]):
"""Authorization-server provider that accepts an ID-JAG via the RFC 7523 jwt-bearer grant."""
def __init__(self) -> None:
self.access_tokens: dict[str, AccessToken] = {}
# SEP-990 clients are pre-registered out of band (DCR refuses the grant) and must be
# confidential. `get_client` must return them, or the token endpoint 401s before the
# exchange runs. Real deployments load these from their own store.
self.clients: dict[str, OAuthClientInformationFull] = {
"enterprise-mcp-client": OAuthClientInformationFull(
client_id="enterprise-mcp-client",
client_secret="enterprise-mcp-secret",
redirect_uris=None,
grant_types=["urn:ietf:params:oauth:grant-type:jwt-bearer"],
token_endpoint_auth_method="client_secret_post",
)
}
async def get_client(self, client_id: str) -> OAuthClientInformationFull | None:
return self.clients.get(client_id)
async def exchange_identity_assertion(
self, client: OAuthClientInformationFull, params: IdentityAssertionParams
) -> OAuthToken:
claims = self._decode_and_validate_id_jag(params.assertion, client)
access_token = f"access_{secrets.token_hex(16)}"
self.access_tokens[access_token] = AccessToken(
token=access_token,
client_id=claims.client_id,
scopes=claims.scopes,
expires_at=int(time.time()) + 3600,
# Bind to the resource from the validated ID-JAG, not the client-controlled request.
resource=claims.resource,
subject=claims.subject,
)
# No refresh token: SEP-990 relies on the IdP re-issuing ID-JAGs to control session lifetime.
return OAuthToken(
access_token=access_token,
token_type="Bearer",
expires_in=3600,
scope=" ".join(claims.scopes),
)
def _decode_and_validate_id_jag(self, assertion: str, client: OAuthClientInformationFull) -> IdJagClaims:
"""Verify the ID-JAG and return its trusted claims, or reject the request.
Replace this stub with real RFC 7523 §3 / SEP-990 §5.1 validation. It fails closed - it
raises rather than trusting the assertion - so a copy of this example cannot accidentally
accept unverified tokens. RFC 7523 §3.1 / RFC 6749 §5.2 specify `invalid_grant` for a
rejected assertion.
"""
raise NotImplementedError("Validate the ID-JAG (signature, iss/aud/exp/typ, client_id, resource)")
async def load_access_token(self, token: str) -> AccessToken | None:
return self.access_tokens.get(token)
+15
View File
@@ -0,0 +1,15 @@
"""Example showing image handling with MCPServer."""
from PIL import Image as PILImage
from mcp.server.mcpserver import Image, MCPServer
mcp = MCPServer("Image Example")
@mcp.tool()
def create_thumbnail(image_path: str) -> Image:
"""Create a thumbnail from an image"""
img = PILImage.open(image_path)
img.thumbnail((100, 100))
return Image(data=img.tobytes(), format="png")
@@ -0,0 +1,56 @@
"""Example showing lifespan support for startup/shutdown with strong typing."""
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from dataclasses import dataclass
from mcp.server.mcpserver import Context, MCPServer
# Mock database class for example
class Database:
"""Mock database class for example."""
@classmethod
async def connect(cls) -> "Database":
"""Connect to database."""
return cls()
async def disconnect(self) -> None:
"""Disconnect from database."""
pass
def query(self) -> str:
"""Execute a query."""
return "Query result"
@dataclass
class AppContext:
"""Application context with typed dependencies."""
db: Database
@asynccontextmanager
async def app_lifespan(server: MCPServer) -> AsyncIterator[AppContext]:
"""Manage application lifecycle with type-safe context."""
# Initialize on startup
db = await Database.connect()
try:
yield AppContext(db=db)
finally:
# Cleanup on shutdown
await db.disconnect()
# Pass lifespan to server
mcp = MCPServer("My App", lifespan=app_lifespan)
# Access type-safe lifespan context in tools
@mcp.tool()
def query_db(ctx: Context[AppContext]) -> str:
"""Tool that uses initialized resources."""
db = ctx.request_context.lifespan_context.db
return db.query()
@@ -0,0 +1 @@
"""Low-level server examples for MCP Python SDK."""
@@ -0,0 +1,64 @@
"""Run from the repository root:
uv run examples/snippets/servers/lowlevel/basic.py
"""
import asyncio
import mcp_types as types
import mcp.server.stdio
from mcp.server import Server, ServerRequestContext
async def handle_list_prompts(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListPromptsResult:
"""List available prompts."""
return types.ListPromptsResult(
prompts=[
types.Prompt(
name="example-prompt",
description="An example prompt template",
arguments=[types.PromptArgument(name="arg1", description="Example argument", required=True)],
)
]
)
async def handle_get_prompt(ctx: ServerRequestContext, params: types.GetPromptRequestParams) -> types.GetPromptResult:
"""Get a specific prompt by name."""
if params.name != "example-prompt":
raise ValueError(f"Unknown prompt: {params.name}")
arg1_value = (params.arguments or {}).get("arg1", "default")
return types.GetPromptResult(
description="Example prompt",
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(type="text", text=f"Example prompt text with argument: {arg1_value}"),
)
],
)
server = Server(
"example-server",
on_list_prompts=handle_list_prompts,
on_get_prompt=handle_get_prompt,
)
async def run():
"""Run the basic low-level server."""
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options(),
)
if __name__ == "__main__":
asyncio.run(run())
@@ -0,0 +1,63 @@
"""Run from the repository root:
uv run examples/snippets/servers/lowlevel/direct_call_tool_result.py
"""
import asyncio
import mcp_types as types
import mcp.server.stdio
from mcp.server import Server, ServerRequestContext
async def handle_list_tools(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:
"""List available tools."""
return types.ListToolsResult(
tools=[
types.Tool(
name="advanced_tool",
description="Tool with full control including _meta field",
input_schema={
"type": "object",
"properties": {"message": {"type": "string"}},
"required": ["message"],
},
)
]
)
async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult:
"""Handle tool calls by returning CallToolResult directly."""
if params.name == "advanced_tool":
message = (params.arguments or {}).get("message", "")
return types.CallToolResult(
content=[types.TextContent(type="text", text=f"Processed: {message}")],
structured_content={"result": "success", "message": message},
_meta={"hidden": "data for client applications only"},
)
raise ValueError(f"Unknown tool: {params.name}")
server = Server(
"example-server",
on_list_tools=handle_list_tools,
on_call_tool=handle_call_tool,
)
async def run():
"""Run the server."""
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options(),
)
if __name__ == "__main__":
asyncio.run(run())
@@ -0,0 +1,102 @@
"""Run from the repository root:
uv run examples/snippets/servers/lowlevel/lifespan.py
"""
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from typing import TypedDict
import mcp_types as types
import mcp.server.stdio
from mcp.server import Server, ServerRequestContext
# Mock database class for example
class Database:
"""Mock database class for example."""
@classmethod
async def connect(cls) -> "Database":
"""Connect to database."""
print("Database connected")
return cls()
async def disconnect(self) -> None:
"""Disconnect from database."""
print("Database disconnected")
async def query(self, query_str: str) -> list[dict[str, str]]:
"""Execute a query."""
# Simulate database query
return [{"id": "1", "name": "Example", "query": query_str}]
class AppContext(TypedDict):
db: Database
@asynccontextmanager
async def server_lifespan(_server: Server[AppContext]) -> AsyncIterator[AppContext]:
"""Manage server startup and shutdown lifecycle."""
db = await Database.connect()
try:
yield {"db": db}
finally:
await db.disconnect()
async def handle_list_tools(
ctx: ServerRequestContext[AppContext], params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:
"""List available tools."""
return types.ListToolsResult(
tools=[
types.Tool(
name="query_db",
description="Query the database",
input_schema={
"type": "object",
"properties": {"query": {"type": "string", "description": "SQL query to execute"}},
"required": ["query"],
},
)
]
)
async def handle_call_tool(
ctx: ServerRequestContext[AppContext], params: types.CallToolRequestParams
) -> types.CallToolResult:
"""Handle database query tool call."""
if params.name != "query_db":
raise ValueError(f"Unknown tool: {params.name}")
db = ctx.lifespan_context["db"]
results = await db.query((params.arguments or {})["query"])
return types.CallToolResult(content=[types.TextContent(type="text", text=f"Query results: {results}")])
server = Server(
"example-server",
lifespan=server_lifespan,
on_list_tools=handle_list_tools,
on_call_tool=handle_call_tool,
)
async def run():
"""Run the server with lifespan management."""
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options(),
)
if __name__ == "__main__":
import asyncio
asyncio.run(run())
@@ -0,0 +1,81 @@
"""Run from the repository root:
uv run examples/snippets/servers/lowlevel/structured_output.py
"""
import asyncio
import json
import mcp_types as types
import mcp.server.stdio
from mcp.server import Server, ServerRequestContext
async def handle_list_tools(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListToolsResult:
"""List available tools with structured output schemas."""
return types.ListToolsResult(
tools=[
types.Tool(
name="get_weather",
description="Get current weather for a city",
input_schema={
"type": "object",
"properties": {"city": {"type": "string", "description": "City name"}},
"required": ["city"],
},
output_schema={
"type": "object",
"properties": {
"temperature": {"type": "number", "description": "Temperature in Celsius"},
"condition": {"type": "string", "description": "Weather condition"},
"humidity": {"type": "number", "description": "Humidity percentage"},
"city": {"type": "string", "description": "City name"},
},
"required": ["temperature", "condition", "humidity", "city"],
},
)
]
)
async def handle_call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> types.CallToolResult:
"""Handle tool calls with structured output."""
if params.name == "get_weather":
city = (params.arguments or {})["city"]
weather_data = {
"temperature": 22.5,
"condition": "partly cloudy",
"humidity": 65,
"city": city,
}
return types.CallToolResult(
content=[types.TextContent(type="text", text=json.dumps(weather_data, indent=2))],
structured_content=weather_data,
)
raise ValueError(f"Unknown tool: {params.name}")
server = Server(
"example-server",
on_list_tools=handle_list_tools,
on_call_tool=handle_call_tool,
)
async def run():
"""Run the structured output server."""
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options(),
)
if __name__ == "__main__":
asyncio.run(run())
@@ -0,0 +1,42 @@
"""MCPServer quickstart example.
Run from the repository root:
uv run examples/snippets/servers/mcpserver_quickstart.py
"""
from mcp.server.mcpserver import MCPServer
# Create an MCP server
mcp = MCPServer("Demo")
# Add an addition tool
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers"""
return a + b
# Add a dynamic greeting resource
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
"""Get a personalized greeting"""
return f"Hello, {name}!"
# Add a prompt
@mcp.prompt()
def greet_user(name: str, style: str = "friendly") -> str:
"""Generate a greeting prompt"""
styles = {
"friendly": "Please write a warm, friendly greeting",
"formal": "Please write a formal, professional greeting",
"casual": "Please write a casual, relaxed greeting",
}
return f"{styles.get(style, styles['friendly'])} for someone named {name}."
# Run with streamable HTTP transport
if __name__ == "__main__":
mcp.run(transport="streamable-http", json_response=True)
@@ -0,0 +1,18 @@
from mcp.server.mcpserver import Context, MCPServer
mcp = MCPServer(name="Notifications Example")
@mcp.tool()
async def process_data(data: str, ctx: Context) -> str:
"""Process data with logging."""
# Different log levels
await ctx.debug(f"Debug: Processing '{data}'") # pyright: ignore[reportDeprecated]
await ctx.info("Info: Starting processing") # pyright: ignore[reportDeprecated]
await ctx.warning("Warning: This is experimental") # pyright: ignore[reportDeprecated]
await ctx.error("Error: (This is just a demo)") # pyright: ignore[reportDeprecated]
# Notify about resource changes
await ctx.session.send_resource_list_changed()
return f"Processed: {data}"
+45
View File
@@ -0,0 +1,45 @@
"""Run from the repository root:
uv run examples/snippets/servers/oauth_server.py
"""
from pydantic import AnyHttpUrl
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
from mcp.server.mcpserver import MCPServer
class SimpleTokenVerifier(TokenVerifier):
"""Simple token verifier for demonstration."""
async def verify_token(self, token: str) -> AccessToken | None:
pass # This is where you would implement actual token validation
# Create MCPServer instance as a Resource Server
mcp = MCPServer(
"Weather Service",
# Token verifier for authentication
token_verifier=SimpleTokenVerifier(),
# Auth settings for RFC 9728 Protected Resource Metadata
auth=AuthSettings(
issuer_url=AnyHttpUrl("https://auth.example.com"), # Authorization Server URL
resource_server_url=AnyHttpUrl("http://localhost:3001"), # This server's URL
required_scopes=["user"],
),
)
@mcp.tool()
async def get_weather(city: str = "London") -> dict[str, str]:
"""Get weather data for a city"""
return {
"city": city,
"temperature": "22",
"condition": "Partly cloudy",
"humidity": "65%",
}
if __name__ == "__main__":
mcp.run(transport="streamable-http", json_response=True)
@@ -0,0 +1,36 @@
"""Example of implementing pagination with the low-level MCP server."""
import mcp_types as types
from mcp.server import Server, ServerRequestContext
# Sample data to paginate
ITEMS = [f"Item {i}" for i in range(1, 101)] # 100 items
async def handle_list_resources(
ctx: ServerRequestContext, params: types.PaginatedRequestParams | None
) -> types.ListResourcesResult:
"""List resources with pagination support."""
page_size = 10
# Extract cursor from request params
cursor = params.cursor if params is not None else None
# Parse cursor to get offset
start = 0 if cursor is None else int(cursor)
end = start + page_size
# Get page of resources
page_items = [
types.Resource(uri=f"resource://items/{item}", name=item, description=f"Description for {item}")
for item in ITEMS[start:end]
]
# Determine next cursor
next_cursor = str(end) if end < len(ITEMS) else None
return types.ListResourcesResult(resources=page_items, next_cursor=next_cursor)
server = Server("paginated-server", on_list_resources=handle_list_resources)
+26
View File
@@ -0,0 +1,26 @@
from mcp_types import SamplingMessage, TextContent
from mcp.server.mcpserver import Context, MCPServer
mcp = MCPServer(name="Sampling Example")
@mcp.tool()
async def generate_poem(topic: str, ctx: Context) -> str:
"""Generate a poem using LLM sampling."""
prompt = f"Write a short poem about {topic}"
result = await ctx.session.create_message( # pyright: ignore[reportDeprecated]
messages=[
SamplingMessage(
role="user",
content=TextContent(type="text", text=prompt),
)
],
max_tokens=100,
)
# Since we're not passing tools param, result.content is single content
if result.content.type == "text":
return result.content.text
return str(result.content)
@@ -0,0 +1,28 @@
"""Run from the repository root:
uv run examples/snippets/servers/streamable_config.py
"""
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("StatelessServer")
# Add a simple tool to demonstrate the server
@mcp.tool()
def greet(name: str = "World") -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
# Run server with streamable_http transport
# Transport-specific options (stateless_http, json_response) are passed to run()
if __name__ == "__main__":
# Stateless server with JSON responses (recommended)
mcp.run(transport="streamable-http", stateless_http=True, json_response=True)
# Other configuration options:
# Stateless server with SSE streaming responses
# mcp.run(transport="streamable-http", stateless_http=True)
# Stateful server with session persistence
# mcp.run(transport="streamable-http")
@@ -0,0 +1,38 @@
"""Basic example showing how to mount StreamableHTTP server in Starlette.
Run from the repository root:
uvicorn examples.snippets.servers.streamable_http_basic_mounting:app --reload
"""
import contextlib
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.mcpserver import MCPServer
# Create MCP server
mcp = MCPServer("My App")
@mcp.tool()
def hello() -> str:
"""A simple hello tool"""
return "Hello from MCP!"
# Create a lifespan context manager to run the session manager
@contextlib.asynccontextmanager
async def lifespan(app: Starlette):
async with mcp.session_manager.run():
yield
# Mount the StreamableHTTP server to the existing ASGI server
# Transport-specific options are passed to streamable_http_app()
app = Starlette(
routes=[
Mount("/", app=mcp.streamable_http_app(json_response=True)),
],
lifespan=lifespan,
)
@@ -0,0 +1,38 @@
"""Example showing how to mount StreamableHTTP server using Host-based routing.
Run from the repository root:
uvicorn examples.snippets.servers.streamable_http_host_mounting:app --reload
"""
import contextlib
from starlette.applications import Starlette
from starlette.routing import Host
from mcp.server.mcpserver import MCPServer
# Create MCP server
mcp = MCPServer("MCP Host App")
@mcp.tool()
def domain_info() -> str:
"""Get domain-specific information"""
return "This is served from mcp.acme.corp"
# Create a lifespan context manager to run the session manager
@contextlib.asynccontextmanager
async def lifespan(app: Starlette):
async with mcp.session_manager.run():
yield
# Mount using Host-based routing
# Transport-specific options are passed to streamable_http_app()
app = Starlette(
routes=[
Host("mcp.acme.corp", app=mcp.streamable_http_app(json_response=True)),
],
lifespan=lifespan,
)
@@ -0,0 +1,48 @@
"""Example showing how to mount multiple StreamableHTTP servers with path configuration.
Run from the repository root:
uvicorn examples.snippets.servers.streamable_http_multiple_servers:app --reload
"""
import contextlib
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.mcpserver import MCPServer
# Create multiple MCP servers
api_mcp = MCPServer("API Server")
chat_mcp = MCPServer("Chat Server")
@api_mcp.tool()
def api_status() -> str:
"""Get API status"""
return "API is running"
@chat_mcp.tool()
def send_message(message: str) -> str:
"""Send a chat message"""
return f"Message sent: {message}"
# Create a combined lifespan to manage both session managers
@contextlib.asynccontextmanager
async def lifespan(app: Starlette):
async with contextlib.AsyncExitStack() as stack:
await stack.enter_async_context(api_mcp.session_manager.run())
await stack.enter_async_context(chat_mcp.session_manager.run())
yield
# Mount the servers with transport-specific options passed to streamable_http_app()
# streamable_http_path="/" means endpoints will be at /api and /chat instead of /api/mcp and /chat/mcp
app = Starlette(
routes=[
Mount("/api", app=api_mcp.streamable_http_app(json_response=True, streamable_http_path="/")),
Mount("/chat", app=chat_mcp.streamable_http_app(json_response=True, streamable_http_path="/")),
],
lifespan=lifespan,
)
@@ -0,0 +1,31 @@
"""Example showing path configuration when mounting MCPServer.
Run from the repository root:
uvicorn examples.snippets.servers.streamable_http_path_config:app --reload
"""
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.mcpserver import MCPServer
# Create a simple MCPServer server
mcp_at_root = MCPServer("My Server")
@mcp_at_root.tool()
def process_data(data: str) -> str:
"""Process some data"""
return f"Processed: {data}"
# Mount at /process with streamable_http_path="/" so the endpoint is /process (not /process/mcp)
# Transport-specific options like json_response are passed to streamable_http_app()
app = Starlette(
routes=[
Mount(
"/process",
app=mcp_at_root.streamable_http_app(json_response=True, streamable_http_path="/"),
),
]
)
@@ -0,0 +1,53 @@
"""Run from the repository root:
uvicorn examples.snippets.servers.streamable_starlette_mount:app --reload
"""
import contextlib
from starlette.applications import Starlette
from starlette.routing import Mount
from mcp.server.mcpserver import MCPServer
# Create the Echo server
echo_mcp = MCPServer(name="EchoServer")
@echo_mcp.tool()
def echo(message: str) -> str:
"""A simple echo tool"""
return f"Echo: {message}"
# Create the Math server
math_mcp = MCPServer(name="MathServer")
@math_mcp.tool()
def add_two(n: int) -> int:
"""Tool to add two to the input"""
return n + 2
# Create a combined lifespan to manage both session managers
@contextlib.asynccontextmanager
async def lifespan(app: Starlette):
async with contextlib.AsyncExitStack() as stack:
await stack.enter_async_context(echo_mcp.session_manager.run())
await stack.enter_async_context(math_mcp.session_manager.run())
yield
# Create the Starlette app and mount the MCP servers
app = Starlette(
routes=[
Mount("/echo", echo_mcp.streamable_http_app(stateless_http=True, json_response=True)),
Mount("/math", math_mcp.streamable_http_app(stateless_http=True, json_response=True)),
],
lifespan=lifespan,
)
# Note: Clients connect to http://localhost:8000/echo/mcp and http://localhost:8000/math/mcp
# To mount at the root of each path (e.g., /echo instead of /echo/mcp):
# echo_mcp.streamable_http_app(streamable_http_path="/", stateless_http=True, json_response=True)
# math_mcp.streamable_http_app(streamable_http_path="/", stateless_http=True, json_response=True)
@@ -0,0 +1,97 @@
"""Example showing structured output with tools."""
from typing import TypedDict
from pydantic import BaseModel, Field
from mcp.server.mcpserver import MCPServer
mcp = MCPServer("Structured Output Example")
# Using Pydantic models for rich structured data
class WeatherData(BaseModel):
"""Weather information structure."""
temperature: float = Field(description="Temperature in Celsius")
humidity: float = Field(description="Humidity percentage")
condition: str
wind_speed: float
@mcp.tool()
def get_weather(city: str) -> WeatherData:
"""Get weather for a city - returns structured data."""
# Simulated weather data
return WeatherData(
temperature=22.5,
humidity=45.0,
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): # type: ignore[reportMissingParameterType]
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}
@@ -0,0 +1,20 @@
from mcp.server.mcpserver import Context, MCPServer
mcp = MCPServer(name="Progress Example")
@mcp.tool()
async def long_running_task(task_name: str, ctx: Context, steps: int = 5) -> str:
"""Execute a task with progress updates."""
await ctx.info(f"Starting: {task_name}") # pyright: ignore[reportDeprecated]
for i in range(steps):
progress = (i + 1) / steps
await ctx.report_progress(
progress=progress,
total=1.0,
message=f"Step {i + 1}/{steps}",
)
await ctx.debug(f"Completed step {i + 1}") # pyright: ignore[reportDeprecated]
return f"Task '{task_name}' completed"