Skip to content

Commit 98ba41a

Browse files
authored
fix: Route MCP traffic to unified binary for PRD-061 consent flow (#2171)
* fix: Route MCP traffic to unified binary for PRD-061 consent flow The MCP OAuth consent flow (PRD-061) requires the BFF and MCP server to share in-memory consent/state stores, which only works when both run inside the unified binary. The deploy configs were still routing MCP traffic to a separate mcp-server container that cannot share stores with the BFF, causing MCP OAuth to fail silently after token issuance. - Caddyfile: route /mcp, /oauth/*, /.well-known/* to unified binary - docker-compose: add MCP env vars to meridian service, remove standalone mcp-server service (demo + develop) - .env templates: remove obsolete Dex-specific MCP vars (MCP_DEX_ISSUER_URL, MCP_DEX_CLIENT_ID, MCP_DEX_CALLBACK_URL, MCP_JWKS_URL), enable MCP_OAUTH_ENABLED by default * fix: Remove orphaned MERIDIAN_API_KEY from develop env template No longer consumed after mcp-server-develop container removal. --------- Co-authored-by: Ben Coombs <bjcoombs@users.noreply.github.com>
1 parent 5b3fed2 commit 98ba41a

File tree

5 files changed

+40
-161
lines changed

5 files changed

+40
-161
lines changed

deploy/demo/.env.demo.example

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -167,55 +167,38 @@ DEPOSIT_CLEARING_ACCOUNT_ID=CLEARING-GBP-001
167167
#OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317
168168

169169
# ---------------------------------------------------------------------------
170-
# MCP Server (AI Integration)
170+
# MCP OAuth 2.1 (AI Integration)
171171
# ---------------------------------------------------------------------------
172+
# MCP OAuth runs inside the unified binary, sharing in-memory consent stores
173+
# with the BFF (PRD-061). No separate mcp-server container needed.
172174

173175
# [REQUIRED] Public base URL for MCP OAuth callbacks.
174176
MCP_BASE_URL=https://demo.meridianhub.cloud
175177

176-
# [REQUIRED] API key for MCP server to authenticate with Meridian gRPC API.
177-
# Must match an entry in API_KEYS above (format: "key:identity").
178-
MERIDIAN_API_KEY=changeme
179-
180-
# [OPTIONAL] Enable OAuth 2.1 for MCP clients (Claude.ai, etc.).
181-
# When true, MCP clients must complete an OAuth flow to get a JWT.
182-
MCP_OAUTH_ENABLED=false
178+
# [OPTIONAL] Enable OAuth 2.1 for MCP clients (Claude Code, etc.).
179+
# When true, MCP clients complete an OAuth consent flow via the Meridian UI.
180+
MCP_OAUTH_ENABLED=true
183181

184182
# [OPTIONAL] OAuth client ID that MCP clients use to authenticate.
185183
#MCP_OAUTH_CLIENT_ID=meridian-mcp
186184

187-
# [OPTIONAL] Base domain for subdomain-based tenant resolution on MCP requests.
188-
#MCP_BASE_DOMAIN=demo.meridianhub.cloud
189-
190185
# [OPTIONAL] Default tenant slug for single-tenant deployments.
191186
# When set, bare-domain OAuth requests (no tenant subdomain) use this tenant.
192187
# When empty in multi-tenant mode, bare-domain requests fail closed with HTTP 400.
193188
#MCP_DEFAULT_TENANT_SLUG=volterra
194189

195-
# [REQUIRED when MCP_OAUTH_ENABLED=true] Dex OIDC issuer URL (internal).
196-
# The MCP server acts as an OIDC client of Dex for delegated authentication.
197-
#MCP_DEX_ISSUER_URL=http://dex:5556/dex
198-
199-
# [OPTIONAL] Dex static client ID for the MCP server's OIDC integration.
200-
#MCP_DEX_CLIENT_ID=meridian-service
201-
202-
# [REQUIRED when MCP_OAUTH_ENABLED=true] MCP server's OIDC callback URL (must be registered in dex.yaml).
203-
#MCP_DEX_CALLBACK_URL=https://demo.meridianhub.cloud/oauth/callback
204-
205-
# [REQUIRED when MCP_OAUTH_ENABLED=true] JWKS URL for validating bearer tokens on MCP requests.
206-
# Points to the BFF's JWKS endpoint so BFF-issued tokens are accepted.
207-
#MCP_JWKS_URL=https://demo.meridianhub.cloud/api/auth/jwks
208-
209-
# [REQUIRED when MCP_OAUTH_ENABLED=true] RSA private key for JWT signing.
210-
# Must be the same key used by the BFF for cross-subdomain session sharing.
190+
# ---------------------------------------------------------------------------
191+
# JWT Signing Key
192+
# ---------------------------------------------------------------------------
193+
# Used by both BFF auth and MCP OAuth for token signing/validation.
211194
#
212-
# Preferred approach mount the key as a file (keeps it out of `docker inspect`):
195+
# Preferred approach - mount the key as a file (keeps it out of `docker inspect`):
213196
# 1. Generate a key: openssl genrsa -out /opt/meridian/secrets/jwt-signing-key.pem 2048
214197
# 2. Lock it down: chmod 600 /opt/meridian/secrets/jwt-signing-key.pem
215198
# 3. Set the path: JWT_SIGNING_KEY_FILE=/run/secrets/jwt-signing-key.pem
216-
# (The docker-compose.yml mounts /opt/meridian/secrets /run/secrets inside the containers.)
199+
# (The docker-compose.yml mounts /opt/meridian/secrets -> /run/secrets inside the containers.)
217200
#
218-
# Fallback approach inline PEM string (less secure, visible in `docker inspect`):
201+
# Fallback approach - inline PEM string (less secure, visible in `docker inspect`):
219202
# Store as a single line with literal \n for newlines (docker-compose .env limitation).
220203
# Generate: openssl genrsa 2048 | awk '{printf "%s\\n", $0}'
221204
#

deploy/demo/Caddyfile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ demo.meridianhub.cloud, *.demo.meridianhub.cloud {
1616
}
1717

1818
# MCP Server: streamable HTTP transport + OAuth 2.1 endpoints
19+
# Routed to the unified binary where MCP OAuth shares in-memory
20+
# consent stores with the BFF (required by PRD-061).
1921
# Also handles /.well-known/* for OAuth metadata discovery.
2022
@mcp_transport {
2123
path /mcp /mcp/* /oauth/* /.well-known/oauth-authorization-server
2224
}
2325
handle @mcp_transport {
24-
reverse_proxy mcp-server:8090
26+
reverse_proxy meridian:8090
2527
}
2628

2729
# Dex OIDC endpoints (embedded in meridian binary)
@@ -64,11 +66,13 @@ develop.meridianhub.cloud, *.develop.meridianhub.cloud {
6466
}
6567

6668
# MCP Server: streamable HTTP transport + OAuth 2.1 endpoints
69+
# Routed to the unified binary where MCP OAuth shares in-memory
70+
# consent stores with the BFF (required by PRD-061).
6771
@mcp_transport {
6872
path /mcp /mcp/* /oauth/* /.well-known/oauth-authorization-server
6973
}
7074
handle @mcp_transport {
71-
reverse_proxy mcp-server-develop:8090
75+
reverse_proxy meridian-develop:8090
7276
}
7377

7478
# Dex OIDC endpoints (embedded in meridian binary)

deploy/demo/docker-compose.yml

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
#
33
# Services:
44
# postgres - PostgreSQL 16 (internal only, not exposed to host)
5-
# meridian - Unified binary with embedded Dex OIDC; starts after postgres is healthy
5+
# meridian - Unified binary with embedded Dex OIDC + MCP server; starts after postgres is healthy
66
# caddy - Reverse proxy; exposes 80/443, terminates TLS via Cloudflare Origin Certificate
7-
# mcp-server - MCP server for AI integration (internal, proxied via Caddy)
7+
#
8+
# MCP OAuth runs inside the unified binary so the BFF and MCP share in-memory
9+
# consent/state stores (PRD-061). No separate mcp-server container needed.
810
#
911
# Usage:
1012
# cp deploy/demo/.env.demo.example /opt/meridian/.env
@@ -116,6 +118,12 @@ services:
116118
DB_MAX_IDLE_CONNS: ${DB_MAX_IDLE_CONNS:-5}
117119
DB_CONN_MAX_LIFETIME: ${DB_CONN_MAX_LIFETIME:-5m}
118120
DB_CONN_MAX_IDLE_TIME: ${DB_CONN_MAX_IDLE_TIME:-10m}
121+
122+
# --- MCP OAuth 2.1 (runs inside unified binary, shares consent stores with BFF) ---
123+
MCP_OAUTH_ENABLED: ${MCP_OAUTH_ENABLED:-false}
124+
MCP_OAUTH_CLIENT_ID: ${MCP_OAUTH_CLIENT_ID:-meridian-mcp}
125+
MCP_BASE_URL: ${MCP_BASE_URL:-}
126+
MCP_DEFAULT_TENANT_SLUG: ${MCP_DEFAULT_TENANT_SLUG:-}
119127
volumes:
120128
# Mount the secrets directory read-only so JWT_SIGNING_KEY_FILE can reference
121129
# /run/secrets/jwt-signing-key.pem without the key appearing in docker inspect.
@@ -134,61 +142,6 @@ services:
134142
# and retries upstream connections itself during meridian startup.
135143
# Not exposed to host — accessed only by caddy on the internal network
136144

137-
mcp-server:
138-
image: ${MERIDIAN_IMAGE:-ghcr.io/meridianhub/meridian:demo}
139-
entrypoint: ["/mcp-server"]
140-
restart: unless-stopped
141-
depends_on:
142-
meridian:
143-
condition: service_started
144-
environment:
145-
# --- MCP Transport ---
146-
MCP_TRANSPORT: http
147-
MCP_PORT: "8090"
148-
MCP_SERVER_NAME: ${MCP_SERVER_NAME:-meridian-mcp}
149-
MCP_BASE_URL: ${MCP_BASE_URL:?MCP_BASE_URL must be set in the env file (e.g. https://your-domain)}
150-
151-
# --- Meridian API ---
152-
MERIDIAN_API_URL: meridian:50051
153-
MERIDIAN_API_KEY: ${MERIDIAN_API_KEY:?MERIDIAN_API_KEY must be set in the env file}
154-
155-
# --- Application ---
156-
LOG_LEVEL: ${LOG_LEVEL:-info}
157-
158-
# --- OAuth 2.1 + OIDC (optional) ---
159-
MCP_OAUTH_ENABLED: ${MCP_OAUTH_ENABLED:-false}
160-
MCP_OAUTH_CLIENT_ID: ${MCP_OAUTH_CLIENT_ID:-meridian-mcp}
161-
MCP_BASE_DOMAIN: ${BASE_DOMAIN:-}
162-
163-
# --- Tenant defaults (single-tenant demo mode) ---
164-
MCP_DEFAULT_TENANT_SLUG: ${MCP_DEFAULT_TENANT_SLUG:-} # e.g. "volterra" — used when no tenant subdomain is present
165-
166-
# --- Dex OIDC (delegates authentication to embedded Dex when MCP_OAUTH_ENABLED=true) ---
167-
MCP_DEX_ISSUER_URL: ${MCP_DEX_ISSUER_URL:-} # e.g. https://demo.meridianhub.cloud/dex (Dex is embedded in meridian binary)
168-
MCP_DEX_CLIENT_ID: ${MCP_DEX_CLIENT_ID:-meridian-service} # Dex static client ID
169-
MCP_DEX_CALLBACK_URL: ${MCP_DEX_CALLBACK_URL:-} # e.g. https://demo.meridianhub.cloud/oauth/callback
170-
171-
# --- JWT signing (shared with BFF for session interoperability) ---
172-
JWT_SIGNING_KEY_FILE: ${JWT_SIGNING_KEY_FILE:-}
173-
JWT_SIGNING_KEY: ${JWT_SIGNING_KEY:-}
174-
JWT_SIGNING_KEY_ID: ${JWT_SIGNING_KEY_ID:-meridian-1}
175-
JWT_SIGNING_ISSUER: ${JWT_SIGNING_ISSUER:-meridian}
176-
177-
# --- Bearer token validation ---
178-
MCP_JWKS_URL: ${MCP_JWKS_URL:-} # e.g. https://demo.meridianhub.cloud/api/auth/jwks
179-
volumes:
180-
- /opt/meridian/secrets:/run/secrets:ro
181-
deploy:
182-
resources:
183-
limits:
184-
cpus: '1'
185-
memory: 512M
186-
reservations:
187-
cpus: '0.25'
188-
memory: 128M
189-
# Not exposed to host — accessed only by caddy on the internal network
190-
# Caddy proxies /mcp to this container
191-
192145
caddy:
193146
image: caddy:2-alpine
194147
restart: unless-stopped

deploy/develop/.env.develop.template

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -118,26 +118,16 @@ JWT_SIGNING_KEY_ID=meridian-develop-1
118118
JWT_SIGNING_ISSUER=meridian-develop
119119

120120
# ---------------------------------------------------------------------------
121-
# MCP Server (AI Integration)
121+
# MCP OAuth 2.1 (AI Integration)
122122
# ---------------------------------------------------------------------------
123+
# MCP OAuth runs inside the unified binary, sharing in-memory consent stores
124+
# with the BFF (PRD-061). No separate mcp-server container needed.
123125

124126
# [REQUIRED] Public base URL for MCP OAuth callbacks.
125127
MCP_BASE_URL=https://develop.meridianhub.cloud
126128

127-
# [REQUIRED] API key for MCP server to authenticate with Meridian gRPC API.
128-
MERIDIAN_API_KEY=
129-
130129
# [OPTIONAL] Enable OAuth 2.1 for MCP clients.
131-
MCP_OAUTH_ENABLED=false
132-
133-
# [OPTIONAL] MCP server name.
134-
MCP_SERVER_NAME=meridian-mcp-develop
130+
MCP_OAUTH_ENABLED=true
135131

136132
# [OPTIONAL] Default tenant slug for single-tenant deployments.
137133
#MCP_DEFAULT_TENANT_SLUG=volterra
138-
139-
# [REQUIRED when MCP_OAUTH_ENABLED=true] Dex OIDC issuer URL.
140-
#MCP_DEX_ISSUER_URL=http://meridian-develop:8090/dex
141-
#MCP_DEX_CLIENT_ID=meridian-service
142-
#MCP_DEX_CALLBACK_URL=https://develop.meridianhub.cloud/oauth/callback
143-
#MCP_JWKS_URL=https://develop.meridianhub.cloud/api/auth/jwks

deploy/develop/docker-compose.develop.yml

Lines changed: 7 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
#
77
# Services:
88
# postgres-develop - PostgreSQL 16 (isolated from demo, not exposed to host)
9-
# meridian-develop - Unified binary (develop branch build)
10-
# mcp-server-develop - MCP server for AI integration (develop branch build)
9+
# meridian-develop - Unified binary with MCP server (develop branch build)
1110
#
1211
# Usage:
1312
# cp deploy/develop/.env.develop.template /opt/meridian-develop/.env
@@ -111,75 +110,25 @@ services:
111110
DB_MAX_IDLE_CONNS: ${DB_MAX_IDLE_CONNS:-5}
112111
DB_CONN_MAX_LIFETIME: ${DB_CONN_MAX_LIFETIME:-5m}
113112
DB_CONN_MAX_IDLE_TIME: ${DB_CONN_MAX_IDLE_TIME:-10m}
114-
volumes:
115-
- /opt/meridian-develop/secrets:/run/secrets:ro
116-
networks:
117-
- develop-internal
118-
- meridian
119-
deploy:
120-
resources:
121-
limits:
122-
cpus: '1'
123-
memory: 2G
124-
reservations:
125-
cpus: '0.5'
126-
memory: 512M
127-
128-
mcp-server-develop:
129-
image: ${MERIDIAN_IMAGE:-ghcr.io/meridianhub/meridian:develop}
130-
container_name: mcp-server-develop
131-
entrypoint: ["/mcp-server"]
132-
restart: unless-stopped
133-
depends_on:
134-
meridian-develop:
135-
condition: service_started
136-
environment:
137-
# --- MCP Transport ---
138-
MCP_TRANSPORT: http
139-
MCP_PORT: "8090"
140-
MCP_SERVER_NAME: ${MCP_SERVER_NAME:-meridian-mcp-develop}
141-
MCP_BASE_URL: ${MCP_BASE_URL:?MCP_BASE_URL must be set in the env file (e.g. https://develop.meridianhub.cloud)}
142-
143-
# --- Meridian API ---
144-
MERIDIAN_API_URL: meridian-develop:50051
145-
MERIDIAN_API_KEY: ${MERIDIAN_API_KEY:?MERIDIAN_API_KEY must be set in the env file}
146113

147-
# --- Application ---
148-
LOG_LEVEL: ${LOG_LEVEL:-info}
149-
150-
# --- OAuth 2.1 + OIDC (optional) ---
114+
# --- MCP OAuth 2.1 (runs inside unified binary, shares consent stores with BFF) ---
151115
MCP_OAUTH_ENABLED: ${MCP_OAUTH_ENABLED:-false}
152116
MCP_OAUTH_CLIENT_ID: ${MCP_OAUTH_CLIENT_ID:-meridian-mcp}
153-
MCP_BASE_DOMAIN: ${BASE_DOMAIN:-}
154-
155-
# --- Tenant defaults (single-tenant demo mode) ---
117+
MCP_BASE_URL: ${MCP_BASE_URL:-}
156118
MCP_DEFAULT_TENANT_SLUG: ${MCP_DEFAULT_TENANT_SLUG:-}
157-
158-
# --- Dex OIDC ---
159-
MCP_DEX_ISSUER_URL: ${MCP_DEX_ISSUER_URL:-}
160-
MCP_DEX_CLIENT_ID: ${MCP_DEX_CLIENT_ID:-meridian-service}
161-
MCP_DEX_CALLBACK_URL: ${MCP_DEX_CALLBACK_URL:-}
162-
163-
# --- JWT signing (SEPARATE from demo) ---
164-
JWT_SIGNING_KEY_FILE: ${JWT_SIGNING_KEY_FILE:-}
165-
JWT_SIGNING_KEY: ${JWT_SIGNING_KEY:-}
166-
JWT_SIGNING_KEY_ID: ${JWT_SIGNING_KEY_ID:-meridian-develop-1}
167-
JWT_SIGNING_ISSUER: ${JWT_SIGNING_ISSUER:-meridian-develop}
168-
169-
# --- Bearer token validation ---
170-
MCP_JWKS_URL: ${MCP_JWKS_URL:-}
171119
volumes:
172120
- /opt/meridian-develop/secrets:/run/secrets:ro
173121
networks:
122+
- develop-internal
174123
- meridian
175124
deploy:
176125
resources:
177126
limits:
178127
cpus: '1'
179-
memory: 512M
128+
memory: 2G
180129
reservations:
181-
cpus: '0.25'
182-
memory: 128M
130+
cpus: '0.5'
131+
memory: 512M
183132

184133
volumes:
185134
postgres_develop_data:

0 commit comments

Comments
 (0)