Skip to main content

SMF - Enterprise MCP Framework

SMF is a production-ready Python framework built on top of FastMCP 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

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

Simple Server

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

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

# 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:

from smf import ServerFactory

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

AppBuilder

Fluent interface for registering tools:

from smf import AppBuilder

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

Transport

Run servers with different transport mechanisms:

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):

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:

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:

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:

# 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

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

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

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

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:

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:

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

# 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

# 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

# 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

# Build package
uv build

# Check package
twine check dist/*

Requirements

  • Python 3.11+
  • FastMCP >= 2.11

License

MIT

Credits

Built on FastMCP by Prefect.

Metadata

Release files for smf-mcp 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for smf-mcp 0.2.1
File Size Uploaded Provenance
smf_mcp-0.2.1.tar.gz 46.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for smf-mcp 0.2.1
File Interpreter ABI Platform
smf_mcp-0.2.1-py3-none-any.whl Python 3 none any Details

Total release size: 109.9 kB

Release files / smf_mcp-0.2.1.tar.gz

Download URL smf_mcp-0.2.1.tar.gz
Size 46.2 kB
Tags Source
SHA-256 checksum
How to use checksums
6284c6f19f975e82aa753b6192d040c28a4fe466d8937dabbe371d87b23f3cbf
BLAKE2b-256 checksum
How to use checksums
a59c6894dee1b5bfb110b4f37b1a56a0626979f60a3a38d598812261bab1bf2f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / smf_mcp-0.2.1-py3-none-any.whl

Download URL smf_mcp-0.2.1-py3-none-any.whl
Size 63.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
4f09b66f405efcec887ccc91eaf4adc2e03cabf9b33b6bef57a42eb77608c15c
BLAKE2b-256 checksum
How to use checksums
39885f546b8063632566a6db038c640fe286babfa5d50c48c4c77ded4204364e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for smf-mcp 0.2.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page