Labsco
chrishayuk logo

chuk-mcp

β˜… 23

from chrishayuk

A Python client for the Model Context Protocol (MCP), an open standard for connecting AI assistants to external data and tools.

πŸ”₯πŸ”₯πŸ”₯βœ“ VerifiedFreeAdvanced setup

chuk-mcp

PyPI - Downloads

A lean, minimal Python implementation of the Model Context Protocol (MCP).

Brings first-class MCP protocol support to Python β€” lightweight, async, and spec-accurate from day one.

Requires Python 3.11+

chuk-mcp gives you a clean, typed, transport-agnostic implementation for both MCP clients and servers. It focuses on the protocol surface (messages, types, versioning, transports) and leaves orchestration, UIs, and agent frameworks to other layers.

✳️ What this is: a protocol compliance library with ergonomic helpers for clients and servers.

β›” What this isn't: a chatbot runtime, workflow engine, or an opinionated application framework.

Architecture: Where chuk-mcp Fits

Stack Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Your AI Application                β”‚
β”‚   (Claude, GPT, custom agents)       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚ MCP Protocol
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   chuk-mcp Client                    β”‚  ← You are here
β”‚   β€’ Protocol compliance              β”‚
β”‚   β€’ Transport (stdio/Streamable HTTP)β”‚
β”‚   β€’ Type-safe messages               β”‚
β”‚   β€’ Capability negotiation           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚ MCP Protocol
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   chuk-mcp Server (optional)         β”‚
β”‚   β€’ Protocol handlers                β”‚
β”‚   β€’ Tool/Resource registration       β”‚
β”‚   β€’ Session management               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
             β”‚
             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚   Your Tools & Resources             β”‚
β”‚   (databases, APIs, files, etc)      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

chuk-mcp provides the protocol layer β€” connect AI applications to tools and data sources using the standard MCP protocol.

Internal Architecture

The library itself is organized in layers that you can use at different levels of abstraction:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              CLI & Demo Layer           β”‚  __main__.py, demos/
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚             Client/Server API           β”‚  High-level abstractions
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚            Protocol Layer               β”‚  Messages, types, features
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚            Transport Layer              β”‚  stdio, Streamable HTTP
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚             Base Layer                  β”‚  Pydantic fallback, config
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Layer Details:

LayerPurposeUsage
CLI & DemoBuilt-in utilities and demonstrationsOptional β€” use protocol layer directly
Client/Server APIHigh-level abstractions for client-server interactionsOptional β€” can use protocol layer directly
Protocol LayerMessage definitions, type-safe request/response handling, capability negotiationCore β€” implements MCP spec
Transport LayerPluggable transport implementations (stdio, Streamable HTTP)Choose based on deployment
Base LayerPydantic fallback, shared config, type adaptersFoundation β€” automatic

Most users work with the Protocol Layer (send_* functions) and Transport Layer (stdio/HTTP clients), optionally using the Client/Server API for higher-level abstractions.


Table of Contents


Why chuk-mcp?

  • Protocol-first: Focuses on MCP messages, types, and capability negotiation β€” spec.modelcontextprotocol.io
  • Client + Server: Full support for building both MCP clients and servers
  • Typed: Full type hints; optional Pydantic models when available
  • Transport-agnostic: stdio by default, Streamable HTTP (NDJSON) for remote servers, easily extensible
  • Async-first: Built on AnyIO; integrate with anyio.run(...) or your existing loop
  • Small & focused: No heavy orchestration or agent assumptions
  • Clean protocol layer: Errors fail fast without retries β€” bring your own error handling strategy
  • Reliable: Clear errors, structured logging hooks, composable with retry/caching layers
  • ⚑ High-performance: Protocol overhead in the 2-5ms range; optional fast JSON for 4x faster serialization. See Protocol Performance for detailed benchmarks

Protocol Performance

chuk-mcp is designed to keep MCP protocol overhead in the 2-5 ms range, so the cost of using tools is dominated by the tools themselves, not the protocol.

Why it's fast:

  • Zero heavy dependencies (AnyIO core only)
  • Async-native stdio & NDJSON HTTP
  • No tool execution inside the library
  • Optional orjson fast path ([fast-json])

πŸ’‘ For concurrency & capacity numbers, see Scaling & Concurrency.

⚑ Latency Benchmarks

Protocol overhead (typical measurements on modern hardware):

  • Initialize β†’ Tool List: 2-3 ms
  • Tool Call Round Trip: < 5 ms overhead (beyond actual tool execution time)
  • Streaming: Near-zero overhead due to NDJSON chunk boundaries

Benchmarks run on macOS (Darwin 24.6.0), Python 3.11 β€” see benchmarks/PERFORMANCE_REPORT.md for exact environment and commands.

πŸš€ JSON Serialization (Optional Fast Path)

Install with [fast-json] for ~4x faster JSON operations using orjson:

  • Serialization: ~6x faster
  • Deserialization: ~2x faster
  • Round-trip: ~4x faster
pip install "chuk-mcp[fast-json]"  # Automatic with graceful fallback

Benchmark numbers from benchmarks/json_performance.py comparing orjson vs stdlib json on realistic MCP messages.

🎯 Ideal Use Cases

This makes chuk-mcp perfect for:

  • High-frequency tool calls β€” minimal overhead per request
  • Real-time agents β€” sub-5ms protocol latency
  • Streaming UIs β€” near-zero NDJSON chunk overhead
  • Tool processors β€” fast enough to be transparent
  • WASM/edge environments β€” minimal footprint
  • High-throughput workloads β€” proven at scale (see Scaling & Concurrency)

At a Glance

Try it now:

# Install an example MCP server
uv tool install mcp-server-sqlite

# Run the quick-start example
uv run python examples/quickstart_sqlite.py

Hello World

A minimal working MCP server in ~10 lines:

# hello_mcp.py
import anyio
from chuk_mcp.server import MCPServer, run_stdio_server
from chuk_mcp.protocol.types import ServerCapabilities, ToolCapabilities

async def main():
    server = MCPServer("hello", "1.0", ServerCapabilities(tools=ToolCapabilities()))

    async def handle_tools_list(message, session_id):
        return server.protocol_handler.create_response(
            message.id,
            {"tools": [{"name": "hello", "description": "Say hi", "inputSchema": {"type": "object"}}]}
        ), None

    server.protocol_handler.register_method("tools/list", handle_tools_list)
    await run_stdio_server(server)

anyio.run(main)

Run it: uv run python hello_mcp.py β€” or connect any MCP client via stdio!


Stdio (local processes):

# Connect to an MCP server via stdio and list tools
import anyio
from chuk_mcp import StdioServerParameters, stdio_client
from chuk_mcp.protocol.messages import send_initialize
from chuk_mcp.protocol.messages.tools import send_tools_list

async def main():
    params = StdioServerParameters(command="uvx", args=["mcp-server-sqlite", "--db-path", "example.db"])
    async with stdio_client(params) as (read, write):
        init = await send_initialize(read, write)
        tools = await send_tools_list(read, write)
        print("Server:", init.serverInfo.name)
        print("Tools:", [t.name for t in tools.tools])

anyio.run(main)

Streamable HTTP (remote servers):

# Local dev (plain HTTP)
import anyio
from chuk_mcp.transports.http import http_client, HttpClientParameters
from chuk_mcp.protocol.messages import send_initialize

async def main():
    params = HttpClientParameters(
        url="http://localhost:8989/mcp",
        timeout_s=30,
        headers={"Authorization": "Bearer <token>"}
    )
    async with http_client(params) as (read, write):
        init = await send_initialize(read, write)
        print("Connected:", init.serverInfo.name)

anyio.run(main)

# TLS (secure transport)
async def main_secure():
    params = HttpClientParameters(
        url="https://mcp.example.com/mcp",
        timeout_s=30,
        headers={"Authorization": "Bearer <token>"}
    )
    async with http_client(params) as (read, write):
        init = await send_initialize(read, write)
        print("Connected:", init.serverInfo.name)

anyio.run(main_secure)

Core Concepts

Tools

Discover and call server-exposed functions.

from chuk_mcp.protocol.messages.tools import send_tools_list, send_tools_call

# list
tools = await send_tools_list(read, write)
for t in tools.tools:
    print(t.name, "-", t.description)

# call
call = await send_tools_call(read, write, name="greet", arguments={"name": "World"})
print(call.content)

See full example: examples/e2e_tools_client.py

Resources

List/read (and optionally subscribe to) data sources.

from chuk_mcp.protocol.messages.resources import send_resources_list, send_resources_read

resources = await send_resources_list(read, write)
if resources.resources:
    uri = resources.resources[0].uri
    data = await send_resources_read(read, write, uri)
    print(data.contents)

See full examples:

Prompts

Parameterized, reusable prompt templates.

from chuk_mcp.protocol.messages.prompts import send_prompts_list, send_prompts_get

prompts = await send_prompts_list(read, write)
if prompts.prompts:
    got = await send_prompts_get(read, write, name=prompts.prompts[0].name, arguments={})
    for m in got.messages:
        print(m.role, m.content)

See full example: examples/e2e_prompts_client.py

Roots (optional)

Advertise directories the client authorizes the server to access.

from chuk_mcp.protocol.messages.roots import send_roots_list
roots = await send_roots_list(read, write)  # if supported

See full example: examples/e2e_roots_client.py

Sampling & Completion (optional)

Some servers can ask the client to sample text or provide completion for arguments. These are opt-in and capability-gated.

See full examples:


Transports

chuk-mcp cleanly separates protocol from transport, so you can use the same protocol handlers with any transport layer:

  • Stdio β€” ideal for local child-process servers
  • Streamable HTTP β€” speak to remote servers over HTTP (chunked/NDJSON)
  • SSE (Server-Sent Events) β€” for browser/IDE integrations with one-way server push
  • Extensible β€” implement your own transport by adapting the simple (read, write) async interface

Note: chuk-mcp is fully async (AnyIO). Use anyio.run(...) or integrate into your event loop.

Note: Protocol capabilities are negotiated during initialize, independent of transport. You choose the transport (stdio or Streamable HTTP) based on deployment/runtime needs.

Thread-safety: Client instances are not thread-safe across event loops. See FAQ for details.

Streamable HTTP uses chunked NDJSON. Configure HttpClientParameters(timeout_s=30, headers={"Authorization": "Bearer ..."}). Clients stream NDJSON with backpressure. For large payloads, prefer NDJSON chunks over base64 blobs to avoid memory spikes.

Framing: Streamable HTTP uses NDJSON (one JSON object per line). Servers should flush after each object; proxies must not buffer indefinitely.

Compression: Enable gzip at the proxy to reduce large content streams. MCP payloads compress well.

Protocol Layer Design: The protocol layer is intentionally clean and minimal β€” errors are raised immediately without retries. This design keeps the protocol layer focused on message transport and compliance with the MCP specification. For use cases requiring retry logic, error handling, rate limiting, or caching, use chuk-tool-processor which provides composable wrappers for retries with exponential backoff, rate limiting, and caching. This separation of concerns allows you to choose the right retry strategy for your specific application needs.

Security: When exposing Streamable HTTP, terminate TLS at a proxy and require auth (e.g., bearer tokens). For private CAs, configure your client's trust store (e.g., SSL_CERT_FILE=/path/ca.pem, REQUESTS_CA_BUNDLE, or SSL_CERT_DIR). The protocol layer is transport-agnostic and does not impose auth.


Examples & Feature Demonstrations

The examples/ directory contains comprehensive, working demonstrations of all MCP features:

Quick Start Examples

End-to-End (E2E) Examples

Complete client-server pairs built with pure chuk-mcp, demonstrating both client and server implementation for each MCP feature:

Core Features:

Advanced Features:

Error Handling:

Running Examples:

Many E2E examples are self-contained with their own protocol-level server built using pure chuk-mcp. Where relevant, the client starts the corresponding demo server:

# Run any example directly - the client will start its server
uv run python examples/e2e_tools_client.py

# Test all E2E examples
for example in examples/e2e_*_client.py; do
    echo "Testing $example"
    uv run python "$example" || exit 1
done

Note: Where relevant, examples include a corresponding e2e_*_server.py showing a minimal server built with the same protocol layer.

See examples/README.md for detailed documentation of all examples.


Versioning & Compatibility

  • chuk-mcp follows the MCP spec revisions and negotiates capabilities at initialize.
  • Newer features are capability-gated and degrade gracefully with older servers.
  • Optional typing/validation uses Pydantic if available, otherwise a lightweight fallback.

πŸ“‹ Supported Protocol Versions (as of v0.1.x)

VersionStatusSupport Policy
2025-06-18LatestPrimary support, all features
2025-03-26StableFull compatibility, maintained
2024-11-05LegacyBackward compatibility, deprecation TBD

Tested Platforms: Linux, macOS, Windows (Python 3.11+)

Support Policy: Latest and Stable versions receive full support. Legacy version support will be maintained until 2026-Q2, after which it may be deprecated. See changelog for migration guidance.

πŸ“Š Client Feature Support Matrix

Feature Category2024-11-052025-03-262025-06-18Implementation Status
Core Operations
Tools (list/call)βœ…βœ…βœ…βœ… Complete
Resources (list/read/subscribe)βœ…βœ…βœ…βœ… Complete
Prompts (list/get)βœ…βœ…βœ…βœ… Complete
Transport
Stdioβœ…βœ…βœ…βœ… Complete
Streamable HTTPβ€“βœ…βœ…βœ… Complete
Advanced Features
Samplingβœ…βœ…βœ…βœ… Complete
Completionβœ…βœ…βœ…βœ… Complete
Rootsβœ…βœ…βœ…βœ… Complete
ElicitationβŒβŒβœ…βœ… Complete
Quality Features
Progress Trackingβœ…βœ…βœ…βœ… Complete
Cancellationβœ…βœ…βœ…βœ… Complete
Notificationsβœ…βœ…βœ…βœ… Complete
Loggingβœ…βœ…βœ…βœ… Complete
Annotationsβœ…βœ…βœ…βœ… Complete

Features degrade gracefully when interacting with older servers.

See the changelog for exact spec versions supported and any deprecations.

Versioning Policy

This project follows Semantic Versioning for public APIs under chuk_mcp.*:

  • Major (X.0.0): Breaking changes to public APIs
  • Minor (0.X.0): New features, backward compatible
  • Patch (0.0.X): Bug fixes, backward compatible

Breaking Changes & Migration

v0.7.2: Exception Handling Changes

What Changed: send_initialize() and send_initialize_with_client_tracking() now always raise exceptions instead of returning None on errors.

Why: This enables proper error handling, automatic OAuth re-authentication in downstream tools (like mcp-cli), and follows Python best practices.

Migration Guide:

Before (v0.7.1 and earlier):

result = await send_initialize(read, write)
if result is None:
    logging.error("Initialization failed")
    return
# Use result
print(f"Connected to {result.serverInfo.name}")

After (v0.7.2+):

try:
    result = await send_initialize(read, write)
    # Success - result is guaranteed to be InitializeResult (not None)
    print(f"Connected to {result.serverInfo.name}")
except RetryableError as e:
    # Handle retryable errors (e.g., 401 authentication)
    logging.error(f"Retryable error: {e}")
except VersionMismatchError as e:
    # Handle version incompatibility
    logging.error(f"Version mismatch: {e}")
except TimeoutError as e:
    # Handle timeout
    logging.error(f"Timeout: {e}")
except Exception as e:
    # Handle other errors
    logging.error(f"Error: {e}")

Return Type Changes:

  • send_initialize(): Optional[InitializeResult] β†’ InitializeResult
  • send_initialize_with_client_tracking(): Optional[InitializeResult] β†’ InitializeResult

Benefits:

  • βœ… Automatic OAuth re-authentication in mcp-cli
  • βœ… Proper error propagation and debugging
  • βœ… Type safety (no Optional checks needed)
  • βœ… Full exception context with stack traces

See Also:


Comparison with Official MCP SDK

Featurechuk-mcpOfficial MCP Python SDK
PhilosophyProtocol compliance libraryFull framework
ScopeClient + Server, protocol-focusedClient + Server framework
TypingOptional Pydantic (fallback available)Pydantic required
Transportsstdio, SSE, Streamable HTTP (pluggable)stdio, SSE, Streamable HTTP
Browser/WASMPyodide-compatibleVaries / not a primary target
DependenciesMinimal (anyio core)Heavier stack
Server FrameworkLightweight helpersOpinionated server structure
API StyleExplicit send_* functionsHigher-level abstractions
Target Use CaseProtocol integration, custom clients/serversFull MCP applications
OrchestrationExternal (you choose)Built-in patterns
Learning CurveLow (protocol-level)Medium (framework concepts)

When to choose chuk-mcp:

  • Building custom MCP clients or servers
  • Need transport flexibility (Streamable HTTP)
  • Want minimal dependencies
  • Prefer protocol-level control
  • Running in constrained environments (WASM, edge functions)
  • Need to integrate MCP into existing applications

Real-world example: chuk-mcp-server uses chuk-mcp as its protocol compliance layer

When to choose official SDK:

  • Building full MCP servers quickly with opinionated patterns
  • Want framework abstractions out of the box
  • Primarily using stdio transport
  • Prefer higher-level APIs

Design Goals & Non-Goals

Goals

  • Be the simplest way to implement MCP in Python (client or server)
  • Keep the API small, explicit, and typed
  • Make transports pluggable and protocol logic reusable
  • Support both client and server use cases with lightweight abstractions

Non‑Goals

  • Competing with full agent frameworks / IDEs
  • Baking in opinionated application structure or workflow engines
  • Shipping heavyweight dependencies by default
  • Providing high-level orchestration (that's your application layer)

Scaling & Concurrency

chuk-mcp handles hundreds of concurrent connections efficiently with minimal resource usage:

Concurrency Benchmarks

Tested Performance (see benchmarks/PERFORMANCE_REPORT.md for full details):

  • 700+ concurrent connections tested successfully (stopped at timeout, not capacity limit)
  • 252+ connections/sec throughput for rapid connection churn
  • ~34KB memory per connection with linear scaling
  • Zero memory leaks verified over 200+ iterations

Capacity Estimates:

  • Small scale (< 100 agents): 512MB RAM, 1 core
  • Medium scale (100-1,000 agents): 1-2GB RAM, 2-4 cores
  • Large scale (1,000-10,000 agents): 4-8GB RAM, 8+ cores
  • Enterprise scale (10,000+ agents): Load balancing recommended

Best Practices

Pattern: Create all β†’ Initialize all (Sequential)

# RECOMMENDED: Fastest pattern for multiple agents
agent1 = create_agent(mcp_config1)
agent2 = create_agent(mcp_config2)
agent3 = create_agent(mcp_config3)

# Then initialize
await agent1.initialize_tools()
await agent2.initialize_tools()
await agent3.initialize_tools()

Pattern: Interleaved (Also Supported)

# WORKS: Fixed in v0.8.1 with lazy stream initialization
agent1 = create_agent(mcp_config1)
await agent1.initialize_tools()

agent2 = create_agent(mcp_config2)
await agent2.initialize_tools()

agent3 = create_agent(mcp_config3)  # No longer hangs!
await agent3.initialize_tools()

Important: Always use StdioClient as an async context manager:

# CORRECT: Streams initialized in async context
async with StdioClient(params) as client:
    # Use client here
    pass

# INCORRECT: Don't access streams before __aenter__
client = StdioClient(params)
client.get_streams()  # ❌ Raises RuntimeError

Monitoring Recommendations

For deployments, monitor these metrics:

  • Active Connections: Track concurrent client count
  • Memory Growth: Should remain flat over time (~0.034MB per connection)
  • File Descriptors: Monitor via lsof or /proc/<pid>/fd
  • Connection Success Rate: Should maintain 100%

See benchmarks/PERFORMANCE_REPORT.md for detailed performance analysis and deployment guidelines.


Feature Showcase

This section provides detailed code snippets demonstrating MCP features. All examples include full type safety.

πŸ”§ Tools β€” Calling Functions

Tools are functions that AI can invoke:

from chuk_mcp.protocol.messages.tools import send_tools_list, send_tools_call
from chuk_mcp.protocol.types.content import parse_content, TextContent

# List all available tools β€” returns typed ListToolsResult
tools_result = await send_tools_list(read, write)
print(f"πŸ“‹ Available tools: {len(tools_result.tools)}")

for tool in tools_result.tools:
    print(f"  β€’ {tool.name}: {tool.description}")

# Call a tool β€” returns typed ToolResult
result = await send_tools_call(
    read, write,
    name="greet",
    arguments={"name": "World"}
)

# Parse content with type safety
content = parse_content(result.content[0])
assert isinstance(content, TextContent)
print(f"βœ… Result: {content.text}")

Full example: uv run python examples/e2e_tools_client.py

πŸ“„ Resources β€” Reading Data

Resources provide access to data sources (files, databases, APIs):

from chuk_mcp.protocol.messages.resources import send_resources_list, send_resources_read

# List available resources β€” returns typed ListResourcesResult
resources_result = await send_resources_list(read, write)
print(f"πŸ“š Found {len(resources_result.resources)} resources")

for resource in resources_result.resources:
    print(f"  β€’ {resource.name}")
    print(f"    URI: {resource.uri}")

# Read a resource β€” returns typed ReadResourceResult
if resources_result.resources:
    uri = resources_result.resources[0].uri
    read_result = await send_resources_read(read, write, uri)

    for content in read_result.contents:
        if hasattr(content, 'text'):
            print(f"πŸ“– Content: {content.text[:200]}...")

Full example: uv run python examples/e2e_resources_client.py

πŸ“‘ Resource Subscriptions β€” Live Updates

Subscribe to resources for real-time change notifications:

from chuk_mcp.protocol.messages.resources import (
    send_resources_subscribe,
    send_resources_unsubscribe
)

# Subscribe to a resource
uri = "file:///logs/app.log"
success = await send_resources_subscribe(read, write, uri)

if success:
    print(f"βœ… Subscribed to {uri}")
    print("πŸ“‘ Listening for changes...")

    # In a real app, handle notifications in a loop
    # Notifications arrive as messages from the server

    # Unsubscribe when done
    await send_resources_unsubscribe(read, write, uri)
    print("πŸ”• Unsubscribed")

Full example: uv run python examples/e2e_subscriptions_client.py

πŸ’¬ Prompts β€” Template Management

Prompts are reusable templates with parameters:

from chuk_mcp.protocol.messages.prompts import send_prompts_list, send_prompts_get

# List available prompts β€” returns typed ListPromptsResult
prompts_result = await send_prompts_list(read, write)
print(f"πŸ’¬ Available prompts: {len(prompts_result.prompts)}")

for prompt in prompts_result.prompts:
    print(f"  β€’ {prompt.name}: {prompt.description}")
    if hasattr(prompt, 'arguments') and prompt.arguments:
        args = [a.name for a in prompt.arguments]
        print(f"    Arguments: {', '.join(args)}")

# Get a prompt with arguments β€” returns typed GetPromptResult
prompt_result = await send_prompts_get(
    read, write,
    name="code_review",
    arguments={"file": "main.py", "language": "python"}
)

# Use the formatted messages
for message in prompt_result.messages:
    print(f"πŸ€– {message.role}: {message.content}")

Full example: uv run python examples/e2e_prompts_client.py

🎯 Sampling β€” AI Content Generation

Let servers request AI to generate content on their behalf (requires user approval):

from chuk_mcp.protocol.messages.sampling import sample_text

# Check if server supports sampling
if hasattr(init_result.capabilities, 'sampling'):
    print("βœ… Server supports sampling")

    # Server requests AI to generate content using helper
    result = await sample_text(
        read, write,
        prompt="Explain quantum computing in simple terms",
        max_tokens=1000,
        model_hint="claude",
        temperature=0.7
    )

    # Access typed response
    if hasattr(result.content, 'text'):
        print(f"πŸ€– AI Generated: {result.content.text}")

    print(f"πŸ“Š Model: {result.model}")
    print(f"πŸ”’ Stop Reason: {result.stopReason or 'N/A'}")

Use Case: Servers can use sampling to generate code, documentation, or analysis based on data they have access to.

Full example: uv run python examples/e2e_sampling_client.py

πŸ“ Roots β€” Directory Access Control

Roots define which directories the client allows servers to access.

from chuk_mcp.protocol.messages.roots import (
    send_roots_list,
    send_roots_list_changed_notification
)

# Check if server supports roots
if hasattr(init_result.capabilities, 'roots'):
    print("βœ… Server supports roots capability")

    # List current roots β€” returns typed ListRootsResult
    roots_result = await send_roots_list(read, write)

    print(f"πŸ“ Available roots: {len(roots_result.roots)}")
    for root in roots_result.roots:
        print(f"  β€’ {root.name}: {root.uri}")

    # Notify server when roots change
    await send_roots_list_changed_notification(write)
    print("πŸ“’ Notified server of roots change")

Use Case: Control which directories AI can access, enabling secure sandboxed operations.

Full example: uv run python examples/e2e_roots_client.py

🎭 Elicitation β€” User Input Requests

Elicitation allows servers to request structured input from users:

from chuk_mcp.protocol.messages.elicitation import send_elicitation_request

# Server requests user input
response = await send_elicitation_request(
    read, write,
    prompt="Enter API credentials",
    fields=[
        {"name": "api_key", "type": "text", "required": True},
        {"name": "region", "type": "select", "options": ["us", "eu", "asia"]}
    ]
)

# Access user's input
print(f"User provided: {response.values}")

Use Case: Interactive workflows, OAuth flows, confirmation dialogs.

Full example: uv run python examples/e2e_elicitation_client.py

πŸ’‘ Completion β€” Smart Autocomplete

Get intelligent suggestions for tool arguments:

from chuk_mcp.protocol.messages.completions import (
    send_completion_complete,
    create_argument_info
)

# Get completions for a file path argument β€” returns typed CompletionResult
response = await send_completion_complete(
    read, write,
    ref={"type": "ref/resource", "uri": "file:///data/"},
    argument=create_argument_info(
        name="filename",
        value="sales_202"  # Partial input
    )
)

# Show suggestions
print("πŸ’‘ Suggestions for 'sales_202':")
for value in response.completion.values:
    print(f"  β€’ {value}")

Full example: uv run python examples/e2e_completion_client.py

πŸ“Š Progress Tracking

Monitor long-running operations with progress updates:

from chuk_mcp.protocol.messages.tools import send_tools_call

# Call a long-running tool
# Progress notifications will be sent automatically
print("πŸ”„ Starting long operation...")

result = await send_tools_call(
    read, write,
    name="process_large_dataset",
    arguments={"dataset": "sales_data.csv"}
)

print("βœ… Operation complete")
# Progress notifications are handled automatically by the client

Full example: uv run python examples/e2e_progress_client.py

🚫 Cancellation

Cancel long-running operations with timeout:

import anyio
from chuk_mcp.protocol.messages.cancellation import send_cancelled_notification
from chuk_mcp.protocol.messages.tools import send_tools_call

async def cancel_after_timeout():
    request_id = "long-op-123"

    async with anyio.create_task_group() as tg:
        # Start long-running operation
        tg.start_soon(send_tools_call, read, write, "process_large_dataset",
                      {"dataset": "big.csv"}, request_id)

        # Cancel after 2 seconds
        with anyio.move_on_after(2):
            await anyio.sleep(999)

        # Send cancellation
        await send_cancelled_notification(write, request_id=request_id, reason="timeout")
        print("🚫 Cancellation sent")

anyio.run(cancel_after_timeout)

Full example: uv run python examples/e2e_cancellation_client.py

🌐 Multiple Transports

Use different transport protocols for different scenarios:

import anyio
from chuk_mcp.protocol.messages import send_initialize
from chuk_mcp import stdio_client, StdioServerParameters
from chuk_mcp.transports.http import http_client, HttpClientParameters

async def main():
    # Stdio transport (local processes)
    p1 = StdioServerParameters(
        command="uvx",
        args=["mcp-server-sqlite", "--db-path", "local.db"]
    )
    async with stdio_client(p1) as (r, w):
        init = await send_initialize(r, w)
        print("πŸ“‘ Stdio:", init.serverInfo.name)

    # Streamable HTTP transport (remote servers)
    p2 = HttpClientParameters(url="http://localhost:8989/mcp")
    async with http_client(p2) as (r, w):
        init = await send_initialize(r, w)
        print("🌐 Streamable HTTP:", init.serverInfo.name)

anyio.run(main)

πŸ”„ Multi-Server Orchestration

Connect to multiple servers simultaneously:

from chuk_mcp import stdio_client, StdioServerParameters
from chuk_mcp.protocol.messages import send_initialize
from chuk_mcp.protocol.messages.tools import send_tools_list

servers = [
    StdioServerParameters(
        command="uvx",
        args=["mcp-server-sqlite", "--db-path", "db1.db"]
    ),
    StdioServerParameters(
        command="npx",
        args=["-y", "@modelcontextprotocol/server-filesystem", "."]
    )
]

print("πŸ”— Connecting to multiple servers...")

for i, server_params in enumerate(servers, 1):
    try:
        async with stdio_client(server_params) as (read, write):
            init_result = await send_initialize(read, write)
            tools_result = await send_tools_list(read, write)

            print(f"\nπŸ“‘ Server {i}: {init_result.serverInfo.name}")
            print(f"   Tools: {len(tools_result.tools)}")

            # Show first 3 tools
            for tool in tools_result.tools[:3]:
                print(f"   β€’ {tool.name}")
    except Exception as e:
        print(f"⚠️ Server {i} failed: {e}")

Type Safety & Validation

All protocol messages return fully typed results using Pydantic (or fallback validation):

from chuk_mcp.protocol.types.content import parse_content, TextContent
from chuk_mcp.protocol.messages.tools import send_tools_call

# Call a tool and get a typed result
tool_result = await send_tools_call(read, write, name="greet", arguments={"name": "World"})

# Type-safe content parsing
content = parse_content(tool_result.content[0])
assert isinstance(content, TextContent)
print(content.text)

Benefits:

  • Typed returns: All send_* functions return typed Pydantic models
  • Content parsing: Use parse_content() for type-safe content handling
  • Runtime validation: Automatic validation with clear error messages
  • IDE support: Full autocomplete and type checking

Monitoring & Logging

Built-in features for deployed environments:

from chuk_mcp.protocol.messages.logging import send_logging_set_level

# Set server logging level
await send_logging_set_level(write, level="debug")

Features:

  • Structured logging with configurable levels
  • Performance monitoring (latency, error rates, throughput)
  • Progress tracking and cancellation support
  • Clean error propagation (no automatic retries at protocol layer)

Full example: uv run python examples/e2e_logging_client.py


Ecosystem

chuk-mcp is part of a modular suite of Python MCP tools:

  • chuk-tool-processor β€” Reliable tool call execution with retries, caching, and exponential backoff
  • chuk-mcp-server β€” Real-world MCP server implementation built on chuk-mcp
  • chuk-mcp-cli β€” Interactive CLI and playground for testing MCP servers

Each component focuses on doing one thing well and can be used independently or together. All of these build on chuk-mcp's protocol layer, so they inherit the same low-latency, minimal-overhead characteristics.