Minimal template for building Python MCP servers with the official mcp SDK (FastMCP) and drdroid-debug-toolkit. Use it to spin up a new MCP server per tool (Metabase, Signoz, Grafana, etc.) with minimal edits.
-
On GitHub: Open this repo → click Use this template → Create a new repository. Name it e.g.
metabase-mcp-serverorsignoz-mcp-server. Clone your new repo locally. -
Or clone this template and re-init as your own repo:
git clone <this-template-url> my-mcp-server && cd my-mcp-server rm -rf .git && git init && git add . && git commit -m "Initial commit from mcp-server-template"
- Rename the package folder:
src/mcp_template/→src/<name>_mcp_server/(e.g.src/metabase_mcp_server/). - In
pyproject.toml: setname = "metabase-mcp-server"(or your tool name), and setproject.scriptsto your package’sserver:main, e.g.metabase-mcp-server = "metabase_mcp_server.server:main". - In all Python files under
src/<name>_mcp_server/: replacemcp_templatewith your package name (e.g.metabase_mcp_server).
- In
pyproject.toml(orrequirements.txt), add:drdroid-debug-toolkit(e.g.git+https://github.com/DrDroidLab/drdroid-debug-toolkit.git@master)django(required by the toolkit at import time).
- Install:
uv syncorpip install -e ".[dev]".
- File:
src/<name>_mcp_server/connector.py - What to do: Uncomment the Metabase example block. For Metabase keep it as-is; for another source (e.g. Signoz) change
Source,SourceKeyType, and thekeysinbuild_*_connectorto match that source’s proto. EnsureMCP_CONNECTOR_NAMEandMCP_CONNECTOR_IDmatch what your manager will use.
- File:
src/<name>_mcp_server/example_source_provider.py - What to do: Uncomment the full example block.
- Manager: Point to your drd
SourceManager(e.g.MetabaseSourceManagerorSignozSourceManager) and yourbuild_*_connectorfromconnector.py. - Provider: In
super().__init__(...)set:Source.METABASE(orSource.SIGNOZ, etc.) to your drd source enum.prefix="metabase"(or"signoz", etc.) to your tool name; this becomes the MCP tool name prefix (e.g.metabase_list_databases).
- Manager: Point to your drd
- Rename the class if you like (e.g.
ExampleToolProvider→MetabaseToolProvider) and update imports inserver.py.
- File:
src/<name>_mcp_server/server.py - What to do:
- At the top (before other local imports), add the toolkit path setup so
coreis importable (see metabase-mcp-serverserver.pyfor the block that insertsdrdroid_debug_toolkitintosys.path). - Load backend config (URL, API key) from env or
config.py. - Create your provider and set it:
_provider = MetabaseToolProvider(url, api_key)(orset_tool_provider(...)), so it runs beforemain().
- At the top (before other local imports), add the toolkit path setup so
- Add backend URL and API key to
config.py(e.g.SERVICE_URL,SERVICE_API_KEY) or use env vars only. - Copy
.env.exampleto.envand set the variables. The server and tests read from the repo root.
From the repo root (where pyproject.toml and src/ are):
uv venv .venv && source .venv/bin/activate # or: python -m venv .venv && source .venv/bin/activate
uv sync # or: pip install -e ".[dev]"
uv run metabase-mcp-server # or your script name from pyproject.toml- Stdio (Cursor / Claude): run the above; set your MCP client command to
path/to/venv/bin/python -m metabase_mcp_server.server(or your package name) withcwd= repo root. - HTTP:
MCP_TRANSPORT=streamable-http uv run metabase-mcp-server(default port 8000; override withMCP_PORT).
If you see "No tool provider configured", finish steps 4–6 (connector, provider, and wiring in server.py).
- FastMCP server (
server.py): Exits with a clear message until a ToolProvider is set. Once set, exposesping,echo,list_tools, andexecute_tool(which delegate to your provider). - Config (
config.py): env-basedServerConfigandBackendConfig. - Tool abstraction (
tool_provider.py):ToolDefinition,ToolProviderprotocol, andInMemoryToolProvider. - Connector example (
connector.py): Commented-out Metabase connector example. Uncomment and edit for your source (e.g. Signoz)—changeSource,SourceKeyType, and keys to match your drd source. - Example source provider (
example_source_provider.py): Commented-out provider and manager (Metabase). Uncomment and change source enum (e.g.Source.METABASE→Source.SIGNOZ) and prefix (e.g."metabase"→"signoz") for your tool; then set the provider inserver.pyso the server runs. - DRD extractor (
drd_extractor.py):extract_tools_from_source_manager(source_manager, prefix)to build tool definitions from a drdSourceManager. - Generic drd provider (
drd_source_provider.py):DrdSourceToolProvider(manager, source_enum, connector_name, connector_id, prefix)—used by the example provider; no need to duplicate task or execution logic.
To expose backend tools (e.g. from a drd SourceManager) as MCP tools:
-
Implement a
ToolProviderthat:list_tools()→ list ofToolDefinition(name, description,parameters_schema).call_tool(name, arguments)→ run the backend operation and return a JSON-serializable result.
-
Use the generic extractor (if using drd): call
extract_tools_from_source_manager(manager, prefix="metabase")to getToolDefinitions fromtask_type_callable_mapandform_fields. Implementcall_toolby building the task/connector and calling the source manager’sexecute_task(or equivalent). -
Wire the provider in
server.py: beforemain(), callset_tool_provider(your_provider). The template already registerslist_toolsandexecute_tool; they will then expose your backend’s tools. MCP clients can calllist_toolsto discover names/schemas, thenexecute_tool(name, arguments)to run them. -
(Optional) In a concrete repo (e.g. Metabase), add config (env or YAML) for the backend (URL, API key, etc.) and build the connector/credentials from that so
call_toolcan run without a DB.
See the metabase-mcp-server repo (e.g. ../metabase-mcp-server or your fork) for a full example using MetabaseSourceManager.
The template does not run until you configure a provider. Running uv run mcp-template-server before that will exit with instructions.
- Uncomment and edit
connector.pyfor your source (Metabase, Signoz, etc.). - Uncomment and edit
example_source_provider.py: set the source enum andprefixfor your tool name. - In
server.py, set up the toolkit path (if needed), then create your provider and assign it to_provider(or callset_tool_provider(provider)). - Add backend config (e.g. URL, API key) to
config.pyor env, and pass it when creating the provider.
Then:
cd mcp-server-template
uv venv .venv && source .venv/bin/activate
uv sync
uv run mcp-template-server- HTTP/streamable (e.g. MCP Inspector):
MCP_TRANSPORT=streamable-http uv run mcp-template-server - Override port with
MCP_PORT, name withMCP_SERVER_NAME.
# Install pytest if needed (e.g. add [project.optional-dependencies] dev = ["pytest"] in pyproject.toml, then uv sync --extra dev)
uv run pytesttests/conftest.py adds src to the path and loads .env from the project root so tests see env-based credentials when present. For backend credential or integration tests (e.g. connection check, calling a real tool), use the pattern from metabase-mcp-server: skip when credentials or optional deps are missing, use lazy imports for heavy backends, and run pytest with the project venv so all dependencies are available.
See Create a new MCP server from this template at the top for the full step-by-step. In short: use the template → rename package and entrypoint → add toolkit + Django → uncomment and edit connector.py and example_source_provider.py (source enum + prefix) → wire provider and config in server.py → set env → run. One repo per integration (Metabase, Signoz, Grafana, etc.) for discoverability and per-tool config.
- Copy this template into a new directory, e.g.
metabase-mcp-server, or use “Use this template” on GitHub. - Optionally create the repo on GitHub first and clone it, then copy template files into it.
(Full steps are in the section above; the bullets below are redundant and can be removed.)
- Rename
src/mcp_template/tosrc/<service>_mcp_server/(e.g.metabase_mcp_server). - In
pyproject.toml: setname = "metabase-mcp-server", updateproject.scriptsto point to the new package’sserver:main. - In all Python files: replace
mcp_templateimports with the new package name.
- In the new package, add config (env or YAML) for the service (e.g.
METABASE_URL,METABASE_API_KEY). - Using the generic provider (easiest): Add a manager subclass (overrides
get_active_connectorsto return your connector) and a connector module. Then create the provider withDrdSourceToolProvider(manager, Source.METABASE, connector_name, connector_id, prefix="metabase"). See metabase-mcp-server:metabase_provider.pyis a thin wrapper that only passes manager,Source.METABASE, and prefix; you can do the same for Signoz/Grafana by changing the source enum and prefix. - Or implement a custom
ToolProvider(e.g. useextract_tools_from_source_managerand implementcall_toolyourself). - In
server.py, instantiate the provider and callset_tool_provider(provider)(or register tools and set_provider) beforemain().
- Add
drdroid-debug-toolkit(or your backend SDK) topyproject.toml. If the toolkit is local or from a private repo, use a path or Git dependency.
- Add tests that call
list_toolsandexecute_tool(with mocks or a test instance). - Add a
.gitignore(see this repo’s root),.env.example, and a README with tool list, env vars, and run/deploy instructions. - Add Dockerfile/docker-compose if you want to run the server in a container.
Include:
- What the server does and which tools it exposes.
- Requirements: Python version, env vars (with
.env.example). - Install and run:
uv sync,uv run <entrypoint>, and how to choose stdio vs HTTP. - MCP client setup: example config for Cursor/Claude (command + args for stdio, or URL for HTTP).
- Creating other MCP servers: short note that this repo was created from
mcp-server-templateand link back to the template.
mcp-server-template/
├── .gitignore
├── README.md
├── pyproject.toml
├── src/
│ └── mcp_template/
│ ├── __init__.py
│ ├── config.py
│ ├── connector.py # Uncomment and edit for your source
│ ├── drd_extractor.py
│ ├── drd_source_provider.py # Generic provider (no edits needed)
│ ├── example_source_provider.py # Uncomment and edit source enum + prefix
│ ├── server.py # Add toolkit path + set _provider
│ └── tool_provider.py
└── tests/
└── test_basic_server.py.gitignore: ignores__pycache__,.venv,.env,config.yaml, IDE/cache dirs, build artifacts. Keep.env.exampleif you add one.- Config: prefer env vars for deployment; optional
config.yamlfor local overrides (and ignoreconfig.yamlin git).
The metabase-mcp-server repo is an example created from this template. It:
- Uses
MetabaseSourceManagerfromdrdroid-debug-toolkit. - Builds a connector from
METABASE_URLandMETABASE_API_KEY. - Exposes all Metabase tasks (alerts, pulses, dashboards, questions, databases, collections, search) via
list_toolsandexecute_tool.
Clone or copy that repo to see a full setup; use the same pattern for other tools (SigNoz, Grafana, etc.).