Enterprise-grade MCP framework built on FastMCP
Project description
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:
ServerFactoryandAppBuilderfor 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]oruv add smf-mcp[elasticsearch7]for Elasticsearch 7.xpip install smf-mcp[elasticsearch8]oruv add smf-mcp[elasticsearch8]for Elasticsearch 8.xpip install smf-mcp[elasticsearch9]oruv 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file smf_mcp-0.2.1.tar.gz.
File metadata
- Download URL: smf_mcp-0.2.1.tar.gz
- Upload date:
- Size: 46.2 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6284c6f19f975e82aa753b6192d040c28a4fe466d8937dabbe371d87b23f3cbf
|
|
| MD5 |
591283b998854d0bf263bb3d4a72ddb7
|
|
| BLAKE2b-256 |
a59c6894dee1b5bfb110b4f37b1a56a0626979f60a3a38d598812261bab1bf2f
|
File details
Details for the file smf_mcp-0.2.1-py3-none-any.whl.
File metadata
- Download URL: smf_mcp-0.2.1-py3-none-any.whl
- Upload date:
- Size: 63.8 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f09b66f405efcec887ccc91eaf4adc2e03cabf9b33b6bef57a42eb77608c15c
|
|
| MD5 |
398dc303b6d028aa4d14acaff0148d99
|
|
| BLAKE2b-256 |
39885f546b8063632566a6db038c640fe286babfa5d50c48c4c77ded4204364e
|