chuk-mcp-runtime 0.11.1
Generic CHUK MCP Runtime for MCP servers
License: Apache-2.0
Requires Python: >=3.11

# CHUK MCP Runtime

**Version 0.10.4** - Pydantic-Native Artifact Integration

[![PyPI](https://img.shields.io/pypi/v/chuk-mcp-runtime.svg)](https://pypi.org/project/chuk-mcp-runtime/)
[![Test](https://github.com/chrishayuk/chuk-mcp-runtime/actions/workflows/test.yml/badge.svg)](https://github.com/chrishayuk/chuk-mcp-runtime/actions/workflows/test.yml)
![Python Version](https://img.shields.io/badge/python-3.11%2B-blue)
![License](https://img.shields.io/badge/license-Apache%202.0-blue)
![Coverage](https://img.shields.io/badge/coverage-97%25-brightgreen)
![Official MCP SDK](https://img.shields.io/badge/built%20on-Official%20MCP%20SDK-blue)

A robust runtime for the official Model Context Protocol (MCP) — adds proxying, session management, JWT auth, **persistent user storage with scopes**, and progress notifications.

> ✅ **Continuously tested against the latest official MCP SDK releases** for guaranteed protocol compatibility.

---

**CHUK MCP Runtime extends the official MCP SDK**, adding a battle-tested runtime layer for real deployments — without modifying or re-implementing the protocol.

## Architecture

```
┌──────────────────────────┐
│  Client / Agent          │
│  (Claude, OpenAI, etc.)  │
└───────────┬──────────────┘
            │
            ▼
┌──────────────────────────┐
│  CHUK MCP Runtime        │
│  - Proxy Manager         │
│  - Session Manager       │
│  - Artifact Storage      │
│  - Resource Provider     │
│  - JWT Auth & Progress   │
└───────────┬──────────────┘
            │
            ▼
┌──────────────────────────┐
│  MCP SDK Servers & Tools │
│  (Official MCP Protocol) │
└──────────────────────────┘
```

## Why CHUK MCP Runtime?

- 🔌 **Multi-Server Proxy** - Connect multiple MCP servers through one unified endpoint
- 🔐 **Secure by Default** - All built-in tools disabled unless explicitly enabled
- 🌐 **Universal Connectivity** - stdio, SSE, and HTTP transports supported
- 🔧 **OpenAI Compatible** - Transform MCP tools into OpenAI function calling format
- 📊 **Progress Notifications** - Real-time progress reporting for long operations
- ⚡ **Production Features** - Session isolation, timeout protection, JWT auth
- 📦 **Storage Scopes (NEW v0.9)** - Session (ephemeral), User (persistent), Sandbox (shared)

## Quick Start (30 seconds)

Run any official MCP server (like `mcp-server-time`) through the CHUK MCP Runtime proxy:

```bash
chuk-mcp-proxy --stdio time --command uvx -- mcp-server-time
```

That's it! You now have a running MCP proxy with tools like `proxy.time.get_current_time` (default 60s tool timeout).

> ℹ️ **Tip:** Everything after `--` is forwarded to the stdio child process (here: `mcp-server-time`).

> 💡 **Windows:** Install `uv` and use `uvx` from a shell with it on PATH, or replace `--command uvx -- mcp-server-time` with your Python launcher. Note that `mcp-server-time` may expose a Python module name like `mcp_server_time` depending on install method (e.g., `py -m mcp_server_time`).

### Hello World with Local Tools (10 seconds)

Create your first local MCP tool:

```python
# my_tools/tools.py
from chuk_mcp_runtime.common.mcp_tool_decorator import mcp_tool

@mcp_tool(name="greet", description="Say hi")
async def greet(name: str = "world") -> str:
    return f"Hello, {name}!"
```

```yaml
# config.yaml
server:
  type: "stdio"

mcp_servers:
  my_tools:
    enabled: true
    location: "./my_tools"
    tools:
      enabled: true
      module: "my_tools.tools"
```

```bash
# Run it (default 60s tool timeout)
chuk-mcp-server --config config.yaml
```

**Smoke test (stdio):**

```bash
# From a second terminal while chuk-mcp-server is running on stdio:
# Send tools/list over stdin and read stdout (minimal JSON-RPC roundtrip)
printf '%s\n' '{
  "jsonrpc":"2.0",
  "id": 1,
  "method":"tools/list",
  "params": {}
}'
```

## Installation

### Requirements
- Python 3.11+ (with `uv` recommended)
- On minimal distros/containers, install `tzdata` for timezone support
- (Optional) `jq` for pretty-printing JSON in curl examples

```bash
# Basic installation
uv pip install chuk-mcp-runtime

# With optional dependencies (installs dependencies for SSE/HTTP transports and development tooling)
uv pip install "chuk-mcp-runtime[websocket,dev]"

# Install tzdata for proper timezone support (containers, Alpine Linux)
uv pip install tzdata
```

## What Can You Build?

- **Multi-Server Gateway**: Expose multiple MCP servers (time, weather, GitHub, etc.) through one proxy
- **Enterprise MCP Services**: Add session management, persistent storage, and JWT auth to any MCP setup
- **OpenAI Bridge**: Transform any MCP server's tools into OpenAI-compatible function calls
- **Hybrid Architectures**: Run local Python tools alongside remote MCP servers
- **Progress-Aware Tools**: Build long-running operations with real-time client updates
- **Persistent User Files (NEW)**: Store user documents, prompts, and files that survive sessions

## Table of Contents

- [What's New in v0.10.4](#whats-new-in-v0104)
- [What's New in v0.9.0](#whats-new-in-v090)
- [Redis Cluster Support](#redis-cluster-support-new-in-v0104)
- [Key Concepts](#key-concepts)
- [Configuration Reference](#configuration-reference)
- [Proxy Configuration Examples](#proxy-configuration-examples)
- [Creating Local Tools](#creating-local-mcp-tools)
- [MCP Resources](#mcp-resources)
- [Progress Notifications](#progress-notifications)
- [Request Context & Headers](#request-context--headers)
- [Built-in Tools](#built-in-tool-categories)
- [Security Model](#security-model)
- [Environment Variables](#environment-variables)
- [Development](#development)
- [Troubleshooting](#troubleshooting)

## What's New in v0.10.4

### 🎯 Pydantic-Native Artifact Integration

**Enhanced type safety and better developer experience** with full pydantic integration from `chuk-artifacts` 0.10.1+ (includes Redis Cluster support):

**Updated Dependencies**:
- `chuk-artifacts` 0.10.1 - Redis Cluster support, enhanced VFS providers
- `chuk-sessions` 0.6.0 - Redis Cluster support with automatic detection

#### ✅ Type-Safe Artifact Metadata

All artifact operations now use pydantic models internally:

```python
from chuk_artifacts.models import ArtifactMetadata

# Metadata is now a pydantic model with full validation
metadata: ArtifactMetadata = await store.metadata(artifact_id)

# Direct attribute access (type-safe)
print(f"Size: {metadata.bytes} bytes")
print(f"Type: {metadata.mime}")
print(f"Scope: {metadata.scope}")  # session | user | sandbox

# Pydantic serialization
metadata_dict = metadata.model_dump()  # Convert to dict
metadata_json = metadata.model_json()  # Convert to JSON
```

#### 🔧 What Changed

- **Internal improvements**: All artifact tools use pydantic models internally
- **Better performance**: Direct attribute access instead of dict lookups
- **Enhanced validation**: Automatic pydantic validation on all metadata
- **Zero breaking changes**: All existing code works unchanged (backward compatible)

#### 📊 Benefits

- ✅ **Type safety**: Full type hints with pydantic models
- ✅ **Better IDE support**: Autocomplete for all metadata fields
- ✅ **Automatic validation**: Pydantic ensures data integrity
- ✅ **Cleaner code**: Direct attribute access (`metadata.bytes` vs `metadata.get("bytes", 0)`)
- ✅ **100% backward compatible**: Dict-style access still works

#### 🔄 Compatibility

```python
# Both work - choose your style
size = metadata.bytes              # ✅ Pydantic (new, recommended)
size = metadata.get("bytes", 0)    # ✅ Dict-style (still works)
size = metadata["bytes"]           # ✅ Also works
```

## What's New in v0.9.0

### 🎉 Storage Scopes - The Game Changer

Three storage scopes for different use cases:

| Scope | Lifecycle | Use Case | Example |
|-------|-----------|----------|---------|
| **session** | Ephemeral (15min-24h) | Temporary work, caches | AI-generated code during chat |
| **user** | Persistent (1 year+) | User documents, saved files | Reports, custom prompts, uploads |
| **sandbox** | Shared (no expiry) | Templates, system files | Boilerplate, shared resources |

### 🔒 Security Enhancements

- ✅ Removed `session_id`/`user_id` parameters from all tools (prevents client impersonation)
- ✅ All identity from server-side context only
- ✅ Automatic scope-based access control in `read_file` and `delete_file`
- ✅ User files require authentication

### 🛠️ New Tools

**Explicit session tools** (ephemeral):
- `write_session_file` / `upload_session_file` - Always ephemeral
- `list_session_files` - List session files

**Explicit user tools** (persistent):
- `write_user_file` / `upload_user_file` - Always persistent
- `list_user_files` - Search/filter user's files

**General tools** (scope parameter):
- `write_file(scope="user")` / `upload_file(scope="user")` - Flexible scope selection

### ✅ 100% Backward Compatible

Existing code works unchanged - tools default to `scope="session"` (same behavior as v0.8.2).

**Quick comparison:**

```python
# v0.8.2 (still works in v0.9.0)
await write_file(content, filename)  # Ephemeral

# v0.9.0 - New capabilities
await write_user_file(content, filename)              # Persistent!
await write_file(content, filename, scope="user")     # Also persistent
files = await list_user_files(mime_prefix="text/*")   # Search user files
```

**See:** `CHANGELOG_V09.md` for complete release notes, `ARTIFACTS_V08_SUMMARY.md` for detailed guide.

## Redis Cluster Support (NEW in v0.10.4)

CHUK MCP Runtime now supports **Redis Cluster** for high availability and horizontal scaling through automatic cluster detection in `chuk-sessions` 0.6.0+ and `chuk-artifacts` 0.10.1+.

### Quick Start

**Standalone Redis** (existing):
```bash
export REDIS_URL=redis://localhost:6379
export ARTIFACT_SESSION_PROVIDER=redis
```

**Redis Cluster** (new):
```bash
# Comma-separated node list - automatic cluster detection
export REDIS_URL=redis://node1:7000,node2:7001,node3:7002
export ARTIFACT_SESSION_PROVIDER=redis
```

**With TLS**:
```bash
# Standalone with TLS
export REDIS_URL=rediss://secure-redis:6380
export REDIS_TLS_INSECURE=1  # For self-signed certificates

# Cluster with TLS
export REDIS_URL=rediss://node1:7000,node2:7001,node3:7002
export REDIS_TLS_INSECURE=1
```

### Environment Isolation

Prevent key collisions when multiple environments share the same Redis cluster:

```bash
# Development
export ENVIRONMENT=dev
export DEPLOYMENT_ID=local

# Staging
export ENVIRONMENT=staging
export DEPLOYMENT_ID=us-west-1
export SANDBOX_REGISTRY_TTL=3600  # 1 hour

# Production
export ENVIRONMENT=production
export DEPLOYMENT_ID=us-east-1
export SANDBOX_REGISTRY_TTL=86400  # 24 hours
```

This creates isolated namespaces:
- Dev: `dev:local:sbx:*`
- Staging: `staging:us-west-1:sbx:*`
- Production: `production:us-east-1:sbx:*`

### Configuration Variables

| Variable | Description | Default | Example |
|----------|-------------|---------|---------|
| `REDIS_URL` | Redis connection URL (standalone or cluster) | `redis://localhost:6379` | `redis://n1:7000,n2:7001` |
| `REDIS_TLS_INSECURE` | Disable SSL certificate verification | `0` | `1` |
| `ENVIRONMENT` | Environment name for namespace isolation | `dev` | `production`, `staging` |
| `DEPLOYMENT_ID` | Deployment identifier for namespace isolation | `default` | `us-east-1`, `us-west-1` |
| `SANDBOX_REGISTRY_TTL` | Sandbox registry entry TTL in seconds | `86400` | `3600`, `7200` |

### Architecture Notes

**Automatic Detection:**
- Single host URL → Uses `redis.asyncio.Redis`
- Multi-host URL (comma-separated) → Uses `redis.asyncio.cluster.RedisCluster`
- Database selector (`/0`) is auto-removed for cluster compatibility

**Thread Safety:**
- All singletons use double-check locking with `asyncio.Lock`
- Safe for concurrent initialization in multi-instance deployments

**Namespace Isolation:**
- Keys are prefixed with `{ENVIRONMENT}:{DEPLOYMENT_ID}:sbx:`
- Prevents collisions when multiple environments share Redis
- Required for staging/production on same cluster

## Core Components Overview

| Component | Purpose |
|-----------|---------|
| **Proxy Manager** | Connects and namespaces multiple MCP servers |
| **Session Manager** | Maintains per-user state across tool calls |
| **Artifact Store** | Handles file persistence with 3 scopes (NEW: user, sandbox) |
| **Auth & Security** | Adds JWT validation, sandboxing, and access control |
| **Progress Engine** | Sends real-time status updates to clients |

## Key Concepts

### Sessions

**Sessions** provide stateful context for multi-turn interactions with MCP tools. Each session:

- Has a unique identifier (session ID)
- Persists across multiple tool calls
- Can store metadata (user info, preferences, etc.)
- Controls access to artifacts (files) within the session scope
- Has an optional TTL (time-to-live) for automatic cleanup

**When to use sessions:**
- Multi-step workflows that need to maintain state
- User-specific file storage (isolate files per user)
- Long-running operations that span multiple requests
- Workflows requiring authentication/authorization context

**Example:**
```python
# Session-aware tool automatically gets current session context
@mcp_tool(name="save_user_file")
async def save_user_file(filename: str, content: str) -> str:
    # Files are automatically scoped to the current session
    # User A's "data.txt" is separate from User B's "data.txt"

    # Note: artifact_store is available via runtime context when artifacts are enabled
    from chuk_mcp_runtime.tools.artifacts_tools import artifact_store
    await artifact_store.write_file(filename, content)
    return f"Saved {filename} to session"
```

### Sandboxes

**Sandboxes** are isolated execution environments that contain one or more sessions. Think of them as:

- **Namespace** - Groups related sessions together
- **Deployment unit** - One sandbox per deployment/pod/instance
- **Isolation boundary** - Sessions in different sandboxes don't interact

**Sandbox ID** is set via:
1. Config file: `sessions.sandbox_id: "my-app"`
2. Environment variable: `MCP_SANDBOX_ID=my-app`
3. Auto-detected: Pod name in Kubernetes (`POD_NAME`)

**Use cases:**
```
Single-tenant app:     sandbox_id = "myapp"
Multi-tenant SaaS:     sandbox_id = "tenant-{customer_id}"
Development/staging:   sandbox_id = "dev-alice" | "staging"
Kubernetes pod:        sandbox_id = $POD_NAME (auto)
```

### Sessions vs Sandboxes

```
Sandbox: "production-app"
├── Session: user-alice-2024
│   ├── File: report.pdf
│   └── File: data.csv
├── Session: user-bob-2024
│   └── File: notes.txt
└── Session: background-job-123
    └── File: results.json

Different Sandbox: "staging-app"
└── (completely isolated from production)
```

### Artifacts (NEW in v0.9: Storage Scopes)

**Artifacts** are files managed by the runtime with **three storage scopes** for different use cases:

#### Storage Scopes

| Scope | Lifecycle | TTL | Use Case | Access Control |
|-------|-----------|-----|----------|----------------|
| **session** | Ephemeral | 15min-24h | Temporary work, caches, generated code | Session-isolated |
| **user** | Persistent | 1 year+ | User documents, saved files, custom prompts | User-owned |
| **sandbox** | Shared | No expiry | Templates, shared resources, system files | Read-only (admin writes) |

**Key Features:**
- **Session isolation** - Files scoped to specific sessions or users
- **Storage backends** - Filesystem, S3, IBM Cloud Object Storage, VFS providers
- **Metadata tracking** - Size, timestamps, content type, ownership
- **Lifecycle management** - Auto-cleanup with TTL expiry
- **Security** - No client-side identity parameters, server context only
- **Search & filtering** - Find files by user, scope, MIME type, metadata

**Storage providers:**
- `vfs-filesystem` - Local disk with VFS support (development)
- `vfs-s3` - AWS S3 with streaming + multipart uploads (distributed/cloud)
- `vfs-sqlite` - SQLite with structured queries (embedded)
- `memory` - In-memory (testing, ephemeral)

### Progress Notifications

**Progress notifications** enable real-time feedback for long-running operations:

- Client provides `progressToken` in request
- Tool calls `send_progress(current, total, message)`
- Runtime sends `notifications/progress` to client
- Client displays progress bar/status

**Perfect for:**
- File processing (10 of 50 files)
- API calls (fetching data batches)
- Multi-step workflows (step 3 of 5)
- Long computations (75% complete)

## Configuration Reference

Complete YAML configuration structure with all available options:

```yaml
# ============================================
# HOST CONFIGURATION
# ============================================
host:
  name: "my-mcp-server"           # Server name (for logging/identification)
  log_level: "INFO"                # Global log level: DEBUG, INFO, WARNING, ERROR

# ============================================
# SERVER TRANSPORT
# ============================================
server:
  type: "stdio"                    # Transport: stdio | sse | streamable-http
  auth: "bearer"                   # Optional: bearer (JWT) | none

# SSE-specific settings (when type: "sse")
sse:
  host: "0.0.0.0"                  # Listen address
  port: 8000                       # Listen port
  sse_path: "/sse"                 # SSE endpoint path
  message_path: "/messages/"       # Message submission path
  health_path: "/health"           # Health check path

# HTTP-specific settings (when type: "streamable-http")
streamable-http:
  host: "127.0.0.1"                # Listen address
  port: 3000                       # Listen port
  mcp_path: "/mcp"                 # MCP endpoint path
  json_response: true              # Enable JSON responses
  stateless: true                  # Stateless mode

# ============================================
# LOGGING CONFIGURATION
# ============================================
logging:
  level: "INFO"                    # Default log level
  format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
  reset_handlers: true             # Reset existing handlers
  quiet_libraries: true            # Suppress noisy library logs

  # Per-logger overrides
  loggers:
    "chuk_mcp_runtime.proxy": "DEBUG"
    "chuk_mcp_runtime.tools": "INFO"

# ============================================
# TOOL CONFIGURATION
# ============================================
tools:
  registry_module: "chuk_mcp_runtime.common.mcp_tool_decorator"
  registry_attr: "TOOLS_REGISTRY"
  timeout: 60                      # Global tool timeout (seconds)

# ============================================
# SESSION MANAGEMENT
# ============================================
sessions:
  sandbox_id: "my-app"             # Sandbox identifier (deployment unit)
  default_ttl_hours: 24            # Session time-to-live

# Session tools (disabled by default)
session_tools:
  enabled: false                   # Master switch for session tools
  tools:
    get_current_session: {enabled: false}
    set_session: {enabled: false}
    clear_session: {enabled: false}
    list_sessions: {enabled: false}
    get_session_info: {enabled: false}
    create_session: {enabled: false}

# ============================================
# ARTIFACT STORAGE
# ============================================
artifacts:
  enabled: false                   # Master switch for artifacts
  storage_provider: "filesystem"   # filesystem | s3 | ibm_cos
  session_provider: "memory"       # memory | redis
  bucket: "my-artifacts"           # Storage bucket/directory name

  # Artifact tools (disabled by default)
  tools:
    upload_file: {enabled: false}
    write_file: {enabled: false}
    read_file: {enabled: false}
    list_session_files: {enabled: false}
    delete_file: {enabled: false}
    list_directory: {enabled: false}
    copy_file: {enabled: false}
    move_file: {enabled: false}
    get_file_metadata: {enabled: false}
    get_presigned_url: {enabled: false}
    get_storage_stats: {enabled: false}

# ============================================
# PROXY CONFIGURATION
# ============================================
proxy:
  enabled: false                   # Enable proxy mode
  namespace: "proxy"               # Tool name prefix (e.g., "proxy.time.get_time")
  keep_root_aliases: false         # Keep original tool names
  openai_compatible: false         # Use underscores (time_get_time)
  only_openai_tools: false         # Register only underscore versions

# ============================================
# MCP SERVERS (Local & Remote)
# ============================================
mcp_servers:
  # Local Python tools
  my_tools:
    enabled: true
    location: "./my_tools"         # Directory containing tool modules
    tools:
      enabled: true
      module: "my_tools.tools"     # Python module path

  # Remote stdio server
  time:
    enabled: true
    type: "stdio"
    command: "uvx"
    args: ["mcp-server-time", "--local-timezone", "America/New_York"]
    cwd: "/optional/working/dir"   # Optional working directory

  # Remote SSE server
  weather:
    enabled: true
    type: "sse"
    url: "https://api.example.com/mcp"
    api_key: "your-api-key"        # Or set via API_KEY env var
```

### Configuration Priority

Settings are resolved in this order (highest to lowest):

1. **Command-line arguments** - `chuk-mcp-server --config custom.yaml`
2. **Environment variables** - `MCP_TOOL_TIMEOUT=120`
3. **Configuration file** - Values from YAML
4. **Default values** - Built-in defaults

### Minimal Configurations

**Stdio server with no sessions:**
```yaml
server:
  type: "stdio"
```

**SSE server (referenced in examples):**
```yaml
# sse_config.yaml
server:
  type: "sse"
  # For deployment: add auth: "bearer" and set JWT_SECRET_KEY

sse:
  host: "0.0.0.0"
  port: 8000
  sse_path: "/sse"
  message_path: "/messages/"
  health_path: "/health"
```

**Streamable HTTP server (referenced in examples):**
```yaml
# http_config.yaml
server:
  type: "streamable-http"
  # For deployment: add auth: "bearer" and set JWT_SECRET_KEY

streamable-http:
  host: "0.0.0.0"
  port: 3000
  mcp_path: "/mcp"
  json_response: true
  stateless: true
```

**Proxy only (no local tools):**
```yaml
proxy:
  enabled: true

mcp_servers:
  time:
    type: "stdio"
    command: "uvx"
    args: ["mcp-server-time"]
```

**Full-featured with sessions:**
```yaml
server:
  type: "stdio"

sessions:
  sandbox_id: "prod"

session_tools:
  enabled: true
  tools:
    get_current_session: {enabled: true}
    create_session: {enabled: true}

artifacts:
  enabled: true
  storage_provider: "s3"
  tools:
    write_file: {enabled: true}
    read_file: {enabled: true}
```

## Proxy Configuration Examples

The proxy layer allows you to expose tools from multiple MCP servers through a unified interface.

### Simple Command Line Proxy

```bash
# Basic proxy with dot notation (proxy.time.get_current_time)
chuk-mcp-proxy --stdio time --command uvx -- mcp-server-time --local-timezone America/New_York

# Multiple stdio servers (--stdio is repeatable)
chuk-mcp-proxy --stdio time --command uvx -- mcp-server-time \
               --stdio weather --command uvx -- mcp-server-weather

# Multiple SSE servers (--sse is repeatable)
chuk-mcp-proxy \
  --sse analytics --url https://example.com/mcp --api-key "$API_KEY" \
  --sse metrics   --url https://metrics.example.com/mcp --api-key "$METRICS_API_KEY"

# OpenAI-compatible with underscore notation (time_get_current_time)
chuk-mcp-proxy --stdio time --command uvx -- mcp-server-time --openai-compatible

# Streamable HTTP server (serves MCP over HTTP)
chuk-mcp-server --config http_config.yaml  # See minimal config example below
```

> ⚠️ **Security:** For SSE/HTTP network transports, enable `server.auth: bearer` and set `JWT_SECRET_KEY`.

### Multiple Servers with Config File

```yaml
# proxy_config.yaml
proxy:
  enabled: true
  namespace: "proxy"

mcp_servers:
  time:
    type: "stdio"
    command: "uvx"
    args: ["mcp-server-time", "--local-timezone", "America/New_York"]

  weather:
    type: "stdio"
    command: "uvx"
    args: ["mcp-server-weather"]
```

```bash
chuk-mcp-proxy --config proxy_config.yaml
```

### OpenAI-Compatible Mode

```yaml
# openai_config.yaml
proxy:
  enabled: true
  namespace: "proxy"
  openai_compatible: true   # Enable underscore notation
  only_openai_tools: true   # Only register underscore-notation tools

mcp_servers:
  time:
    type: "stdio"
    command: "uvx"
    args: ["mcp-server-time"]
```

```bash
chuk-mcp-proxy --config openai_config.yaml
```

**OpenAI-Compatible Naming Matrix:**

| Setting | Example Exposed Name |
|---------|---------------------|
| Default (dot notation) | `proxy.time.get_current_time` |
| `openai_compatible: true` | `time_get_current_time` |
| `openai_compatible: true` + `only_openai_tools: true` | Only underscore versions registered |

> **OpenAI-compatible mode** converts dots to underscores (e.g., `proxy.time.get_current_time` → `time_get_current_time`). Namespacing behavior is controlled by `openai_compatible` + `only_openai_tools`.

**OpenAI-compatible demo with HTTP:**

```bash
# Start proxy with OpenAI-compatible naming
chuk-mcp-proxy --stdio time --command uvx -- mcp-server-time --openai-compatible

# Call the underscore tool name over HTTP
curl -s http://127.0.0.1:3000/mcp \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $JWT" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"time_get_current_time","arguments":{"timezone":"UTC"}}}'
```

### Name Aliasing in Proxy Mode

By default, tools are exposed under `proxy.<server>.<tool>`.
Set `keep_root_aliases: true` to also expose the original tool names (no `proxy.` prefix).
This is useful when migrating existing clients gradually. **Root aliases are great for gradual migration, but disable in multi-tenant prod to avoid collisions.**

```yaml
proxy:
  enabled: true
  namespace: "proxy"
  keep_root_aliases: true  # Also expose tools without proxy. prefix
```

With this setting enabled, `proxy.time.get_current_time` is available as both:
- `proxy.time.get_current_time` (namespaced)
- `get_current_time` (root alias)

### Tool Naming Interplay

**Complete naming matrix when options combine:**

| Setting Combination | Registered Names |
|---------------------|------------------|
| Default | `proxy.<server>.<tool>` |
| `keep_root_aliases: true` | `proxy.<server>.<tool>`, **and** `<tool>` |
| `openai_compatible: true` | `<server>_<tool>` |
| `openai_compatible: true` + `only_openai_tools: true` | `<server>_<tool>` **only** |
| `openai_compatible: true` + `keep_root_aliases: true` | `<server>_<tool>`, **and** `<tool>` |

> ⚠️ **Root aliases are un-namespaced.** Use with care in multi-server setups to avoid tool name collisions.

## Security Model

**IMPORTANT**: CHUK MCP Runtime follows a **secure-by-default** approach:

- **All built-in tools are disabled by default**
- Session management tools require explicit enablement
- Artifact storage tools require explicit enablement
- Tools must be individually enabled in configuration
- This prevents unexpected tool exposure and reduces attack surface

## Creating Local MCP Tools

### 1. Create a custom tool

```python
# my_tools/tools.py
from chuk_mcp_runtime.common.mcp_tool_decorator import mcp_tool

@mcp_tool(name="get_current_time", description="Get the current time in a timezone")
async def get_current_time(timezone: str = "UTC") -> str:
    """
    Get the current time in the specified timezone.

    Args:
        timezone: Target timezone (e.g., 'UTC', 'America/New_York')
    """
    from datetime import datetime
    from zoneinfo import ZoneInfo

    tz = ZoneInfo(timezone)
    now = datetime.now(tz)
    return now.strftime("%Y-%m-%d %H:%M:%S %Z")

@mcp_tool(name="calculate_sum", description="Calculate the sum of two numbers", timeout=10)
async def calculate_sum(a: int, b: int) -> dict:
    """
    Calculate the sum of two numbers.

    Args:
        a: First number
        b: Second number
    """
    # ⚠️ PRODUCTION WARNING: Never use eval() for math operations - always validate
    # and compute directly as shown here. eval() is a security risk.
    result = a + b
    return {
        "operation": "addition",
        "operands": [a, b],
        "result": result
    }
```

### 2. Create a config file

```yaml
# config.yaml
host:
  name: "my-mcp-server"
  log_level: "INFO"

server:
  type: "stdio"

# Global tool settings
tools:
  registry_module: "chuk_mcp_runtime.common.mcp_tool_decorator"
  registry_attr: "TOOLS_REGISTRY"
  timeout: 60  # Default timeout for all tools

# Session management (optional - disabled by default)
sessions:
  sandbox_id: "my-app"
  default_ttl_hours: 24

# Session tools (disabled by default - must enable explicitly)
session_tools:
  enabled: true  # Must explicitly enable
  tools:
    get_current_session: {enabled: true}
    set_session: {enabled: true}
    clear_session: {enabled: true}
    create_session: {enabled: true}

# Artifact storage (disabled by default - must enable explicitly)
artifacts:
  enabled: true  # Must explicitly enable
  storage_provider: "filesystem"
  session_provider: "memory"
  bucket: "my-artifacts"
  tools:
    upload_file: {enabled: true}
    write_file: {enabled: true}
    read_file: {enabled: true}
    list_session_files: {enabled: true}
    delete_file: {enabled: true}
    get_file_metadata: {enabled: true}

# Local tool modules
mcp_servers:
  my_tools:
    enabled: true
    location: "./my_tools"
    tools:
      enabled: true
      module: "my_tools.tools"
```

### 3. Run the server

```bash
chuk-mcp-server --config config.yaml
```

## MCP Resources

**MCP Resources** provide read-only access to data through the Model Context Protocol's `resources/list` and `resources/read` endpoints. Resources are perfect for exposing configuration, documentation, system information, and user files to AI agents.

### Resources vs Tools

| Feature | **Resources** | **Tools** |
|---------|--------------|-----------|
| **Purpose** | Read-only data access | Actions & state changes |
| **Use Cases** | Config, docs, files, metrics | Create, update, delete operations |
| **MCP Methods** | `resources/list`, `resources/read` | `tools/list`, `tools/call` |
| **Side Effects** | None (read-only) | May modify state |
| **Session Isolation** | Artifact resources only | Tool-dependent |

### Resource Types

CHUK MCP Runtime supports two types of resources:

#### 1. Custom Resources (@mcp_resource)

Custom resources expose application data, configuration, documentation, or any read-only content through simple Python functions.

**Creating custom resources:**

```python
# my_resources/resources.py
from chuk_mcp_runtime.common.mcp_resource_decorator import mcp_resource
import json
import os

@mcp_resource(
    uri="config://database",
    name="Database Configuration",
    description="Database connection settings",
    mime_type="application/json"
)
async def get_database_config():
    """Return database configuration as JSON."""
    config = {
        "host": "localhost",
        "port": 5432,
        "database": "myapp_db",
        "pool_size": 10
    }
    return json.dumps(config, indent=2)

@mcp_resource(
    uri="system://info",
    name="System Information",
    description="Current system status",
    mime_type="text/plain"
)
async def get_system_info():
    """Return system information."""
    return f"""System Information
Platform: {os.uname().sysname}
Node: {os.uname().nodename}
User: {os.getenv('USER', 'unknown')}
"""

@mcp_resource(
    uri="docs://api/overview",
    name="API Documentation",
    description="API endpoints guide",
    mime_type="text/markdown"
)
def get_api_docs():
    """Return API documentation (sync functions work too!)."""
    return """# API Documentation

## Authentication
All requests require Bearer token.

## Endpoints
- GET /api/users - List users
- POST /api/users - Create user
"""
```

**Configuration:**

```yaml
# config.yaml
server:
  type: "stdio"

# Import module containing custom resources
tools:
  modules_to_import:
    - my_resources.resources
```

**Custom resource features:**
- **Static or dynamic** - Return fixed data or compute on-demand
- **Sync or async** - Both function types supported
- **Any content type** - Text, JSON, binary, images, etc.
- **Custom URI schemes** - Use meaningful URIs like `config://`, `docs://`, `system://`

#### 2. Artifact Resources (Session-Isolated User Files)

Artifact resources provide **automatic, session-isolated access to user files** through the MCP resources protocol. When users create, upload, or modify files via artifact tools, those files are automatically exposed as resources with strong session isolation guarantees.

**Key Concepts:**

- **Automatic Exposure**: Files created via `write_file`, `upload_file`, etc. are automatically available via `resources/list` and `resources/read`
- **Session Isolation**: Users can only list and read their own files - cross-session access is blocked
- **Unified Protocol**: Access files through the same MCP resources protocol as custom resources
- **URI Format**: `artifact://{artifact_id}` where `artifact_id` is the unique file identifier

**How Artifact Resources Work:**

```python
# Step 1: User creates a file via an artifact tool
# This happens via MCP tool call: tools/call with name="write_file"

# Example tool call from AI agent:
{
  "method": "tools/call",
  "params": {
    "name": "write_file",
    "arguments": {
      "filename": "analysis.md",
      "content": "# Data Analysis\n\nKey findings...",
      "mime": "text/markdown",
      "summary": "Q3 analysis report"
    }
  }
}

# Step 2: File is stored with session association
# - artifact_id: "abc-123-def-456"
# - session_id: "session-alice"
# - filename: "analysis.md"
# - mime: "text/markdown"

# Step 3: File automatically appears in resources/list
{
  "method": "resources/list"
}
# Returns:
{
  "resources": [
    {
      "uri": "artifact://abc-123-def-456",
      "name": "analysis.md",
      "description": "Q3 analysis report",
      "mimeType": "text/markdown"
    }
  ]
}

# Step 4: Read the resource content
{
  "method": "resources/read",
  "params": {"uri": "artifact://abc-123-def-456"}
}
# Returns:
{
  "contents": [
    {
      "uri": "artifact://abc-123-def-456",
      "mimeType": "text/markdown",
      "text": "# Data Analysis\n\nKey findings..."
    }
  ]
}
```

**Session Isolation Example:**

```python
# Alice's session (session-alice)
# Creates: report.md -> artifact://file-alice-1

# Bob's session (session-bob)
# Creates: report.md -> artifact://file-bob-1

# When Alice calls resources/list:
# Returns ONLY: artifact://file-alice-1

# When Bob calls resources/list:
# Returns ONLY: artifact://file-bob-1

# If Alice tries to read Bob's file:
# resources/read {"uri": "artifact://file-bob-1"}
# Result: Error - Artifact not found (access blocked)
```

**Configuration:**

```yaml
# config.yaml
artifacts:
  enabled: true
  storage_provider: "filesystem"  # or "s3", "ibm_cos"
  session_provider: "memory"      # or "redis"

  # Storage configuration (for filesystem provider)
  filesystem:
    base_path: "./artifacts"

  # Enable artifact tools (users create files via these tools)
  tools:
    write_file: {enabled: true}         # Create/update text files
    upload_file: {enabled: true}        # Upload binary files
    read_file: {enabled: true}          # Read file content
    list_session_files: {enabled: true} # List user's files
    delete_file: {enabled: true}        # Delete files
```

**Supported File Operations:**

| Tool | Purpose | Creates Resource? |
|------|---------|-------------------|
| `write_file` | Create or update text file | ✅ Yes |
| `upload_file` | Upload binary file (images, PDFs, etc.) | ✅ Yes |
| `read_file` | Read file content by filename | No (uses resource instead) |
| `list_session_files` | List user's files | No (use `resources/list`) |
| `delete_file` | Delete a file | Removes resource |

**Text vs Binary Content:**

```python
# Text files (JSON, Markdown, code, etc.)
# Returned as "text" in resource content
{
  "uri": "artifact://text-123",
  "mimeType": "application/json",
  "text": '{"key": "value"}'
}

# Binary files (images, PDFs, etc.)
# Returned as base64-encoded "blob"
{
  "uri": "artifact://binary-456",
  "mimeType": "image/png",
  "blob": "iVBORw0KGgoAAAANSUhEUgAA..."  # base64 encoded
}
```

**Artifact Resource Metadata:**

Each artifact resource includes:
- **URI**: `artifact://{artifact_id}` - Unique resource identifier
- **Name**: Original filename (e.g., "report.pdf")
- **Description**: Summary/description provided during creation
- **MIME Type**: Content type (e.g., "application/pdf", "text/markdown")
- **Session ID**: Internal - used for access control (not exposed)

**Integration with Custom Resources:**

Artifact resources and custom resources work together seamlessly:

```python
# Both appear in the same resources/list response:
{
  "resources": [
    # Custom resources (global)
    {"uri": "config://database", "name": "Database Config", ...},
    {"uri": "docs://api", "name": "API Documentation", ...},

    # Artifact resources (session-isolated)
    {"uri": "artifact://abc-123", "name": "user-report.md", ...},
    {"uri": "artifact://def-456", "name": "analysis.pdf", ...}
  ]
}

# AI agents can access both types through the same protocol
```

**Security & Access Control:**

Artifact resources have **multi-layer security**:

1. **Session Validation**: Every `resources/read` call validates session ownership
2. **Metadata Verification**: Artifact metadata must match current session
3. **Access Blocking**: Cross-session reads return "not found" error
4. **Audit Trail**: All access attempts can be logged for compliance

**Common Use Cases:**

- **Document Generation**: AI creates reports, summaries, code files
- **Data Analysis**: AI processes data and saves results as artifacts
- **File Management**: Users upload files, AI analyzes and references them
- **Multi-step Workflows**: AI saves intermediate results as artifacts
- **Context Persistence**: Files remain accessible across conversation turns

**Artifact Resource Features:**
- ✅ **Session isolation** - Users only see their own files
- ✅ **Automatic exposure** - Files created via tools become resources immediately
- ✅ **URI scheme** - Consistent `artifact://{id}` format
- ✅ **Security** - Built-in access control and validation
- ✅ **Persistence** - Files survive server restarts (with filesystem/cloud storage)
- ✅ **Binary support** - Images, PDFs, archives via base64 encoding
- ✅ **Metadata** - Filenames, MIME types, descriptions included

### Resource URI Schemes

| URI Scheme | Type | Example | Use Case |
|------------|------|---------|----------|
| `config://` | Custom | `config://database` | Configuration data |
| `system://` | Custom | `system://info` | System information |
| `docs://` | Custom | `docs://api/overview` | Documentation |
| `data://` | Custom | `data://logo.png` | Static assets |
| `artifact://` | Artifact | `artifact://abc-123-def` | User files |

### Using Resources in AI Agents

Resources are designed for AI agents to retrieve contextual data:

```python
# AI agent workflow:
# 1. List available resources
response = await client.call("resources/list")
# Returns: config://database, system://info, artifact://report-123

# 2. Read specific resource
content = await client.call("resources/read", {"uri": "c