fix: mcp auth

This commit is contained in:
johnyeo
2026-06-03 13:40:08 +01:00
parent 718e971e6f
commit 5a607170e5
4 changed files with 42 additions and 27 deletions

View File

@@ -9,6 +9,7 @@ const envSchema = z
.object({
AUTUMN_MCP_URL: z.string().min(1).default("http://localhost:3099/mcp"),
BETTER_AUTH_SECRET: optionalString,
BETTER_AUTH_URL: optionalString,
CHAT_MODEL: z.string().min(1).default("anthropic/claude-sonnet-4-6"),
CHAT_NAME: z.string().min(1).default("Autumn"),
CHAT_STATE_DATABASE_URL: optionalString,
@@ -18,7 +19,6 @@ const envSchema = z
ENCRYPTION_PASSWORD: z.string().min(1),
FIRECRAWL_API_KEY: z.string().min(1),
MCP_OAUTH_ENVIRONMENT: z.enum(["live", "sandbox"]).default("sandbox"),
MCP_SERVER_URL: optionalString,
PORT: z.coerce.number().int().positive().default(3099),
SLACK_CLIENT_ID: z.string().min(1),
SLACK_CLIENT_SECRET: z.string().min(1),
@@ -32,8 +32,8 @@ const envSchema = z
return {
...values,
MCP_SERVER_URL:
values.MCP_SERVER_URL ??
BETTER_AUTH_URL:
values.BETTER_AUTH_URL ??
(process.env.NODE_ENV === "production"
? "https://api.useautumn.com"
: "http://localhost:8080"),

View File

@@ -21,7 +21,7 @@ app.get("/health", (c) => c.json({ ok: true }));
registerMcpRoutes(app, {
"oauth-enabled": true,
"oauth-environment": env.MCP_OAUTH_ENVIRONMENT,
"server-url": env.MCP_SERVER_URL,
"server-url": env.BETTER_AUTH_URL,
logger: createConsoleLogger("info"),
});

View File

@@ -289,7 +289,9 @@ async function startDev() {
CHAT_PORT: CHAT_PORT.toString(),
MCP_DEBUG_PENDING_ACTIONS: process.env.MCP_DEBUG_PENDING_ACTIONS ?? "1",
MCP_SERVER_URL:
process.env.MCP_SERVER_URL ?? `http://localhost:${SERVER_PORT}`,
process.env.MCP_SERVER_URL ?? `http://localhost:${CHAT_PORT}`,
CHAT_SERVER_URL:
process.env.CHAT_SERVER_URL ?? `http://localhost:${CHAT_PORT}`,
MCP_RESOURCE_URLS:
process.env.MCP_RESOURCE_URLS ?? `http://localhost:${CHAT_PORT}/mcp`,
AUTUMN_MCP_URL:

View File

@@ -65,13 +65,8 @@ const emulateGoogleUrl =
// OAuth flow leaves and returns via a third-party host (emulate.dev), so the
// state cookie must be SameSite=None+Secure to survive the round trip.
const isHttpsBaseUrl = process.env.BETTER_AUTH_URL?.startsWith("https://");
const hostedMcpResourceUrls =
process.env.MCP_UPSTREAM_URL && process.env.BETTER_AUTH_URL
? [
new URL("/mcp", process.env.BETTER_AUTH_URL).href,
new URL("/internal/mcp", process.env.BETTER_AUTH_URL).href,
]
: [];
const isProductionAuth = process.env.NODE_ENV === "production";
const parseMcpResourceUrl = (rawUrl: string) => {
const resourceUrl = rawUrl.trim();
if (!resourceUrl) return null;
@@ -83,15 +78,36 @@ const parseMcpResourceUrl = (rawUrl: string) => {
return null;
}
};
const mcpResourceUrls =
process.env.MCP_RESOURCE_URLS?.split(",")
// Public hosts that serve OAuth-protected MCP endpoints. leaf serves both the
// MCP server (MCP_SERVER_URL) and the chat/slackbot (CHAT_SERVER_URL); the
// autumn server can also proxy /mcp under its own origin (BETTER_AUTH_URL).
// The OAuth `resource` indicator is host-based, so every public host + path
// must be a registered audience. MCP_RESOURCE_URLS is an explicit override.
const mcpServerUrl =
process.env.MCP_SERVER_URL ??
(isProductionAuth ? "https://mcp.useautumn.com" : "http://localhost:3099");
const chatServerUrl =
process.env.CHAT_SERVER_URL ??
(isProductionAuth ? "https://chat.useautumn.com" : "http://localhost:3099");
const mcpResourcePaths = ["/mcp", "/internal/mcp"];
const mcpResourceBases = [
process.env.BETTER_AUTH_URL,
mcpServerUrl,
chatServerUrl,
].filter((base): base is string => Boolean(base));
const mcpResourceUrls = [
...new Set([
...mcpResourceBases.flatMap((base) =>
mcpResourcePaths.map((path) => new URL(path, base).href),
),
...(process.env.MCP_RESOURCE_URLS?.split(",")
.map(parseMcpResourceUrl)
.filter((url): url is string => Boolean(url)) ?? [];
const internalMcpResourceUrls = mcpResourceUrls.map((resourceUrl) => {
const url = new URL(resourceUrl);
url.pathname = "/internal/mcp";
return url.href;
});
.filter((url): url is string => Boolean(url)) ?? []),
]),
];
/**
* Passkey (WebAuthn) is bound to the FRONTEND origin where the browser calls
@@ -255,12 +271,9 @@ const options = {
// Resource-based scopes with R/W actions (plus legacy CRUDL +
// meta scopes — see shared/utils/scopeDefinitions.ts).
scopes: [...ALL_SCOPES],
validAudiences: [
process.env.BETTER_AUTH_URL,
...hostedMcpResourceUrls,
...mcpResourceUrls,
...internalMcpResourceUrls,
].filter(Boolean) as string[],
validAudiences: [process.env.BETTER_AUTH_URL, ...mcpResourceUrls].filter(
Boolean,
) as string[],
allowDynamicClientRegistration: true,
allowUnauthenticatedClientRegistration: true,
customAccessTokenClaims: ({ referenceId }) => ({