Build a custom MCP server with OAuth 2.1
What you'll build:
A minimal, runnable Python MCP server that Blockbrain can connect to via OAuth 2.1 β so internal tools or data you expose can be safely consumed by a Blockbrain agent.
Audience: a developer at your side. Copy the four code blocks below into a folder, run five commands, point Blockbrain at the resulting URL, and you have a working authenticated integration.
Time required: ~20 minutes for a working local server; ~45 minutes including hardening.
See also: MCP Server β admin UI walkthrough.
Prerequisites
Python 3.11+
A public HTTPS URL for your local server while testing β easiest options: an HTTPS tunnelling tool such as
cloudflared tunnelorngrokTenant Administrator access to your Blockbrain tenant
Familiarity with OAuth 2.1 Authorization Code flow with PKCE (helpful but not required)
When to use which authentication mode
Blockbrain's MCP-server registration UI offers four authentication methods. Choose based on how your MCP server should know who is calling:
None
Internal/dev only and the server URL is not public
Anyone who reaches the URL can call the tools
API Key (Fixed Token)
Service-to-service. One shared secret. No per-user identity needed.
Cannot distinguish individual Blockbrain users on your side
OAuth 2.1 β this guide
Production scenarios. Blockbrain's tenant admin authorizes the connection once via consent flow.
More moving parts, but the standard MCP-spec way
User Token (delegated)
You want per-user authorization on your MCP server (e.g. enforce that User A only sees their own data)
Your server must validate Blockbrain's forwarded JWT β see section 8
The OAuth 2.1 discovery flow Blockbrain uses
When you select OAuth 2.1 in the admin UI and click Discover OAuth, Blockbrain follows RFC 9728 + RFC 8414 to find your authorization server, then runs Authorization Code + PKCE:
The Python example
Three files in one folder. Copy each block verbatim.
server.py
server.pyrequirements.txt
requirements.txt.env.example
.env.exampleReplace the demo client credentials with strong random values before deploying anywhere reachable from the public internet.
Run it locally
Verify the endpoints with curl
Register your MCP server in Blockbrain
Expose your local server publicly:
cloudflared tunnel --url http://localhost:8080(orngrok http 8080). Note the public HTTPS URL.Set
SERVER_HOSTin.envto that public URL and restart the server.Open Blockbrain β Admin β Agents β MCP Servers β + Add MCP Server.
Fill in:
Server Name: e.g. my-mcp-demo
Server URL:
https://<your-tunnel>/mcpTransport:
HTTPAuthentication:
OAuth 2.1
Click Discover OAuth. Blockbrain reads your two
.well-knownendpoints and pre-fills the OAuth config.Click Configure, complete the consent flow, and confirm the access token comes back.
Save. Assign the integration to a test agent. In a chat, ask the agent to call
echo "hello"β you should seeecho: hellocome back.
Full admin-UI walkthrough with screenshots: MCP Server β for admins.
Optional β validate Blockbrain's forwarded user JWT
If you also want per-user authorization (delegated-access mode), set VALIDATE_USER_TOKEN=1. The validate_user_jwt helper in server.py checks every incoming bearer token against Blockbrain's public JWKs at https://auth.theblockbrain.ai/oauth/v2/keys, verifies signature, expiration, and audience, and returns the claims (including external_user_id, urn:zitadel:iam:org:id, etc.).
Headers Blockbrain sends with every request
Your MCP server can read these to identify the calling user, tenant, and thread context:
X-User-ID
End-user identifier
user_12345
X-External-User-ID
Your system's user identifier (if activated)
ext_user_abc
X-Tenant-ID
Tenant identifier
tenant_xyz
X-Agent-ID
Agent making the request
agent_007
X-Data-Room-ID
Associated data room
room_456
X-Thread-ID
Conversation thread
thread_789
Authorization
Bearer token (OAuth access token, or forwarded user JWT)
Bearer ...
Troubleshooting
"Discover OAuth" returns 404
.well-known paths not exposed at the public URL
Confirm the tunnel forwards root paths; re-curl both well-known endpoints
Redirect-URI mismatch on consent
IdP does not support Dynamic Client Registration (e.g. Microsoft Entra)
Use the static OAUTH_CLIENT_ID in this example rather than a dynamically registered one
Token returned but tools list is empty
Scopes not preserved during configure
Make sure your authorization-server metadata includes scopes_supported
Production hardening checklist
Before shipping to a production environment, replace or add:
The in-memory
auth_codes/access_tokensdicts β a persistent store (e.g. Redis), so tokens survive restarts and can be shared across replicas.Refresh-token rotation. The example issues access tokens only.
Structured logging and an audit trail for every
/authorizeand/tokencall.Move
OAUTH_CLIENT_SECRETto a secrets manager β never check it into source control.Terminate HTTPS in front of the server (reverse proxy, load balancer, or your platform's ingress).
Rate limiting on
/tokenand/authorize.If multi-tenant, scope
OAUTH_CLIENT_IDper tenant rather than reusing one shared value.
Next steps
SSE transport β Blockbrain also supports SSE (
https://your-server/sse). To switch, replace theapp.mount("/mcp", ...)line with the SSE app frommcp.server.sse.Real tools β replace the
echotool with calls into your domain (database queries, internal APIs, etc.).JavaScript / TypeScript example β a Node/Express equivalent of this guide using
@modelcontextprotocol/sdkis being prepared as a follow-up.
Last updated

