modern-di integration for FastMCP.
Full guide: FastMCP integration docs
Usage example: examples/
Installation
uv add modern-di-fastmcp # or: pip install modern-di-fastmcp
Usage
setup_di attaches the root container to the server and adds a middleware that builds a request container for every MCP request. FromDI, used as a parameter's default value, resolves a provider (or type) from it and keeps the parameter out of the tool schema.
import dataclasses
import fastmcp
from modern_di import Container, Group, Scope, providers
from modern_di_fastmcp import FromDI, setup_di
@dataclasses.dataclass(kw_only=True)
class Settings:
greeting: str = "Hello"
@dataclasses.dataclass(kw_only=True)
class GreetingService:
settings: Settings # auto-injected by type
class Dependencies(Group):
settings = providers.Factory(scope=Scope.APP, creator=Settings)
service = providers.Factory(scope=Scope.REQUEST, creator=GreetingService)
mcp = fastmcp.FastMCP("greeter")
container = Container(groups=[Dependencies])
setup_di(mcp, container)
container.validate() # optional fail-fast; must come after setup_di registers its providers
@mcp.tool
def greet(name: str, service: GreetingService = FromDI(Dependencies.service)) -> str: # noqa: B008
return f"{service.settings.greeting}, {name}!"
FromDI must be the default value, not Annotated metadata: FastMCP keeps an Annotated parameter in the schema and asks the client for it. When the parameter's type has no schema, as with a plain class, FastMCP itself refuses the tool when it is defined. When it has one, as with a dataclass, the server raises TypeError at startup naming the parameter. The check covers the server's own tools, resources and prompts, not those of a mounted server or ones added after startup.
To stop ruff's B008 from flagging every FromDI default, add it to your ruff config:
[tool.ruff.lint.flake8-bugbear]
extend-immutable-calls = ["modern_di_fastmcp.FromDI"]
The current fastmcp.Context is resolvable within DI via the pre-built fastmcp_context_provider context provider.
Shutdown order
setup_di closes the root container from a provider lifespan it adds to the server. Providers added after setup_di tear down before the close and can still resolve from the container. These run after the close and get ContainerClosedError:
- the teardown of
FastMCP(lifespan=...) - the teardown of providers added before
setup_di
For these, pass manage_lifespan=False and open and close the container in your own server lifespan:
import collections.abc
import contextlib
@contextlib.asynccontextmanager
async def lifespan(server: fastmcp.FastMCP) -> collections.abc.AsyncIterator[None]:
async with container:
yield
... # shutdown code that still needs the container
mcp = fastmcp.FastMCP("greeter", lifespan=lifespan)
container = Container(groups=[Dependencies])
setup_di(mcp, container, manage_lifespan=False)
API
| Symbol | Description |
|---|---|
setup_di(server, container, *, manage_lifespan=True) |
Attaches the container to the server and adds the DI middleware. The server's lifespan opens the container at startup and closes it with close_async() at shutdown. When the server is mounted in another app whose lifespan runs it, let that app own the container and pass manage_lifespan=False here (see Sharing a container with FastAPI). When apps that share a container run with separate lifecycles, pass manage_lifespan=False to every setup_di and open and close the container yourself with async with container:, or the first app to stop closes it for the rest. At startup it raises TypeError for a FromDI used inside Annotated. Raises RuntimeError when called a second time for the same server. Returns the container |
FromDI(dependency) |
Parameter default that resolves a provider (or type) from the request container. Raises RuntimeError naming setup_di when no request container is active, including in a background task (task=True), which FastMCP runs outside middleware |
fetch_di_container(server) |
Returns the root container attached to the server. Raises RuntimeError when setup_di was not called |
fastmcp_context_provider |
ContextProvider for the current fastmcp.Context (REQUEST scope) |
📦 PyPI
📝 License
Part of modern-python
Built on modern-di, a dependency-injection framework with an IoC container and scopes.
Browse the full list of templates and libraries in
modern-python; the org profile has the categorized index.
Metadata
Release files for modern-di-fastmcp 4.0.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| modern_di_fastmcp-4.0.3.tar.gz | 6.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| modern_di_fastmcp-4.0.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 12.4 kB
Release files / modern_di_fastmcp-4.0.3.tar.gz
| Download URL | modern_di_fastmcp-4.0.3.tar.gz |
|---|---|
| Size | 6.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
4a6da7474b2b1e3a9f76abe07ad1ce6340626e9fdb3b83a0554197b04b5c7f04
|
|
BLAKE2b-256 checksum How to use checksums |
00666463085a135c3ca17bfe34465a38641236f44f6fe2820cc0e46f182d3b9e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / modern_di_fastmcp-4.0.3-py3-none-any.whl
| Download URL | modern_di_fastmcp-4.0.3-py3-none-any.whl |
|---|---|
| Size | 6.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
103071b3de39ae6cbf3a7cff4f3a965e538c5a3bf04e6ce06880c167770094bb
|
|
BLAKE2b-256 checksum How to use checksums |
2c14f1fccf072aec854dbb77fd2abf8eadd1da2b562d457b3e420d488566a33f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|