Skip to main content

Tutorial: Zero-Trust Authorization for MCP Servers

Control which applications can invoke MCP servers and which downstream services those servers can reach by using access policies.

This tutorial shows how to secure MCP traffic with Catalyst access policies. You will run three apps: an MCP client that discovers and invokes tools on an MCP server, and an MCP server that calls a downstream weather service. You will then apply policies to control access at each service boundary.

note

This tutorial uses Catalyst Cloud to get started quickly with zero infrastructure setup. Catalyst Cloud uses a tunnel to route service invocation requests from the cloud to your local development machine. For production or on-premises requirements, Diagrid also offers a self-hosted enterprise option where traffic stays within your private network.

You will learn how to:

  • Use Catalyst service invocation for MCP client-to-server communication
  • Block an MCP client from reaching an MCP server with a deny policy
  • Restrict the MCP server's downstream access to a backend service

Your apps run entirely on your local machine. You are not deploying code to Catalyst. diagrid dev run opens a secure tunnel between Catalyst Cloud and your local processes and registers each app with a Dapr App ID. When the MCP client sends a service invocation request, it goes to Catalyst Cloud instead of directly to the MCP server. Catalyst evaluates the access policy there. If the policy allows the request, Catalyst forwards it back through the tunnel to the local server. If the policy denies it, Catalyst returns a 403 PermissionDenied immediately, and the request never reaches your local server process.

1. Prerequisites

2. Log in to Catalyst

diagrid login

Confirm your identity:

diagrid whoami

3. Clone the Repository

git clone https://github.com/diagridio/catalyst-quickstarts.git
cd catalyst-quickstarts/mcp-access-control/python

The tutorial contains three services:

ServiceApp IDDescription
mcp_clientmcp-clientDiscovers and invokes MCP tools through Catalyst service invocation
mcp_servermcp-serverExposes MCP tools (add, get_weather_alert, echo) via FastMCP
weather_serviceweather-serviceDownstream service that returns mock weather alerts

All service-to-service communication flows through Catalyst, where access policies are evaluated before each request reaches its destination.

4. Install Dependencies

python3 -m venv .venv
source .venv/bin/activate
pip install -e mcp_client/ -e mcp_server/ -e weather_service/

5. Run with the Default Allow Policy

Start all three apps with Catalyst Cloud:

diagrid dev run -f mcp-quickstart.yaml --project mcp-access-qs --approve

Open a new terminal and trigger the MCP client:

curl -s -X POST http://localhost:5001/run | python -m json.tool

You should see a successful response with all tools discovered and invoked:

{
"tools": [
{"name": "add", "description": "Add two numbers together."},
{"name": "get_weather_alert", "description": "Get a weather alert for a given city."},
{"name": "echo", "description": "Echo back a message."}
],
"add_result": "5",
"weather_alert": "Severe thunderstorm warning until 6 PM CDT",
"errors": []
}

With the default allow policy, Catalyst permits the service invocation calls. The MCP client discovers three tools and successfully invokes both add and get_weather_alert.

6. Block the MCP Client

Stop the running application by pressing Ctrl+C, then disconnect the app IDs from Catalyst:

diagrid dev stop --app-id mcp-client --project mcp-access-qs
diagrid dev stop --app-id mcp-server --project mcp-access-qs
diagrid dev stop --app-id weather-service --project mcp-access-qs

Create a deny-all access configuration for the MCP server:

diagrid configuration create mcp-server-deny \
--project mcp-access-qs \
--default-action deny \
--policy mcp-client:deny

Apply the configuration to the MCP server App ID:

diagrid appid update mcp-server \
--project mcp-access-qs \
--app-config mcp-server-deny

This configuration denies incoming service invocation requests to the MCP server. Because authorization is enforced in Catalyst Cloud rather than by the server process, denied requests never reach your local server. Catalyst returns the 403 before any traffic is forwarded through the tunnel.

7. Verify the Client Is Blocked

Restart all apps:

diagrid dev run -f mcp-quickstart.yaml --project mcp-access-qs --approve

Open a new terminal and trigger the MCP client again:

curl -s -X POST http://localhost:5001/run | python -m json.tool

The client receives a 403 error and cannot reach the MCP server. No tools are discovered, and no calls succeed:

{
"detail": {
"error": "ACCESS_DENIED",
"message": "MCP server returned HTTP 403"
}
}

8. Block the MCP Server's Downstream Access

Stop the running application by pressing Ctrl+C, then disconnect all app IDs:

diagrid dev stop --app-id mcp-client --project mcp-access-qs
diagrid dev stop --app-id mcp-server --project mcp-access-qs
diagrid dev stop --app-id weather-service --project mcp-access-qs

Restore client access to the MCP server, but block the MCP server from reaching the weather service:

# Allow the MCP client to call the MCP server
diagrid configuration create mcp-server-allow \
--project mcp-access-qs \
--default-action allow

diagrid appid update mcp-server \
--project mcp-access-qs \
--app-config mcp-server-allow

# Block the MCP server from calling the weather service
diagrid configuration create weather-deny \
--project mcp-access-qs \
--default-action deny \
--policy mcp-server:deny

diagrid appid update weather-service \
--project mcp-access-qs \
--app-config weather-deny

Restart all apps:

diagrid dev run -f mcp-quickstart.yaml --project mcp-access-qs --approve

Open a new terminal and trigger the MCP client:

curl -s -X POST http://localhost:5001/run | python -m json.tool

This time, the add tool succeeds, but get_weather_alert returns a 403 Forbidden error:

{
"tools": [
{"name": "add", "description": "Add two numbers together."},
{"name": "get_weather_alert", "description": "Get a weather alert for a given city."},
{"name": "echo", "description": "Echo back a message."}
],
"add_result": "5",
"weather_alert": null,
"errors": [
{
"tool": "get_weather_alert",
"error": "Error fetching weather alert: ... 403 Forbidden ..."
}
]
}

The MCP client can still reach the MCP server and invoke local tools such as add, but the server's downstream call to the weather service is blocked. This demonstrates granular downstream access control. You can allow access to an MCP server while restricting which backend services its tools are allowed to call.

9. Clean Up

Stop the running application by pressing Ctrl+C.

Delete the Catalyst Cloud project:

diagrid project delete mcp-access-qs
note

If you plan to continue with the Authentication tutorial, skip this cleanup step. That tutorial reuses the same mcp-access-qs project and App IDs.

Summary

In this tutorial, you:

  • Set up an MCP client, MCP server, and downstream weather service communicating through Catalyst service invocation
  • Verified that all MCP tools succeed with the default allow policy
  • Applied a deny policy that blocked the MCP client from reaching the MCP server entirely
  • Applied a downstream deny policy that allowed the client to use the MCP server, but blocked the server's call to the weather service

Next Steps