# SMF - Enterprise MCP Framework

**SMF** is a production-ready Python framework built on top of [FastMCP](https://fastmcp.wiki/) that makes it significantly simpler to create, structure, and deploy MCP (Model Context Protocol) servers.

## Features

- 🏗️ **High-level abstractions**: `ServerFactory` and `AppBuilder` for minimal boilerplate
- 🔧 **Tool registration**: Simple decorator-based tool registration
- 🔌 **Plugin system**: Extensible architecture with stable interfaces
- 🚀 **CLI & Templates**: Project scaffolding and code generation
- 📦 **Simple & Focused**: Tools-only approach, no unnecessary complexity

## Architecture

```
┌─────────────────────────────┐
│   CLI & Templates           │
├─────────────────────────────┤
│  Extensions & Plugins       │
├─────────────────────────────┤
│    Core SMF Layer          │
├─────────────────────────────┤
│    FastMCP (Upstream)        │
└─────────────────────────────┘
```

SMF wraps FastMCP without modifying it, ensuring compatibility with FastMCP updates while adding enterprise features.

## Quick Start

### Installation

```bash
uv add smf-mcp
# or
pip install smf-mcp
```

### Simple Server

```python
from smf import create_server

# Create server
mcp = create_server("My Server")

# Register a tool
@mcp.tool
def greet(name: str) -> str:
    """Greet someone by name."""
    return f"Hello, {name}!"

if __name__ == "__main__":
    from smf.transport import run_server
    run_server(mcp)
```

### Advanced Server with AppBuilder

```python
from smf import AppBuilder

# Use AppBuilder for fluent registration
with AppBuilder() as builder:
    @builder.tool(tags=["math"])
    def add(a: float, b: float) -> float:
        """Add two numbers."""
        return a + b

    mcp = builder.build()

if __name__ == "__main__":
    from smf.transport import run_server
    run_server(mcp, transport="http", port=8000)
```

## CLI

```bash
# Initialize new project
smf init my-server

# Initialize project with Elasticsearch plugin
smf init my-server --elasticsearch --es-index "products"

# Run server
smf run server.py --transport http --port 8000

# Inspect server (Official Web Inspector)
smf inspector server.py
```

## Core Components

### ServerFactory

Creates configured FastMCP servers:

```python
from smf import ServerFactory

factory = ServerFactory()
mcp = factory.create(name="My Server")
```

### AppBuilder

Fluent interface for registering tools:

```python
from smf import AppBuilder

builder = AppBuilder()
builder.tool(my_function)
mcp = builder.build()
```

### Transport

Run servers with different transport mechanisms:

```python
from smf.transport import run_server

# Stdio (default)
run_server(mcp)

# HTTP
run_server(mcp, transport="http", host="0.0.0.0", port=8000)

# SSE
run_server(mcp, transport="sse", host="0.0.0.0", port=8000)
```

### Authentication

SMF relies on FastMCP's auth system and adds a generic OIDC/OAuth proxy
provider that can be configured entirely via environment variables.

Enable auth (no code changes):

```bash
FASTMCP_SERVER_AUTH=smf.auth.providers.oidc.OIDCProxyProvider
FASTMCP_SERVER_AUTH_OIDC_CONFIG_URL=https://idp.example.com/.well-known/openid-configuration
FASTMCP_SERVER_AUTH_OIDC_CLIENT_ID=your-client-id
FASTMCP_SERVER_AUTH_OIDC_CLIENT_SECRET=your-client-secret
FASTMCP_SERVER_AUTH_OIDC_BASE_URL=https://your-mcp-server.example.com
```

If your IdP does not support OIDC discovery, you can provide explicit endpoints:

```bash
FASTMCP_SERVER_AUTH_OIDC_AUTHORIZATION_ENDPOINT=https://idp.example.com/oauth2/authorize
FASTMCP_SERVER_AUTH_OIDC_TOKEN_ENDPOINT=https://idp.example.com/oauth2/token
FASTMCP_SERVER_AUTH_OIDC_JWKS_URI=https://idp.example.com/.well-known/jwks.json
FASTMCP_SERVER_AUTH_OIDC_ISSUER=https://idp.example.com
```

Optional authorization helper for tools:

```python
from smf.auth import require_scopes

@mcp.tool
@require_scopes("read:data")
def read_data() -> str:
    return "ok"
```

## Plugins

### Elasticsearch Plugin

Create Elasticsearch-powered MCP servers:

```bash
# Create server with CLI
smf init my-server --elasticsearch --es-index "products"

# Or use in code
from smf.plugins.elasticsearch import (
    ElasticsearchConfiguration,
    build_elasticsearch_connection,
    create_elasticsearch_tools,
)

es_config = ElasticsearchConfiguration.from_env()
es_client = build_elasticsearch_connection(es_config)  # Returns native Elasticsearch client
mcp = create_server("My Server")
tools = create_elasticsearch_tools(es_client, index="products")
for tool in tools:
    mcp.tool(tool)
```

Install: Choose the version matching your Elasticsearch cluster:

- `pip install smf-mcp[elasticsearch7]` or `uv add smf-mcp[elasticsearch7]` for Elasticsearch 7.x
- `pip install smf-mcp[elasticsearch8]` or `uv add smf-mcp[elasticsearch8]` for Elasticsearch 8.x
- `pip install smf-mcp[elasticsearch9]` or `uv add smf-mcp[elasticsearch9]` for Elasticsearch 9.x

## Project Structure

When you initialize a new project with `smf init`, you get:

```
my-server/
├── src/
│   └── tools/
│       ├── __init__.py
│       └── tools.py          # Your tools
├── server.py                  # Main server file
└── README.md                  # Project documentation
```

## Elasticsearch Resilience Features

SMF includes production-ready resilience patterns for Elasticsearch:

### Retry with Exponential Backoff

```python
from smf.plugins.elasticsearch import RetryConfig, with_retry

@with_retry(RetryConfig(max_retries=5, initial_delay=0.1))
def search_documents(query: str) -> dict:
    return es_client.search(query=query)
```

### Circuit Breaker

```python
from smf.plugins.elasticsearch import with_circuit_breaker, CircuitBreakerConfig

config = CircuitBreakerConfig(failure_threshold=5, timeout=30.0)

@with_circuit_breaker("elasticsearch", config)
def search(query: str) -> dict:
    return es_client.search(query=query)
```

### Bulk Operations

```python
from smf.plugins.elasticsearch import bulk_index, BulkStats

def generate_documents():
    for i in range(10000):
        yield {"id": i, "text": f"Document {i}"}

stats: BulkStats = bulk_index(es_client, "my-index", generate_documents())
print(f"Indexed {stats.indexed}/{stats.total} documents")
```

### Pagination with search_after

```python
from smf.plugins.elasticsearch import search_after_iterator

query = {"match": {"status": "active"}}
sort = [{"created_at": "desc"}, {"_id": "asc"}]

for hit in search_after_iterator(es_client, "my-index", query, sort):
    print(hit["_source"]["title"])
```

## Error Handling

SMF provides a structured exception hierarchy:

```python
from smf import SMFError, ConfigurationError, PluginError
from smf.plugins.elasticsearch import CircuitBreakerOpenError

try:
    result = search_documents("test")
except CircuitBreakerOpenError as e:
    print(f"Circuit open, retry after {e.retry_after} seconds")
except SMFError as e:
    print(f"SMF error: {e.message} (code: {e.code})")
```

## Logging

SMF includes structured logging support:

```python
from smf import get_logger, set_log_context

logger = get_logger("my_module")

set_log_context(request_id="abc123", user="admin")
logger.info("Processing request", action="search", index="products")
```

## Development

### Setup

```bash
# Clone repository
git clone https://github.com/guinat/smf-mcp.git
cd smf-mcp

# Install with dev dependencies
uv sync --extra dev

# Install pre-commit hooks
uv run pre-commit install
```

### Running Tests

```bash
# Run all unit tests
uv run pytest tests/unit

# Run with coverage
uv run pytest tests/unit --cov=src/smf --cov-report=html

# Run specific test file
uv run pytest tests/unit/test_exceptions.py -v

# Run integration tests (requires Elasticsearch)
ELASTICSEARCH_HOSTS=http://localhost:9200 uv run pytest tests/integration -m integration
```

### Code Quality

```bash
# Run linter
uv run ruff check src tests

# Run formatter
uv run ruff format src tests

# Run type checker
uv run mypy src

# Run all checks (what CI runs)
uv run pre-commit run --all-files
```

### Building

```bash
# Build package
uv build

# Check package
twine check dist/*
```

## Requirements

- Python 3.11+
- FastMCP >= 2.11

## License

MIT

## Credits

Built on [FastMCP](https://fastmcp.wiki/) by Prefect.
