Service MCP
Service MCP — Generic FastMCP + SQLAlchemy CRUD Template
A production-ready, generic FastMCP server template with SQLAlchemy async CRUD, built by distilling a real-world fund NAV MCP service into a reusable skeleton. Use it to bootstrap new MCP data-management services.
Stack: Python 3.12+ · FastMCP 3.x · SQLAlchemy 2.x (async) · Pydantic v2 · Typer CLI · uv
Features
Registry-driven CRUD tools — add/update/delete (single + batch) are generated from a one-line entity registry; no per-entity boilerplate.
FK code auto-resolution — business codes (
product_code) are resolved to internal IDs by the handler layer; callers never see primary keys.Auto-placeholder creation — child records referencing a missing parent create a
abnormal=Placeholderstub parent automatically (e.g. price records for an unknown product).Orphan marking — deleting a parent marks dependent child rows
abnormal=Orphanedinstead of cascade-deleting; areview_abnormal_itemstool aggregates all pending-review rows.Composite-key delete — child records can be located by their natural compound key (
product_code + price_date).Dynamic Filter/Search —
Filter/SearchByKeyword/SearchByFieldsclasses are generated at import time from ORM introspection;.pyistubs are regenerated by a script for IDE support.Conflict versioning — same-day-same-source price conflicts bump a
versioncolumn and mark the row for human review.Multi-transport CLI —
stdio/sse/streamable-http/ui(FastMCP Apps dashboard).Layered config — env vars → TOML → code defaults (
MCP_prefix,MCP_ENVselects the TOML).Auth-ready — JWT middleware +
mcp_permpermission decorators with permission discovery.Docker deployment — compose stack (PostgreSQL 18 + Redis 8, optional pgAdmin) with lifecycle scripts (
ctl.sh/ctl.ps1).Extras — mock data generator, idempotent migration example, SQLite/MySQL/PostgreSQL/InfluxDB support, FastMCP Apps config UI.
Generic infrastructure — async per-key rate limiter with jitter (
utils/rate_limiter.py), large-result spill-to-file with startup sweep (utils/spill.py), cron expression parsing (utils/cron.py), background task manager + cron scheduler (task/), and a BOM-safe CSV export helper with demo toolexport_products_csv(utils/export.py+tools/export_tools.py).
Quick start
uv venv && uv sync --dev
# Run the server (pick one transport)
uv run service-mcp stdio
uv run service-mcp streamable-http --host 0.0.0.0 --port 8001
uv run service-mcp sse --host 0.0.0.0 --port 8001
uv run service-mcp ui --dev-port 8080 --mcp-port 8001On first start the server auto-creates configs/config.{MCP_ENV}.toml with a default in-memory
SQLite database + Redis cache config — zero configuration to get going.
Example entities
The template ships one minimal domain — Product + ProductPrice — implemented end-to-end to demonstrate every pattern you need to replicate for your own entities:
Entity | Demonstrates |
| unique business code, soft-delete flag, auto-placeholder creation (orphan parent) |
| FK code resolution, composite unique key ( |
Follow the chain: add_product → AddHandler → CodeResolveMixin._resolve_fk_codes →
ProductPrice rows auto-resolve product_code → product_id; delete_product marks all its
prices abnormal=Orphaned; add_product_price with a conflicting same-day-same-source value
bumps version and flags abnormal=PriceConflict.
Project structure
service_mcp/
├── server.py # FastMCP app + Typer CLI (stdio/sse/streamable-http/ui)
├── config.py # MCPSettings layered config (env → TOML → code)
├── apps/ # FastMCP Apps UI (config_app: DB/cache management dashboard)
├── auth/ # JWT middleware, mcp_perm decorator, permission/entity discovery
├── db/ # DBManager (async SQLAlchemy CRUD/paginate) + InfluxDBManager
├── handlers/ # CodeResolveMixin + Add/Update/Delete/Query handlers
├── models/
│ ├── orm/ # SQLAlchemy models (base.py audit columns, product.py example)
│ ├── pydantic/ # dynamic Filter/Search generators + per-entity request/response models
│ └── schemas.py # DB/cache config schemas + pagination
├── tools/ # crud_factory (registry-driven), query_tools, basic_tools, dict_tools
├── task/ # generic background task manager + cron scheduler (TaskManager/TaskScheduler)
└── utils/ # enums, logging, path helpers, rate_limiter, spill, cron, export (CSV)
configs/ # config.example.toml skeleton (per-env TOMLs are git-ignored)
docker/ # compose files, entrypoint, ctl.sh/ctl.ps1
mock/ # mock_product_data.py
scripts/ # rename_project.py, refresh_project_stub.py, migrate_example.py
tests/ # pytest suite (in-memory SQLite)Add a new entity
ORM: create
service_mcp/models/orm/<entity>.pysubclassingBase(audit columns are inherited); add a unique business code column with acomment(used by friendly duplicate messages) and anabnormal: AbnormalType | Nonecolumn for orphan marking. Export it inmodels/orm/__init__.py(importbasefirst).Pydantic: create
<Entity>Base/Create/Update/Delete(extendsBaseDeleteModel, requires at least one lookup field) /Responseinmodels/pydantic/<entity>.py; reuse the validator helpers inproduct_validators.pyas a template.Filter/Search: add
create_filter_class(...)/create_search_class(...)calls inmodels/pydantic/filter.py/search.py. If you override the generated class with an explicitclass, re-register it withregister_pyi_class(..., explicit=True).Regenerate stubs:
uv run python scripts/refresh_project_stub.py(run twice; the second run must produce no diff).Handlers: add registry rows —
_CODE_RESOLVE_MAP(FK codes),_NAME_RESOLVE_MAP(name fallback),_OWN_CODE_FIELDS(own unique codes),_AUTO_CREATE_MODELS(placeholder auto-creation),_DELETE_NAME_LOOKUP,_COMPOUND_TARGET_REGISTRY(compound delete keys),_ORPHAN_REGISTRY(children to mark on delete),FIELD_MAPPING_CONFIG(FK display fields for query results).Tools: add a row to
crud_factory._ENTITIES(gives you add/update/delete single+batch tools) and list/search tools inquery_tools.py.Enums: add
EntityType/AuthResourceentries and any domain enums inutils/enums.py.Mock/tests: add a TABLE_META row in
mock/mock_product_data.pyand seeded fixtures intests/conftest.py.
Rename the project (one command)
The template uses placeholder naming (service_mcp / service-mcp / "Service MCP"). To create a
new project from this template:
uv run python scripts/rename_project.py my_company \
--project my-company-mcp --display "My Company MCP" --db my_company_data
uv sync # regenerate uv.lock / reinstall
uv run pytest # confirm greenThe script rewrites all file contents and renames the package directory. Run with --dry-run to
preview. uv.lock is intentionally skipped — regenerate it with uv sync.
Docker deployment
cp docker/.env.example docker/.env # edit passwords/DB names
./docker/ctl.sh deploy -e prod # or: ctl.ps1 on WindowsInfra only (app runs locally):
cd docker && docker compose up -dServices: PostgreSQL 18 (5432), Redis 8 (6379), optional pgAdmin (5050).
Configuration reference
Env var | Meaning | Default |
| environment name; selects |
|
|
|
|
| default transport |
|
| HTTP transport bindings |
|
| enable Redis cache |
|
|
|
|
| per-database config (nested | — |
| logging config (console/file/JSON rotation) | — |
Testing & quality
pytest # all tests (in-memory SQLite, no external services)
ruff check . # lint
ruff format . # format
mypy service_mcp # type checkLicense
MIT