fix(runner): correct PUBLIC_URL + mount runner-map volume
All checks were successful
Deploy to Production / deploy (push) Successful in 1m38s
All checks were successful
Deploy to Production / deploy (push) Successful in 1m38s
Two overlapping bugs were killing OAuth discovery for every external MCP client (Claude Desktop, Cursor, etc.): 1. worker.ts injected PUBLIC_URL=http://<RUNNER_HOST>:<port> into the runner container even when MCP_DOMAIN was set. Result: the runner's /.well-known/oauth-protected-resource advertised an unreachable URL and the WWW-Authenticate header pointed at a non-HTTPS loopback address. Claude Desktop refused to follow the discovery chain. Now derives PUBLIC_URL from the same computePublicUrl() helper that builds the user-visible URL stored in mcp_servers.public_url, so the container's self-reported resource matches its actual route. 2. docker-compose.prod.yml never mounted /opt/buildmymcpserver/runner-map into the api / generator containers. The .conf snippet written by the generator landed in an ephemeral container path; the host inotify watcher saw an empty directory and produced an empty runner-map.combined. Result: nginx 404'd every /<slug>/* request, the runner was unreachable from the public domain, and OAuth discovery couldn't even begin. Mount added to both services. Existing weather server has the wrong PUBLIC_URL baked in and must be recreated after deploy. No customers yet. export computePublicUrl from deploy.ts so worker.ts can call it.
This commit is contained in:
parent
3a05766f88
commit
1093dc40a7
@ -52,7 +52,7 @@ async function removeRunnerMapEntry(slug: string): Promise<void> {
|
||||
}
|
||||
}
|
||||
|
||||
function computePublicUrl(slug: string, port: number): string {
|
||||
export function computePublicUrl(slug: string, port: number): string {
|
||||
if (config.MCP_DOMAIN) return `https://${config.MCP_DOMAIN}/${slug}`;
|
||||
return `http://${config.RUNNER_HOST}:${port}`;
|
||||
}
|
||||
|
||||
@ -5,7 +5,13 @@ import { Redis } from 'ioredis';
|
||||
import { config } from './config.js';
|
||||
import { dockerBuild, prepareBuildContext, staticCheck } from './lib/build.js';
|
||||
import { generateSpec } from './lib/claude.js';
|
||||
import { allocatePort, deployContainer, dockerAvailable, stopContainer } from './lib/deploy.js';
|
||||
import {
|
||||
allocatePort,
|
||||
computePublicUrl,
|
||||
deployContainer,
|
||||
dockerAvailable,
|
||||
stopContainer,
|
||||
} from './lib/deploy.js';
|
||||
import { emitDone, emitError, emitLog, emitStatus } from './lib/emit.js';
|
||||
import { renderServerCode } from './lib/render.js';
|
||||
|
||||
@ -156,7 +162,13 @@ export const worker = new Worker<JobData>(
|
||||
await emitStatus(buildId, 'deploying');
|
||||
|
||||
const port = await allocatePort();
|
||||
const publicUrl = `http://${config.RUNNER_HOST}:${port}`;
|
||||
// The container's PUBLIC_URL must match what end-users (and Claude
|
||||
// Desktop's DCR client) actually reach. When MCP_DOMAIN is set we
|
||||
// route via https://<MCP_DOMAIN>/<slug>; the hardcoded loopback URL
|
||||
// we used to inject caused the runner to advertise an unreachable
|
||||
// resource_metadata URL in its WWW-Authenticate header, killing OAuth
|
||||
// discovery from any external MCP client.
|
||||
const publicUrl = computePublicUrl(slug, port);
|
||||
const envVars: Record<string, string> = {
|
||||
...secrets,
|
||||
PUBLIC_URL: publicUrl,
|
||||
|
||||
@ -66,6 +66,13 @@ services:
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- bmm_keys:/app/apps/api/keys
|
||||
# Per-runner nginx snippets — written by the generator, deleted by the
|
||||
# api when a server is removed. The host-side systemd watcher combines
|
||||
# them into runner-map.combined + reloads nginx. Without this mount the
|
||||
# snippet files land in an ephemeral container path and the path-routed
|
||||
# /<slug>/* endpoints return 404 from nginx — breaking OAuth discovery
|
||||
# for every external MCP client.
|
||||
- /opt/buildmymcpserver/runner-map:/var/runner-map
|
||||
networks: [bmm-network]
|
||||
depends_on:
|
||||
postgres:
|
||||
@ -104,6 +111,9 @@ services:
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- bmm_build_context:/app/build-context
|
||||
# Same runner-map mount as the api — generator drops the snippet on
|
||||
# deploy, watcher concatenates, nginx reloads.
|
||||
- /opt/buildmymcpserver/runner-map:/var/runner-map
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
|
||||
Loading…
Reference in New Issue
Block a user