Quickstart: MCP
In this quickstart, you'll put a Model Context Protocol server behind Catalyst Cloud and secure it from both sides. You will learn how to:
- Reach an MCP server through Catalyst's MCP proxy endpoint in order to dynamically provide access policy rights to the tools. This is achieved by giving the MCP server a secure identity.
- Give Catalyst the credential it needs to authenticate itself to your MCP server, without that credential ever entering the caller's code.
- Grant a caller (application or agent) access to one tool but not another, and dynamically change that policy at runtime with no redeploy.
- See why a newly registered MCP server is closed by default, and how the two security gates fail independently.
Authentication and authorization are separate gates here, and this quickstart runs them in that order on purpose: nothing works at all until Catalyst can reach your server, and even then only approved callers and tools get through.
The mcp-client never talks to the MCP server directly — it only calls Catalyst's proxy endpoint. Catalyst enforces the per-tool access policy before proxying anything, then authenticates itself to mcp-server with a shared-secret header on every request. The MCP server exposes two tools: add, which is harmless, and get_account_balance, which stands in for the kind of sensitive tool you'd want restricted to specific callers.
1. Prerequisites
Before you proceed, ensure you have the following prerequisites installed.
2. Log in to Catalyst
Authenticate to Diagrid Catalyst using the following command:
diagrid login
This command opens a new browser window where you'll be shown a confirmation code that should match the code in your terminal. Confirm the code, and if you're not logged into Catalyst, you'll be redirected to login.
Confirm your user details are correct using the following command:
diagrid whoami
The expected output contains the name of the organization, your user name, and the Catalyst API endpoint.
3. Clone Quickstart Code
Clone the quickstart code from GitHub:
git clone https://github.com/diagridio/catalyst-quickstarts
Navigate to the quickstart directory:
- macOS/Linux
- Windows
cd catalyst-quickstarts/mcp-auth/python
cd catalyst-quickstarts\mcp-auth\python
4. Install Dependencies
The client and the server share one uv-managed project at the quickstart root, so a single sync installs dependencies for both:
uv sync
5. Create the project, app, and MCP server
Create the Catalyst project and set it as your default, so the commands that follow don't need an explicit --project flag:
diagrid project create mcp-auth --use
Create the App identity for the caller. mcp-client has no MCP-specific behavior of its own — it's a plain caller — so a generic Catalyst App is all it needs:
diagrid app create mcp-client --wait
Now register the MCP server:
diagrid apply -f resources/mcp-server.yaml
The server doesn't get a plain diagrid app create like the client did. It needs an identity and its MCP-specific behavior — the proxy endpoint and the access policy — and applying the MCPServer resource is what grants both at once. Creating it as a plain App first would collide with that.
This registers a connection pointing at http://localhost:8000/mcp, with its access policy starting deny-all and no upstream credential configured yet. Nothing is listening on localhost:8000 until the next step either, so the server isn't reachable yet.
resources/mcp-server.yaml should have no headers block at this point. You'll add one in step 7.2. If you're re-running this quickstart, see the note in step 7.1.
6. Run the application with Catalyst Cloud
The diagrid dev run command launches both services locally, opens the tunnel connectivity they need, and wires Catalyst's endpoint and API token into the client's process — no manual token or endpoint copying.
diagrid dev run -f mcp-auth-quickstart.yaml --project mcp-auth --approve --skip-managed-kv --skip-managed-pubsub --skip-default-resiliency
This quickstart needs no state store, pub/sub broker, or default resiliency policy, so the --skip-* flags keep the project to just what MCP requires.
mcp-auth-quickstart.yaml also points resourcesPath at ./resources, so dev run re-applies the mcp-server resource you registered in step 5. That's harmless, since applying is idempotent, and it recognizes the App identity is already managed by the MCPServer resource.
Because mcp-server declares an appPort, it gets a secure tunnel from Catalyst Cloud to localhost:8000 — reachable from your hosted project with no public endpoint or inbound firewall rule. mcp-client declares no port, since it only calls out to Catalyst and never receives inbound requests, so no tunnel is opened for it.
Wait until you see application logs for both apps in the terminal before continuing. Leave diagrid dev run running in this terminal and use a second terminal for the steps below.
Catalyst starts periodically probing the MCP server's reachability as soon as its tunnel is up, independent of anything the client does. This terminal may already show rejected requests before you trigger anything yourself.
7. Exercise the two security gates
7.1 See it fail closed
A freshly registered MCP server is closed in two ways at once: it requires a shared-secret header that Catalyst hasn't been given, and its access policy denies every caller and every tool by default.
In a second terminal, trigger the client to open a fresh MCP session and return the list of available tools:
- macOS/Linux
- Windows
curl -s -X POST http://localhost:5001/run | python -m json.tool
Invoke-RestMethod -Method Post -Uri 'http://localhost:5001/run' | ConvertTo-Json -Depth 6
Both problems look identical from here — Session terminated, with no detail:
{
"tools": [],
"add_result": null,
"balance_result": null,
"errors": [
{ "step": "list_tools", "error": "Session terminated" },
{ "tool": "add", "error": "Session terminated" },
{ "tool": "get_account_balance", "error": "Session terminated" }
]
}
The real reason is in the diagrid dev run terminal, in the client's own log lines (labeled == APP - mcp-client ==):
INFO:httpx:HTTP Request: POST https://.../v1.0/diagrid/mcp/mcp-server "HTTP/1.1 404 Not Found"
That 404 is Catalyst's access policy turning the caller away before the request ever reaches the MCP server. Read it as "this caller matches no rule" rather than "the server doesn't exist" — this quickstart has no case where the server is missing.
You may also see the server's own lines (== APP - mcp-server ==) showing 401 Unauthorized. That's Catalyst's background credential check, not your call: while the access policy denies you, your request never reaches the server at all.
Keep the applications running.
7.2 Authenticate Catalyst to the MCP server
mcp_server/main.py requires every request to carry an x-mcp-shared-secret header — the server's own defense, independent of Catalyst. Catalyst has to be given that credential before it can authenticate itself to your server on the caller's behalf.
Add a headers entry to resources/mcp-server.yaml:
apiVersion: dapr.io/v1alpha1
kind: MCPServer
metadata:
name: mcp-server
spec:
endpoint:
streamableHTTP:
url: http://localhost:8000/mcp
headers:
- name: x-mcp-shared-secret
value: local-dev-shared-secret
Apply the change from your second terminal. Everything you started in step 6 keeps running — nothing needs restarting:
diagrid apply -f resources/mcp-server.yaml
Catalyst has been retrying its own connection to the server in the background ever since the tunnel came up, so you don't need to trigger anything to see this succeed. Check the diagrid dev run terminal:
INFO: ... "POST /mcp HTTP/1.1" 200 OK
Processing request of type ListToolsRequest
Catalyst now authenticates successfully and the request reaches your tool code. Trigger the client again:
- macOS/Linux
- Windows
curl -s -X POST http://localhost:5001/run | python -m json.tool
Invoke-RestMethod -Method Post -Uri 'http://localhost:5001/run' | ConvertTo-Json -Depth 6
The response is unchanged — still Session terminated for everything. Fixing the upstream credential doesn't unlock the caller, because the access policy is a separate gate and it's still deny-all. Authentication is fixed; authorization is next.
This quickstart uses the simplest credential option, a static header stored in Catalyst's secret store. Catalyst also supports an OAuth2 client-credentials flow and a secretless SPIFFE JWT it mints per request — see Authentication for MCP servers.
7.3 Authorize the "add" tool only
A Catalyst MCP access policy is an allow-list of which caller App IDs may use which tools. Like the credential, you change it at runtime.
Grant mcp-client access to just the add tool:
diagrid mcpserver access grant mcp-server --caller mcp-client --allow-tools add --wait
--wait waits for the control-plane update to finish. Data-plane enforcement can lag a couple of seconds behind that, so if the very next call still looks denied, retry once.
Trigger the client again:
- macOS/Linux
- Windows
curl -s -X POST http://localhost:5001/run | python -m json.tool
Invoke-RestMethod -Method Post -Uri 'http://localhost:5001/run' | ConvertTo-Json -Depth 6
Now add is discoverable and succeeds, while get_account_balance is rejected with a clean 403. The session itself no longer terminates, because the caller matches some rule:
{
"tools": [
{ "name": "add", "description": "Add two numbers together." }
],
"add_result": "5",
"balance_result": null,
"errors": [
{
"tool": "get_account_balance",
"error": "Client error '403 Forbidden' for url '.../v1.0/diagrid/mcp/mcp-server'",
"status_code": 403,
"reason": "ACCESS_DENIED"
}
]
}
Notice that tools lists only add. Catalyst filters tools/list down to what the caller is granted, so an unauthorized tool isn't just un-callable — it's invisible.
7.4 Authorize all tools
Open the server up with a wildcard grant:
diagrid mcpserver access grant mcp-server --caller "*" --allow-tools "*" --wait
Trigger the client one more time:
- macOS/Linux
- Windows
curl -s -X POST http://localhost:5001/run | python -m json.tool
Invoke-RestMethod -Method Post -Uri 'http://localhost:5001/run' | ConvertTo-Json -Depth 6
Both tools are now discoverable and both succeed:
{
"tools": [
{ "name": "add", "description": "Add two numbers together." },
{ "name": "get_account_balance", "description": "Look up the balance for an account. Treated as a sensitive operation." }
],
"add_result": "5",
"balance_result": "Account acct-42 balance: $1,204.53",
"errors": []
}
You can preview a policy verdict at any time without calling the server, which is the fastest way to tell an authorization problem from an authentication one:
diagrid mcpserver access test mcp-server --caller mcp-client --tool get_account_balance
7.5 View in the Catalyst web console
Open the Catalyst Cloud web console, select the mcp-auth project, and go to its MCP Servers page. The entry for mcp-server shows the registered endpoint, the configured upstream credential, and the access policy rules you granted in the previous steps.
8. Clean Up
Press CTRL+C in the terminal that runs diagrid dev run to stop both applications and disconnect from Catalyst Cloud.
To delete the entire project and all provisioned resources:
diagrid project delete mcp-auth
Summary
In this quickstart you:
- Registered an MCP server with Catalyst and reached it through Catalyst's MCP proxy endpoint rather than directly.
- Saw a newly registered server fail closed on both of its independent gates.
- Configured the credential Catalyst presents to your MCP server, keeping it out of the caller's code entirely.
- Granted a caller one tool but not another, then widened the policy — both at runtime, with no redeploy and no restart.
Catalyst held the upstream credential and enforced the access policy in front of your server, so the caller never handled a secret and never had a chance to invoke a tool it wasn't granted.
Next steps
- Read Build with MCP on Catalyst for the full picture of how Catalyst fronts MCP servers.
- Explore the other credential options in Authentication for MCP servers, including OAuth2 and secretless SPIFFE JWT.
- Learn the full rule model in Control tool access for MCP servers.
- Register a server from the console, CLI, or YAML with Add an MCP server, and browse the built-in MCP servers reference.
- Point a standard MCP client or agent framework at Catalyst with Connect an MCP client.