fix(oauth): resolve server by path segment, not subdomain
All checks were successful
Deploy to Production / deploy (push) Successful in 1m24s
All checks were successful
Deploy to Production / deploy (push) Successful in 1m24s
Claude Desktop got past discovery + DCR but /oauth/authorize rejected the resource parameter with invalid_resource. Root cause: resolveServerByResource() extracted the slug from the URL's first hostname label (subdomain routing), but production runs path routing — mcp.buildmymcpserver.com/<slug>. The function saw resource "https://mcp.buildmymcpserver.com/text-generation", tried to look up slug="mcp", missed, returned null → 400. Path lookup is now tried first (matches the production topology and the resource URL we publish via /.well-known/oauth-protected-resource), port lookup second (local dev), subdomain lookup last with an explicit "mcp" guard so the legacy path doesn't shadow the new one.
This commit is contained in:
parent
86cf89ef42
commit
1d845abf92
@ -40,16 +40,42 @@ function pkceVerify(verifier: string, challenge: string, method: string): boolea
|
||||
|
||||
async function resolveServerByResource(resource: string) {
|
||||
const url = new URL(resource);
|
||||
const port = url.port ? Number(url.port) : null;
|
||||
if (port !== null) {
|
||||
const [s] = await db.select().from(mcpServers).where(eq(mcpServers.hostPort, port)).limit(1);
|
||||
|
||||
// Path routing (the prod topology on mcp.buildmymcpserver.com): the slug
|
||||
// is the first path segment. Has to be checked BEFORE the subdomain
|
||||
// heuristic below, otherwise we extract "mcp" from "mcp.example.com/<slug>"
|
||||
// and look up the wrong (or no) server. Claude Desktop's RFC 8707 resource
|
||||
// parameter matches the `resource` field we publish in /.well-known/
|
||||
// oauth-protected-resource, which is exactly the path-routed public URL.
|
||||
const firstSegment = url.pathname.split('/').filter(Boolean)[0];
|
||||
if (firstSegment) {
|
||||
const [s] = await db
|
||||
.select()
|
||||
.from(mcpServers)
|
||||
.where(eq(mcpServers.slug, firstSegment))
|
||||
.limit(1);
|
||||
if (s) return s;
|
||||
}
|
||||
|
||||
// Port-based lookup — used in local dev where the runner is reached
|
||||
// directly at http://<RUNNER_HOST>:<port>.
|
||||
const port = url.port ? Number(url.port) : null;
|
||||
if (port !== null) {
|
||||
const [s] = await db
|
||||
.select()
|
||||
.from(mcpServers)
|
||||
.where(eq(mcpServers.hostPort, port))
|
||||
.limit(1);
|
||||
if (s) return s;
|
||||
}
|
||||
|
||||
// Subdomain routing — legacy / future <slug>.mcp.example.com setup.
|
||||
const slug = url.hostname.split('.')[0];
|
||||
if (slug) {
|
||||
if (slug && slug !== 'mcp') {
|
||||
const [s] = await db.select().from(mcpServers).where(eq(mcpServers.slug, slug)).limit(1);
|
||||
if (s) return s;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user