Distributed Dynamic Dependency Injection (DDDI)¶
The core innovation of MCP Mesh — dependencies are discovered, injected, and updated at runtime across machines, languages, and clouds.
What is DDDI?¶
Distributed Dynamic Dependency Injection (DDDI) is a term coined by the MCP Mesh project to describe a new approach to service composition in distributed AI agent systems.
Traditional dependency injection (Spring, Guice, Angular) wires dependencies at compile time or application startup on a single machine. DDDI is fundamentally different:
| Aspect | Traditional DI | DDDI (MCP Mesh) |
|---|---|---|
| When | Compile/startup time | Runtime (continuous) |
| Where | Single process/machine | Across machines, clouds, runtimes |
| Discovery | Configuration files | Automatic via registry |
| Updates | Requires restart | Hot-swappable (no restart) |
| Languages | Single language | Cross-language (Python, TypeScript, Java) |
| Protocol | In-process calls | MCP (Model Context Protocol) |
| Failure | Application crashes | Graceful degradation |
The Four Pillars of DDDI¶
1. Distributed¶
Dependencies span machines, containers, clouds, and language runtimes. A Python agent can depend on a Java service and a TypeScript tool — all wired automatically.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Python Agent │────>│ Java Service │────>│ TS Tool │
│ (Cloud A) │ │ (Cloud B) │ │ (Edge) │
└──────────────┘ └──────────────┘ └──────────────┘
All dependencies injected automatically via DDDI
2. Dynamic¶
Services are discovered and injected at runtime, not at startup. When a new agent joins the mesh, existing agents automatically discover it and receive updated dependency proxies — without any configuration change or restart.
3. Discovery-Driven¶
DDDI uses capability-based discovery with tag matching, not hardcoded service names:
agent.addTool({
name: "greet",
capability: "greeting",
dependencies: ["calculator"],
execute: async (args, calculator: McpMeshTool | null = null) => {
if (!calculator) {
return "Calculator unavailable";
}
const result = await calculator({ expression: "2+2" });
return `The answer is ${result}`;
},
});
You declare what you need (a capability), not where it lives. The mesh finds it.
4. Injection¶
Dependencies are injected as proxy objects that transparently handle:
- Network communication (MCP protocol over HTTP)
- Serialization and deserialization
- Retry and failover
- Health-aware routing (unhealthy providers are filtered out at resolution)
- Re-wiring when the registry picks a different winner
The developer writes a function with typed parameters. The mesh fills them in.
How DDDI Works in MCP Mesh¶
Registration Phase¶
Resolution Phase¶
Agent declares dependency "calculator"
--> Registry finds all agents with capability "calculator"
--> Tag matching scores candidates (+preferred, -excluded)
--> Exactly one winner selected: score, then version, then agent ID
--> Proxy object created and injected
The registry picks a single deterministic winner — it does not round-robin or spread calls across providers. See Tiebreaker for the canonical description, including what the winner's endpoint means when that agent runs multiple replicas.
Heartbeat Phase¶
Every 5 seconds:
Agent --> HEAD /health --> Registry
Registry responds:
200 = no changes
203 = topology changed --> agent re-registers --> dependencies re-resolved
Hot-Swap Phase¶
New "calculator" agent joins mesh
--> Registry detects topology change
--> Existing agents get 203 on next heartbeat
--> Dependencies re-resolved automatically
--> New proxy injected -- zero downtime
DDDI vs Traditional DI Frameworks¶
Spring Framework (Java)¶
Spring DI wires beans at application startup via @Autowired. All beans must be in the same JVM. If a dependency fails, the application crashes.
DDDI difference: Dependencies span JVMs, machines, and languages. If a dependency fails, the agent continues with graceful degradation.
Angular (TypeScript)¶
Angular DI is compile-time, single-process, and browser-only. Providers are registered in modules and resolved at component creation.
DDDI difference: Dependencies are resolved at runtime across distributed services, not at module compilation.
Google Guice (Java)¶
Guice uses runtime binding but within a single JVM. Modules define bindings statically.
DDDI difference: Bindings are dynamic and cross-machine. No module configuration — capabilities are discovered automatically.
Comparison Summary¶
| Feature | Spring | Angular | Guice | DDDI |
|---|---|---|---|---|
| Cross-machine | No | No | No | Yes |
| Cross-language | No | No | No | Yes |
| Runtime discovery | No | No | Partial | Yes |
| Hot-swap | No | No | No | Yes |
| Graceful degradation | No | No | No | Yes |
| Protocol | In-process | In-process | In-process | MCP/HTTP |
DDDI for AI Agents¶
Multi-agent AI systems have unique requirements that traditional DI cannot handle:
- Heterogeneous runtimes — Python for ML models, Java for enterprise integration, TypeScript for web interfaces
- Dynamic composition — Agents join and leave based on demand, scaling, or deployment
- LLM as a dependency — LLM providers are injected like any other service, with capability matching for model selection
- Tool discovery — LLM agents discover available tools at runtime and select them based on capability and tags
- Fault tolerance — If an agent crashes, others continue working with degraded functionality rather than cascading failure
DDDI makes these scenarios work with zero configuration:
# This agent automatically discovers:
# - An LLM provider matching tags ["+claude"]
# - All tools matching tags ["data", "tools"]
# No URLs, no config files, no restart when topology changes.
@mesh.llm(
provider={"capability": "llm", "tags": ["+claude"]},
filter=[{"tags": ["data", "tools"]}],
)
@mesh.tool(capability="analyst")
async def analyze(query: str, llm: mesh.MeshLlmAgent = None) -> str:
return await llm(f"Analyze: {query}")
Real-World Scenario¶
Agent Lifecycle with DDDI¶
- Calculator agent starts -- Registers capability
calculatorwith registry - Analyst agent starts -- Declares dependency on
calculator-- DDDI injects proxy - Analyst calls calculator -- Proxy handles MCP communication transparently
- Calculator crashes -- Analyst detects failure, continues with graceful degradation
- Calculator restarts -- Registry detects, analyst gets 203 heartbeat -- proxy re-injected
- Second calculator starts -- it becomes a standby candidate, not a second lane. The tiebreaker still picks one winner (equal score and version, so lowest agent ID); the analyst's calls all go there, and the other takes over only if the winner goes unhealthy
- Calculator v2 deployed -- Version-aware routing sends traffic to v2
All of this happens without any configuration changes or restarts to the analyst agent.
Redundancy, not throughput
Step 6 buys failover, not capacity. DDDI selects; it never splits traffic between providers. To actually spread load across replicas, put them behind one Kubernetes Service and advertise the Service DNS name — which is what the mcp-mesh-agent Helm chart does by default. See Tiebreaker.
DDDI Design Principles¶
Declare Intent, Not Location¶
Agents declare what they need ("a calculator capability") not where to find it ("http://calculator:8080"). The registry resolves intent to location at runtime.
Injection is Continuous¶
Unlike traditional DI where injection happens once at startup, DDDI continuously monitors the mesh topology. When a better provider appears or a current one fails, proxies are transparently re-wired.
Graceful Degradation Over Hard Failure¶
When a dependency is unavailable, DDDI injects None (Python), null (TypeScript/Java) instead of throwing an exception. Agents are designed to check availability and provide fallback behavior.
Strictness is opt-in, per edge. Mark a dependency required — {"capability": "x", "required": True} (Python), { capability: "x", required: true } (TypeScript), @Selector(required = true) / @MeshDependency(required = true) (Java) — and the registry folds that edge into a transitive availability predicate:
a capability is available ⇔ its owning agent is healthy AND every one of its
requireddependencies resolves to an available provider
An unavailable capability is excluded from resolution exactly like an unhealthy provider, and that exclusion propagates to its own consumers through the existing dependency-update channel — so downstream proxies flip with no code changes. Optional edges never propagate (soft-fail stays the default everywhere you do not opt in). For HTTP routes, the framework's own wrapper returns 503 {"error":"dependency_unavailable","capability":...} when a required dependency is unavailable at request time. Cycles among required edges can never converge, so the registry rejects the registration that would close one, naming the loop (required dependency cycle: a → b → a); cycles through an optional edge stay legal for bootstrapping.
See the language dependency-injection pages for declaration syntax and the full model.
Protocol-Agnostic Proxies¶
Injected proxies abstract away the communication protocol. Today they use MCP over HTTP; tomorrow they could use gRPC or WebSockets without any changes to agent code.
Terminology Quick Reference¶
| Term | Definition |
|---|---|
| DDDI | Distributed Dynamic Dependency Injection — the overall pattern |
| Capability | A named function or service an agent provides (e.g., calculator) |
| Dependency | A capability that an agent needs from another agent |
| Proxy | An injected object that transparently calls the remote agent |
| Tag | A label for filtering and scoring capability matches |
| Registry | The central service that tracks all agents and their capabilities |
| Heartbeat | Periodic health check that also detects topology changes |
| Hot-swap | Replacing a dependency provider without restarting the consumer |
| Topology | The current set of agents, capabilities, and connections in the mesh |
Learn More¶
- Dependency Injection (Python) — DDDI in action with Python decorators
- Dependency Injection (TypeScript) — DDDI with mesh functions
- Dependency Injection (Java) — DDDI with Spring Boot annotations
- Architecture & Design — How the registry and heartbeat system power DDDI
- Service Views — Grouping related capability dependencies into one typed consumer-owned facade
- Health & Discovery — The heartbeat protocol behind hot-swappable dependencies
- Tag Matching — How capability and tag scoring drives dependency resolution