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
@@ -0,0 +1,78 @@
"""cd to the `examples/snippets` directory and run:
uv run completion-client
"""
import asyncio
import os
from mcp_types import PromptReference, ResourceTemplateReference
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
# Create server parameters for stdio connection
server_params = StdioServerParameters(
command="uv", # Using uv to run the server
args=["run", "server", "completion", "stdio"], # Server with completion support
env={"UV_INDEX": os.environ.get("UV_INDEX", "")},
)
async def run():
"""Run the completion client example."""
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
# List available resource templates
templates = await session.list_resource_templates()
print("Available resource templates:")
for template in templates.resource_templates:
print(f" - {template.uri_template}")
# List available prompts
prompts = await session.list_prompts()
print("\nAvailable prompts:")
for prompt in prompts.prompts:
print(f" - {prompt.name}")
# Complete resource template arguments
if templates.resource_templates:
template = templates.resource_templates[0]
print(f"\nCompleting arguments for resource template: {template.uri_template}")
# Complete without context
result = await session.complete(
ref=ResourceTemplateReference(type="ref/resource", uri=template.uri_template),
argument={"name": "owner", "value": "model"},
)
print(f"Completions for 'owner' starting with 'model': {result.completion.values}")
# Complete with context - repo suggestions based on owner
result = await session.complete(
ref=ResourceTemplateReference(type="ref/resource", uri=template.uri_template),
argument={"name": "repo", "value": ""},
context_arguments={"owner": "modelcontextprotocol"},
)
print(f"Completions for 'repo' with owner='modelcontextprotocol': {result.completion.values}")
# Complete prompt arguments
if prompts.prompts:
prompt_name = prompts.prompts[0].name
print(f"\nCompleting arguments for prompt: {prompt_name}")
result = await session.complete(
ref=PromptReference(type="ref/prompt", name=prompt_name),
argument={"name": "style", "value": ""},
)
print(f"Completions for 'style' argument: {result.completion.values}")
def main():
"""Entry point for the completion client."""
asyncio.run(run())
if __name__ == "__main__":
main()
@@ -0,0 +1,66 @@
"""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", "mcpserver_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.resource_templates:
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()
@@ -0,0 +1,82 @@
"""Client side of SEP-990 (enterprise IdP policy controls).
`IdentityAssertionOAuthProvider` presents an Identity Assertion Authorization Grant (ID-JAG) issued
by the enterprise IdP to the MCP authorization server using the RFC 7523 jwt-bearer grant
(`grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`, ID-JAG as `assertion`), and receives an
MCP access token. No browser redirect or dynamic client registration is involved.
Obtaining the ID-JAG (logging into the IdP and the leg-1 exchange against it) is deployment-specific
and out of scope for the SDK; supply it through the `assertion_provider` callback. The callback
receives the authorization server's issuer (the ID-JAG `aud`) and the MCP server's resource
identifier (the ID-JAG `resource` claim). SEP-990 requires a confidential client, so a client secret
is mandatory, and `issuer` is the authorization server the credentials are provisioned for - the
provider fetches metadata from that issuer's well-known and never asks the resource server which AS
to use.
"""
import asyncio
import httpx
from mcp import ClientSession
from mcp.client.auth.extensions.identity_assertion import IdentityAssertionOAuthProvider
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
class InMemoryTokenStorage:
"""Demo in-memory token storage."""
def __init__(self) -> None:
self.tokens: OAuthToken | None = None
self.client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
return self.tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
self.tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
return self.client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
self.client_info = client_info
async def fetch_id_jag(audience: str, resource: str) -> str:
"""Return the ID-JAG to present.
`audience` is the MCP authorization server's issuer (the ID-JAG `aud` claim); `resource` is the
MCP server's RFC 9728 identifier (the ID-JAG `resource` claim, which the AS audience-restricts
the issued token against). In production this exchanges the user's IdP ID token for an ID-JAG
against the enterprise identity provider.
"""
raise NotImplementedError("Obtain the ID-JAG from your enterprise identity provider")
async def main() -> None:
oauth_auth = IdentityAssertionOAuthProvider(
server_url="http://localhost:8001/mcp",
storage=InMemoryTokenStorage(),
client_id="enterprise-mcp-client",
client_secret="enterprise-mcp-secret",
issuer="http://localhost:8001",
assertion_provider=fetch_id_jag,
scope="user",
)
async with httpx.AsyncClient(auth=oauth_auth, follow_redirects=True) as http_client:
async with streamable_http_client("http://localhost:8001/mcp", http_client=http_client) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print(f"Available tools: {[tool.name for tool in tools.tools]}")
def run() -> None:
asyncio.run(main())
if __name__ == "__main__":
run()
+92
View File
@@ -0,0 +1,92 @@
"""Before running, specify running MCP RS server URL.
To spin up RS server locally, see
examples/servers/simple-auth/README.md
cd to the `examples/snippets` directory and run:
uv run oauth-client
"""
import asyncio
from urllib.parse import parse_qs, urlparse
import httpx
from pydantic import AnyUrl
from mcp import ClientSession
from mcp.client.auth import AuthorizationCodeResult, OAuthClientProvider, TokenStorage
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
class InMemoryTokenStorage(TokenStorage):
"""Demo In-memory token storage implementation."""
def __init__(self):
self.tokens: OAuthToken | None = None
self.client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
"""Get stored tokens."""
return self.tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
"""Store tokens."""
self.tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
"""Get stored client information."""
return self.client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
"""Store client information."""
self.client_info = client_info
async def handle_redirect(auth_url: str) -> None:
print(f"Visit: {auth_url}")
async def handle_callback() -> AuthorizationCodeResult:
callback_url = input("Paste callback URL: ")
params = parse_qs(urlparse(callback_url).query)
return AuthorizationCodeResult(
code=params["code"][0],
state=params.get("state", [None])[0],
iss=params.get("iss", [None])[0],
)
async def main():
"""Run the OAuth client example."""
oauth_auth = OAuthClientProvider(
server_url="http://localhost:8001",
client_metadata=OAuthClientMetadata(
client_name="Example MCP Client",
redirect_uris=[AnyUrl("http://localhost:3000/callback")],
grant_types=["authorization_code", "refresh_token"],
response_types=["code"],
scope="user",
),
storage=InMemoryTokenStorage(),
redirect_handler=handle_redirect,
callback_handler=handle_callback,
)
async with httpx.AsyncClient(auth=oauth_auth, follow_redirects=True) as custom_client:
async with streamable_http_client("http://localhost:8001/mcp", http_client=custom_client) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
print(f"Available tools: {[tool.name for tool in tools.tools]}")
resources = await session.list_resources()
print(f"Available resources: {[r.uri for r in resources.resources]}")
def run():
asyncio.run(main())
if __name__ == "__main__":
run()
@@ -0,0 +1,40 @@
"""Example of consuming paginated MCP endpoints from a client."""
import asyncio
from mcp_types import PaginatedRequestParams, Resource
from mcp.client.session import ClientSession
from mcp.client.stdio import StdioServerParameters, stdio_client
async def list_all_resources() -> None:
"""Fetch all resources using pagination."""
async with stdio_client(StdioServerParameters(command="uv", args=["run", "mcp-simple-pagination"])) as (
read,
write,
):
async with ClientSession(read, write) as session:
await session.initialize()
all_resources: list[Resource] = []
cursor = None
while True:
# Fetch a page of resources
result = await session.list_resources(params=PaginatedRequestParams(cursor=cursor))
all_resources.extend(result.resources)
print(f"Fetched {len(result.resources)} resources")
# Check if there are more pages
if result.next_cursor:
cursor = result.next_cursor
else:
break
print(f"Total resources: {len(all_resources)}")
if __name__ == "__main__":
asyncio.run(list_all_resources())
@@ -0,0 +1,62 @@
"""examples/snippets/clients/parsing_tool_results.py"""
import asyncio
import mcp_types as types
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def parse_tool_results():
"""Demonstrates how to parse different types of content in CallToolResult."""
server_params = StdioServerParameters(command="python", args=["path/to/mcp_server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# Example 1: Parsing text content
result = await session.call_tool("get_data", {"format": "text"})
for content in result.content:
if isinstance(content, types.TextContent):
print(f"Text: {content.text}")
# Example 2: Parsing structured content from JSON tools
result = await session.call_tool("get_user", {"id": "123"})
if hasattr(result, "structured_content") and result.structured_content:
# Access structured data directly
user_data = result.structured_content
print(f"User: {user_data.get('name')}, Age: {user_data.get('age')}")
# Example 3: Parsing embedded resources
result = await session.call_tool("read_config", {})
for content in result.content:
if isinstance(content, types.EmbeddedResource):
resource = content.resource
if isinstance(resource, types.TextResourceContents):
print(f"Config from {resource.uri}: {resource.text}")
else:
print(f"Binary data from {resource.uri}")
# Example 4: Parsing image content
result = await session.call_tool("generate_chart", {"data": [1, 2, 3]})
for content in result.content:
if isinstance(content, types.ImageContent):
print(f"Image ({content.mime_type}): {len(content.data)} bytes")
# Example 5: Handling errors
result = await session.call_tool("failing_tool", {})
if result.is_error:
print("Tool execution failed!")
for content in result.content:
if isinstance(content, types.TextContent):
print(f"Error: {content.text}")
async def main():
await parse_tool_results()
if __name__ == "__main__":
asyncio.run(main())
+82
View File
@@ -0,0 +1,82 @@
"""cd to the `examples/snippets/clients` directory and run:
uv run client
"""
import asyncio
import os
import mcp_types as types
from mcp import ClientSession, StdioServerParameters
from mcp.client.context import ClientRequestContext
from mcp.client.stdio import stdio_client
# Create server parameters for stdio connection
server_params = StdioServerParameters(
command="uv", # Using uv to run the server
args=["run", "server", "mcpserver_quickstart", "stdio"], # We're already in snippets dir
env={"UV_INDEX": os.environ.get("UV_INDEX", "")},
)
# Optional: create a sampling callback
async def handle_sampling_message(
context: ClientRequestContext, params: types.CreateMessageRequestParams
) -> types.CreateMessageResult:
print(f"Sampling request: {params.messages}")
return types.CreateMessageResult(
role="assistant",
content=types.TextContent(
type="text",
text="Hello, world! from model",
),
model="gpt-3.5-turbo",
stop_reason="endTurn",
)
async def run():
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session:
# Initialize the connection
await session.initialize()
# List available prompts
prompts = await session.list_prompts()
print(f"Available prompts: {[p.name for p in prompts.prompts]}")
# Get a prompt (greet_user prompt from mcpserver_quickstart)
if prompts.prompts:
prompt = await session.get_prompt("greet_user", arguments={"name": "Alice", "style": "friendly"})
print(f"Prompt result: {prompt.messages[0].content}")
# List available resources
resources = await session.list_resources()
print(f"Available resources: {[r.uri for r in resources.resources]}")
# List available tools
tools = await session.list_tools()
print(f"Available tools: {[t.name for t in tools.tools]}")
# Read a resource (greeting resource from mcpserver_quickstart)
resource_content = await session.read_resource("greeting://World")
content_block = resource_content.contents[0]
if isinstance(content_block, types.TextResourceContents):
print(f"Resource content: {content_block.text}")
# Call a tool (add tool from mcpserver_quickstart)
result = await session.call_tool("add", arguments={"a": 5, "b": 3})
result_unstructured = result.content[0]
if isinstance(result_unstructured, types.TextContent):
print(f"Tool result: {result_unstructured.text}")
result_structured = result.structured_content
print(f"Structured tool result: {result_structured}")
def main():
"""Entry point for the client script."""
asyncio.run(run())
if __name__ == "__main__":
main()
@@ -0,0 +1,24 @@
"""Run from the repository root:
uv run examples/snippets/clients/streamable_basic.py
"""
import asyncio
from mcp import ClientSession
from mcp.client.streamable_http import streamable_http_client
async def main():
# Connect to a streamable HTTP server
async with streamable_http_client("http://localhost:8000/mcp") as (read_stream, write_stream):
# Create a session using the client streams
async with ClientSession(read_stream, write_stream) as session:
# Initialize the connection
await session.initialize()
# List available tools
tools = await session.list_tools()
print(f"Available tools: {[tool.name for tool in tools.tools]}")
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,318 @@
"""URL Elicitation Client Example.
Demonstrates how clients handle URL elicitation requests from servers.
This is the Python equivalent of TypeScript SDK's elicitationUrlExample.ts,
focused on URL elicitation patterns without OAuth complexity.
Features demonstrated:
1. Client elicitation capability declaration
2. Handling elicitation requests from servers via callback
3. Catching UrlElicitationRequiredError from tool calls
4. Browser interaction with security warnings
5. Interactive CLI for testing
Run with:
cd examples/snippets
uv run elicitation-client
Requires a server with URL elicitation tools running. Start the elicitation
server first:
uv run server elicitation sse
"""
from __future__ import annotations
import asyncio
import json
import webbrowser
from typing import Any
from urllib.parse import urlparse
import mcp_types as types
from mcp_types import URL_ELICITATION_REQUIRED
from mcp import ClientSession
from mcp.client.context import ClientRequestContext
from mcp.client.sse import sse_client
from mcp.shared.exceptions import MCPError, UrlElicitationRequiredError
async def handle_elicitation(
context: ClientRequestContext,
params: types.ElicitRequestParams,
) -> types.ElicitResult | types.ErrorData:
"""Handle elicitation requests from the server.
This callback is invoked when the server sends an elicitation/request.
For URL mode, we prompt the user and optionally open their browser.
"""
if params.mode == "url":
return await handle_url_elicitation(params)
else:
# We only support URL mode in this example
return types.ErrorData(
code=types.INVALID_REQUEST,
message=f"Unsupported elicitation mode: {params.mode}",
)
ALLOWED_SCHEMES = {"http", "https"}
async def handle_url_elicitation(
params: types.ElicitRequestParams,
) -> types.ElicitResult:
"""Handle URL mode elicitation - show security warning and optionally open browser.
This function demonstrates the security-conscious approach to URL elicitation:
1. Validate the URL scheme before prompting the user
2. Display the full URL and domain for user inspection
3. Show the server's reason for requesting this interaction
4. Require explicit user consent before opening any URL
"""
# Extract URL parameters - these are available on URL mode requests
url = getattr(params, "url", None)
elicitation_id = getattr(params, "elicitationId", None)
message = params.message
if not url:
print("Error: No URL provided in elicitation request")
return types.ElicitResult(action="cancel")
# Reject dangerous URL schemes before prompting the user
parsed = urlparse(str(url))
if parsed.scheme.lower() not in ALLOWED_SCHEMES:
print(f"\nRejecting URL with disallowed scheme '{parsed.scheme}': {url}")
return types.ElicitResult(action="decline")
# Extract domain for security display
domain = extract_domain(url)
# Security warning - always show the user what they're being asked to do
print("\n" + "=" * 60)
print("SECURITY WARNING: External URL Request")
print("=" * 60)
print("\nThe server is requesting you to open an external URL.")
print(f"\n Domain: {domain}")
print(f" Full URL: {url}")
print("\n Server's reason:")
print(f" {message}")
print(f"\n Elicitation ID: {elicitation_id}")
print("\n" + "-" * 60)
# Get explicit user consent
try:
response = input("\nOpen this URL in your browser? (y/n): ").strip().lower()
except EOFError:
return types.ElicitResult(action="cancel")
if response in ("n", "no"):
print("URL navigation declined.")
return types.ElicitResult(action="decline")
elif response not in ("y", "yes"):
print("Invalid response. Cancelling.")
return types.ElicitResult(action="cancel")
# Open the browser
print(f"\nOpening browser to: {url}")
try:
webbrowser.open(url)
except Exception as e:
print(f"Failed to open browser: {e}")
print(f"Please manually open: {url}")
print("Waiting for you to complete the interaction in your browser...")
print("(The server will continue once you've finished)")
return types.ElicitResult(action="accept")
def extract_domain(url: str) -> str:
"""Extract domain from URL for security display."""
try:
return urlparse(url).netloc
except Exception:
return "unknown"
async def call_tool_with_error_handling(
session: ClientSession,
tool_name: str,
arguments: dict[str, Any],
) -> types.CallToolResult | None:
"""Call a tool, handling UrlElicitationRequiredError if raised.
When a server tool needs URL elicitation before it can proceed,
it can either:
1. Send an elicitation request directly (handled by elicitation_callback)
2. Return an error with code -32042 (URL_ELICITATION_REQUIRED)
This function demonstrates handling case 2 - catching the error
and processing the required URL elicitations.
"""
try:
result = await session.call_tool(tool_name, arguments)
# Check if the tool returned an error in the result
if result.is_error:
print(f"Tool returned error: {result.content}")
return None
return result
except MCPError as e:
# Check if this is a URL elicitation required error
if e.code == URL_ELICITATION_REQUIRED:
print("\n[Tool requires URL elicitation to proceed]")
# Convert to typed error to access elicitations
url_error = UrlElicitationRequiredError.from_error(e.error)
# Process each required elicitation
for elicitation in url_error.elicitations:
await handle_url_elicitation(elicitation)
return None
else:
# Re-raise other MCP errors
print(f"MCP Error: {e.error.message} (code: {e.error.code})")
return None
def print_help() -> None:
"""Print available commands."""
print("\nAvailable commands:")
print(" list-tools - List available tools")
print(" call <name> [json-args] - Call a tool with optional JSON arguments")
print(" secure-payment - Test URL elicitation via ctx.elicit_url()")
print(" connect-service - Test URL elicitation via UrlElicitationRequiredError")
print(" help - Show this help")
print(" quit - Exit the program")
def print_tool_result(result: types.CallToolResult | None) -> None:
"""Print a tool call result."""
if not result:
return
print("\nTool result:")
for content in result.content:
if isinstance(content, types.TextContent):
print(f" {content.text}")
else:
print(f" [{content.type}]")
async def handle_list_tools(session: ClientSession) -> None:
"""Handle the list-tools command."""
tools = await session.list_tools()
if tools.tools:
print("\nAvailable tools:")
for tool in tools.tools:
print(f" - {tool.name}: {tool.description or 'No description'}")
else:
print("No tools available")
async def handle_call_command(session: ClientSession, command: str) -> None:
"""Handle the call command."""
parts = command.split(maxsplit=2)
if len(parts) < 2:
print("Usage: call <tool-name> [json-args]")
return
tool_name = parts[1]
args: dict[str, Any] = {}
if len(parts) > 2:
try:
args = json.loads(parts[2])
except json.JSONDecodeError as e:
print(f"Invalid JSON arguments: {e}")
return
print(f"\nCalling tool '{tool_name}' with args: {args}")
result = await call_tool_with_error_handling(session, tool_name, args)
print_tool_result(result)
async def process_command(session: ClientSession, command: str) -> bool:
"""Process a single command. Returns False if should exit."""
if command in {"quit", "exit"}:
print("Goodbye!")
return False
if command == "help":
print_help()
elif command == "list-tools":
await handle_list_tools(session)
elif command.startswith("call "):
await handle_call_command(session, command)
elif command == "secure-payment":
print("\nTesting secure_payment tool (uses ctx.elicit_url())...")
result = await call_tool_with_error_handling(session, "secure_payment", {"amount": 99.99})
print_tool_result(result)
elif command == "connect-service":
print("\nTesting connect_service tool (raises UrlElicitationRequiredError)...")
result = await call_tool_with_error_handling(session, "connect_service", {"service_name": "github"})
print_tool_result(result)
else:
print(f"Unknown command: {command}")
print("Type 'help' for available commands.")
return True
async def run_command_loop(session: ClientSession) -> None:
"""Run the interactive command loop."""
while True:
try:
command = input("> ").strip()
except EOFError:
break
except KeyboardInterrupt:
print("\n")
break
if not command:
continue
if not await process_command(session, command):
break
async def main() -> None:
"""Run the interactive URL elicitation client."""
server_url = "http://localhost:8000/sse"
print("=" * 60)
print("URL Elicitation Client Example")
print("=" * 60)
print(f"\nConnecting to: {server_url}")
print("(Start server with: cd examples/snippets && uv run server elicitation sse)")
try:
async with sse_client(server_url) as (read, write):
async with ClientSession(
read,
write,
elicitation_callback=handle_elicitation,
) as session:
await session.initialize()
print("\nConnected! Type 'help' for available commands.\n")
await run_command_loop(session)
except ConnectionRefusedError:
print(f"\nError: Could not connect to {server_url}")
print("Make sure the elicitation server is running:")
print(" cd examples/snippets && uv run server elicitation sse")
except Exception as e:
print(f"\nError: {e}")
raise
def run() -> None:
"""Entry point for the client script."""
asyncio.run(main())
if __name__ == "__main__":
run()