Skip to main content

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:

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).