Skip to content
MCP Mesh - Distributed Service Mesh for AI Agents

MCP Mesh

Enterprise-Grade Distributed Service Mesh for AI Agents

Distributed Service Mesh for AI Agents

You write the logic. The mesh discovers, connects, heals, and traces — across languages, machines, and clouds.

Complete Platform for AI Agents

MCP Mesh is a complete platform for building and deploying AI agents to production scale — and it's all built on one idea, DDDI: you declare a capability, and the mesh resolves, types, heals, and hot-swaps it at runtime. See how MCP Mesh compares →

What is DDDI?

Distributed Dynamic Dependency Injection — dependencies are discovered, injected, and updated at runtime across machines, languages, and clouds. No configuration files, no restart required. Learn more →


🚀 Quick Overview

The same agent, in each language.

pip install mcp-mesh
from fastmcp import FastMCP
import mesh

app = FastMCP("TripPlanner")

@app.tool()
@mesh.tool(
    capability="plan_trip",
    dependencies=[
        {"capability": "weather", "tags": ["+claude"]},
        {"capability": "hotels",  "tags": ["+gpt"]},
        {"capability": "flights"},
        {"capability": "budget",  "tags": ["+claude"]},
    ],
)
async def plan_trip(
    destination: str,
    dates: str,
    weather: mesh.McpMeshTool = None,
    hotels:  mesh.McpMeshTool = None,
    flights: mesh.McpMeshTool = None,
    budget:  mesh.McpMeshTool = None,
) -> TripPlan:
    forecast = await weather(destination=destination, dates=dates)
    options  = await hotels(destination=destination, dates=dates)
    routes   = await flights(destination=destination, dates=dates)
    cost     = await budget(routes=routes, options=options)
    return TripPlan(forecast, options, routes, cost)

@mesh.agent(name="trip-planner", auto_run=True)
class TripAgent: pass
<dependency>
    <groupId>io.mcp-mesh</groupId>
    <artifactId>mcp-mesh-spring-boot-starter</artifactId>
    <version>3.7.1</version>
</dependency>
import io.mcpmesh.MeshAgent;
import io.mcpmesh.MeshTool;
import io.mcpmesh.Param;
import io.mcpmesh.Selector;
import io.mcpmesh.types.McpMeshTool;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.Map;

@MeshAgent(name = "trip-planner", version = "1.0.0", port = 8080)
@SpringBootApplication
public class TripPlannerApp {

    public static void main(String[] args) {
        SpringApplication.run(TripPlannerApp.class, args);
    }

    @MeshTool(
        capability = "plan_trip",
        dependencies = {
            @Selector(capability = "weather", tags = {"+claude"}),
            @Selector(capability = "hotels",  tags = {"+gpt"}),
            @Selector(capability = "flights"),
            @Selector(capability = "budget",  tags = {"+claude"})
        }
    )
    public TripPlan planTrip(
        @Param("destination") String destination,
        @Param("dates") String dates,
        McpMeshTool<Forecast> weather,
        McpMeshTool<HotelOptions> hotels,
        McpMeshTool<FlightRoutes> flights,
        McpMeshTool<Cost> budget
    ) {
        var args = Map.of("destination", destination, "dates", dates);
        var forecast = weather.call(args);
        var options  = hotels.call(args);
        var routes   = flights.call(args);
        var cost     = budget.call(Map.of("routes", routes, "options", options));
        return new TripPlan(forecast, options, routes, cost);
    }
}
npm install @mcpmesh/sdk
import { FastMCP, mesh, McpMeshTool } from "@mcpmesh/sdk";
import { z } from "zod";

const server = new FastMCP({ name: "TripPlanner", version: "1.0.0" });
const agent = mesh(server, { name: "trip-planner", httpPort: 8080 });

agent.addTool({
  name: "plan_trip",
  capability: "plan_trip",
  description: "Plan a trip by composing weather, hotels, flights, and budget",
  dependencies: [
    { capability: "weather", tags: ["+claude"] },
    { capability: "hotels",  tags: ["+gpt"] },
    { capability: "flights" },
    { capability: "budget",  tags: ["+claude"] },
  ],
  parameters: z.object({
    destination: z.string(),
    dates: z.string(),
  }),
  execute: async (
    { destination, dates },
    weather: McpMeshTool | null,
    hotels: McpMeshTool | null,
    flights: McpMeshTool | null,
    budget: McpMeshTool | null,
  ) => {
    const forecast = await weather!({ destination, dates });
    const options  = await hotels!({ destination, dates });
    const routes   = await flights!({ destination, dates });
    const cost     = await budget!({ routes, options });
    return { forecast, options, routes, cost };
  },
});

What just happened?

Four distributed calls, composed like a local function. Each dep could live in this process, another machine, another language. Mesh handles discovery, transport, retry, and failover — your function stays a function. Each dep is just another @mesh.tool, defined the same way — in this agent or another.

Any dep can be a plain tool or an LLM agent — your code can't tell. weather could be a REST API or a Claude-powered reasoning agent returning a typed pydantic forecast. +claude means prefer the reasoning agent; if it dies, mesh auto-rewires to the API. When Claude recovers, mesh rewires back. No deploy, no config, no code change.

See how the Claude-powered weather agent is built (10 lines)
from fastmcp import FastMCP
import mesh

app = FastMCP("ClaudeWeather")

@app.tool()
@mesh.llm(
    system_prompt="file://prompts/weather.j2",
    provider={"capability": "llm", "tags": ["+claude"]},
)
@mesh.tool(capability="weather", tags=["+claude"])
async def weather(destination: str, dates: str,
                  llm: mesh.MeshLlmAgent = None) -> Forecast:
    return await llm(f"Forecast for {destination} on {dates}")

@mesh.agent(name="claude-weather", auto_run=True)
class Agent: pass

The LLM orchestrates tools via the mesh filter pattern and returns a typed pydantic Forecast — no agentic loop to write.

Route by Python if/else, not config
# Two providers of the same capability, wired at runtime
weather = reasoning_weather if user.wants_explanation else api_weather
forecast = await weather(destination, dates)

See the full TripPlanner tutorial →


⚡ Getting Started

Start with the CLI — fastest way to explore mesh, scaffold agents, and read documentation offline.

meshctl is a fully-featured command-line tool that follows you from your first agent through production and beyond: scaffolding, local dev, registry inspection, tracing, observability, deployment, and operations are all one command away. Explore the full CLI reference →

# Install the CLI
npm install -g @mcpmesh/cli

# Explore commands
meshctl --help

# Built-in documentation
meshctl man

Turn your AI coding assistant into a mesh expert

Working with Claude Code, Cursor, Copilot, or any other AI coding assistant? Ask it to run meshctl man and read through the topics it surfaces. The built-in man pages cover every feature in depth — within a few minutes your assistant will be fluent in mesh, ready to scaffold agents, debug DDDI wiring, and answer architecture questions without you having to copy-paste docs into the chat.

Skip the scrolling story

01 ARRIVE

It starts with one.

@mesh.tool(capability="flight_search")

This is the mesh dashboard. Every card is a running agent; every line is a dependency the mesh resolved on its own. Right now there is one agent — a plain Python function that searches flights. No server code, no registration call, no config file.

01 ARRIVE

It needs what it doesn't have.

dependencies=["user_preferences"]

The flight agent needs preferences it cannot provide itself. It names the capability — not a host, not a port, not a URL — and the mesh finds whoever offers it and injects it as a callable parameter. Four more agents come up, and two relationships form without either side being told where the other lives.

02 THINK

Now it needs to reason.

@mesh.llm(provider={"capability": "llm"})

Some problems don't decompose into function calls. The planner needs a model, so it asks for one the same way anything else asks for anything — by capability. It imports no vendor SDK and reads no API key. A provider agent advertises llm, and that is the whole integration.

02 THINK

Reasoning picks its own collaborators.

filter=[{"capability": "flight_search"}, …]

Instead of a fixed call graph, the planner declares what kind of help the model may recruit. The mesh resolves everything matching and hands them over as callable tools. Add a new agent to the mesh tomorrow and the model can reach it — with no redeploy and no change to this code.

02 THINK

And the ones that asked are now asked.

capability="flight_search"consumed, and consuming

Every consumer is also a provider. The flight agent that needed preferences a moment ago is now what the planner is looking for; the same card carries an edge in each direction. No one brokered the introduction and nothing was registered twice. This is the whole idea — not a call graph with a root, but a set of mutual needs that happen to resolve.

03 SURVIVE

A better one arrives.

provider={"capability":"llm","tags":["+claude"]}

A second provider joins, offering the same llm capability. The planner never named an endpoint, so nothing has to be rewired to consider it — it expresses a preference with a tag and the registry scores the candidates. Both remain eligible. One simply wins.

03 SURVIVE

It dies.

meshctl stop claude-provider

The preferred provider stops. Its heartbeat lapses, the registry ages it out, and the planner's dependency count drops. Nothing crashed and nothing was alerted. The mesh has simply stopped counting on something that is no longer there.

03 SURVIVE

Life goes on.

no deploy · no config · no code change

The planner's requirement was a capability, not an address — and something else already satisfies it. Traffic moves. Exactly one edge on this screen changed; every other relationship is untouched, and no agent was restarted to make it happen.

03 SURVIVE

The old one returns.

+claude scores higher — both are ready

It comes back, and traffic returns to it. Not because the substitute failed — it stayed healthy and connected the entire time — but because preference is scored continuously, not decided once at startup. Relationships here are never permanent, in either direction.

04 OPEN

The outside world wants in.

@mesh.route(dependencies=["trip_planning"])

A five-line HTTP handler inherits the entire mesh. It provides no capability of its own and contains no business logic — it declares what it needs and becomes a front door. The same agents are also reachable over MCP and A2A without changing a line.

04 OPEN

One need, many minds.

-> BudgetAnalysistyped, validated, retried

Three specialists resolve as ordinary callables and run at once. Each returns a typed model rather than a blob of text, and the mesh retries the call if a response doesn't match the schema. Fan-out costs one line, because parallelism was never the hard part — knowing who to call was.

05 GROW

Rewritten. Nobody noticed.

weather-agent -> TypeScript · hotel-agent -> Java

Two agents were replaced with implementations in different languages. Same capabilities, same names, same relationships. Look at the graph: nothing moved. Their dependents were never told, because a dependent asks for a capability and has no way to express a preference about the language behind it.

05 GROW

More of it. Same relationships.

replicaCount: 3

Three instances register under one name and collapse into a single card. Nothing re-resolves and no consumer is reconfigured — behind a Kubernetes Service the mesh resolves the name once and Kubernetes spreads the calls across whoever is healthy.

05 GROW

Nothing here was wired by hand.

twelve agents · three languages · every edge resolved itself

Infrastructure that expects agents.

MCP · A2A · RESTthree protocols, three languages, three vendors, one decorator

LEARN

The man pages are compiled into the binary, so they describe the version you actually have rather than whatever shipped last. Seventeen topics answer in Python, TypeScript or Java. A --raw mode turns your AI coding assistant into a mesh expert, and a ten-day tutorial ships inside the CLI.

DEVELOP

Python, TypeScript, and Java agents discover and call each other through a shared Rust core. One scaffold command emits the agent, its Dockerfile, and its Helm values. Claude, GPT, and Gemini are native; a hundred more arrive through LiteLLM, the Vercel AI SDK, or Spring AI.

TEST

Nothing points at a URL, so nothing needs repointing to test. Run an ordinary agent as a stand-in on your laptop — no mock framework, no special annotation — and the local registry wires your consumer to it. What differs between laptop and production is who registered, never your code.

DEPLOY

The same agent code runs on a laptop, in Docker Compose, and on Kubernetes with no changes. Nothing in it names where anything lives, so there is nothing to repoint when it moves. The health probes Kubernetes wants are already served, and scaling is one value in a file.

SECURE

Every inter-agent call is mutually authenticated. Identity is checked with X.509 before a registration is accepted, backed by files, HashiCorp Vault PKI, or SPIRE workload identity. Certificates rotate through the heartbeat without a restart — and on Linux, private keys live in tmpfs and never touch disk.

OBSERVE

Spans cross language boundaries into one trace tree: a Python call into Java into TypeScript reads as a single trace. Redis carries the stream, Tempo stores it, and three Grafana dashboards come prebuilt. meshctl trace renders the call tree in your terminal.

Build one yourself.

npm install -g @mcpmesh/cli · meshctl scaffold

Ready to get started?

Python SDK Java SDK TypeScript SDK View on GitHub

YouTube Discord

Star the repo if MCP Mesh helps you build better AI systems! ⭐