fix: mcp auth
This commit is contained in:
@@ -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"),
|
||||
|
||||
@@ -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"),
|
||||
});
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 }) => ({
|
||||
|
||||
Reference in New Issue
Block a user