MCP on Catalyst
Catalyst lets you run Model Context Protocol (MCP) clients and servers as Catalyst apps and govern the traffic between them with the same controls you already use for any other service-to-service call: App ID identity, access policies, bearer middleware, mTLS, observability, and resiliency.
Because service invocation speaks plain HTTP, the agent's existing MCP client can target the local Catalyst sidecar and reach the MCP server by App ID. Off-the-shelf MCP clients and agent frameworks work unchanged — there is no Catalyst-specific MCP SDK to adopt.
How MCP runs on Catalyst
Both the agent and the MCP server run as Catalyst apps, each with its own App ID. The MCP client directs requests to its local sidecar (http://localhost:3500) and sets dapr-app-id: <mcp-server-app-id>. Catalyst's service invocation API resolves the target, applies the policies attached to the MCP server's App ID, and forwards the request.
For each call, Catalyst can:
- Route the request from the calling app to the target app by App ID
- Authenticate the caller's workload identity (mTLS with SPIFFE-issued credentials)
- Apply access control policies defined for the target MCP server's App ID
- Apply HTTP middleware on the inbound pipeline (such as OAuth 2.0 bearer validation)
- Capture logs, metrics, and traces for the call
Catalyst's service-invocation security and observability features apply to MCP calls just like any other service-to-service call, with no changes to MCP client or server code.
Quickstart: run an MCP server as a Catalyst app
A minimal MCP server using the Python mcp library:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-mcp-server")
@mcp.tool()
def get_inventory(product_id: str) -> dict:
"""Look up inventory for a product."""
return {"product_id": product_id, "stock": 42}
if __name__ == "__main__":
mcp.run(transport="sse")
Run it as a Catalyst app:
diagrid dev run \
--project my-project \
--app-id mcp-server \
--app-port 8000 \
-- python server.py
Run the agent (MCP client) as a separate Catalyst app:
diagrid dev run \
--project my-project \
--app-id my-agent \
-- python agent.py
The agent's MCP client targets the Catalyst sidecar through the service-invocation API:
import os
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
DAPR_HTTP_ENDPOINT = os.getenv("DAPR_HTTP_ENDPOINT", "http://localhost:3500")
MCP_URL = f"{DAPR_HTTP_ENDPOINT}/v1.0/invoke/mcp-server/method/mcp"
async with streamablehttp_client(url=MCP_URL) as (read, write, _):
async with ClientSession(read, write) as session:
await session.initialize()
tools = await session.list_tools()
Because both apps are in the same Catalyst project, service invocation routes my-agent's requests to mcp-server by App ID. No additional networking configuration is required.
Apply security controls
Because MCP tool calls flow through Catalyst's service invocation layer, you can apply two independent security mechanisms:
- OAuth 2.0 authentication — a Dapr bearer middleware on the MCP server validates inbound JWTs. See the Authentication tutorial or the MCP OAuth2 how-to.
- Access policies (ACLs) — a
Configurationresource attached to the MCP server's App ID defines which agent App IDs may invoke it, with a deny-by-default posture. See the Access Policies tutorial or the MCP access control how-to.
These mechanisms can be used independently or layered together for defense in depth.
Tutorials
How-to references
Enable MCP servers from the Catalog
If you need your agent to call an external MCP server — GitHub, Linear, Stripe, or an internal MCP server your org publishes — enable it from your project's MCP Catalog instead of wiring it up by hand. Catalog entries come with the URL, transport, and authentication method already filled in; you supply the project-specific credential (an API key, a token, an OAuth client) and the entry is ready for MCP clients. Runtime governance still flows through each MCP server's App ID and the policies attached to it (above).