chore: refactored mcp auth... again

This commit is contained in:
johnyeo
2026-06-05 17:50:17 +01:00
committed by Charlie Lamb
parent aa638063d5
commit 4e8c26da0a
34 changed files with 586 additions and 452 deletions

View File

@@ -1,6 +1,6 @@
# Autumn Leaf # Autumn Leaf
Autumn's AI service: the Slack chat bot plus the hosted MCP routes (`src/mcp/http.ts`). Autumn's AI service: the Slack chat bot plus the hosted MCP routes (`src/mcp/mcpRouter.ts`).
Local Slack testing uses a normal Slack app in a development workspace. Keep the app undistributed while testing. Local Slack testing uses a normal Slack app in a development workspace. Keep the app undistributed while testing.

View File

@@ -10,6 +10,7 @@
"ts": "tsc --noEmit" "ts": "tsc --noEmit"
}, },
"dependencies": { "dependencies": {
"@autumn/auth": "workspace:*",
"@autumn/logging": "workspace:*", "@autumn/logging": "workspace:*",
"@autumn/mcp": "workspace:*", "@autumn/mcp": "workspace:*",
"@autumn/shared": "workspace:*", "@autumn/shared": "workspace:*",

View File

@@ -4,7 +4,7 @@ import { Hono } from "hono";
import { chatAdapterNames } from "./bot.js"; import { chatAdapterNames } from "./bot.js";
import { env } from "./lib/env.js"; import { env } from "./lib/env.js";
import { logger } from "./lib/logger.js"; import { logger } from "./lib/logger.js";
import { registerMcpRoutes } from "./mcp/http.js"; import { createMcpRouter } from "./mcp/mcpRouter.js";
import { slackRoutes } from "./providers/slack/routes.js"; import { slackRoutes } from "./providers/slack/routes.js";
const app = new Hono<{ Bindings: HttpBindings }>(); const app = new Hono<{ Bindings: HttpBindings }>();
@@ -18,12 +18,16 @@ app.use("*", async (c, next) => {
app.get("/health", (c) => c.json({ ok: true })); app.get("/health", (c) => c.json({ ok: true }));
registerMcpRoutes(app, { app.route(
"oauth-enabled": true, "",
"oauth-environment": env.MCP_OAUTH_ENVIRONMENT, createMcpRouter({
"server-url": env.BETTER_AUTH_URL, "oauth-enabled": true,
logger, "oauth-environment": env.MCP_OAUTH_ENVIRONMENT,
}); "server-url": env.BETTER_AUTH_URL,
logger,
resourceUrl: new URL("/mcp", env.MCP_SERVER_URL).href,
}),
);
app.route("/slack", slackRoutes); app.route("/slack", slackRoutes);

View File

@@ -0,0 +1,34 @@
import {
getOAuthIssuerUrl,
} from "@autumn/auth/oauth";
import {
DEFAULT_AUTUMN_API_URL,
MCP_OAUTH_SCOPES,
} from "@autumn/mcp";
export class OAuthHttpError extends Error {
constructor(
readonly status: number,
message: string,
readonly error = "invalid_token",
readonly wwwAuthenticate?: string,
) {
super(message);
}
}
export const getProtectedResourceMetadata = ({
resourceUrl,
serverURL,
}: {
resourceUrl: string;
serverURL?: string;
}) => ({
resource: resourceUrl,
authorization_servers: [
getOAuthIssuerUrl({ baseUrl: serverURL ?? DEFAULT_AUTUMN_API_URL }),
],
scopes_supported: [...MCP_OAUTH_SCOPES],
bearer_methods_supported: ["header"],
resource_name: "Autumn MCP",
});

View File

@@ -0,0 +1,167 @@
import { createHash } from "node:crypto";
import { getBearerToken } from "@autumn/auth";
import {
getProtectedResourceMetadataUrl,
getWwwAuthenticateHeader,
} from "@autumn/auth/oauth";
import {
DEFAULT_API_VERSION,
MCP_OAUTH_SCOPES,
type AutumnMcpAuth,
environmentSchema,
type MCPServerFlags,
type OAuthEnvironment,
} from "@autumn/mcp";
import * as z from "zod/v4";
import { OAuthHttpError } from "./protectedResourceMetadata.js";
type AuthLogger = {
warning: (message: string, data?: Record<string, unknown>) => void;
};
export interface MCPOAuthFlags extends MCPServerFlags {
readonly "oauth-enabled"?: boolean | undefined;
readonly "oauth-environment"?: OAuthEnvironment | undefined;
}
const xApiVersionSchema = z.string().default(DEFAULT_API_VERSION);
const secretKeySchema = z.string().min(1).optional();
const failOpenSchema = z
.union([
z.boolean(),
z.enum(["true", "false"]).transform((v) => v === "true"),
])
.default(true);
const parseRequestOption = <T>({
value,
schema,
message,
}: {
value: unknown;
schema: z.ZodType<T>;
message: string;
}): T => {
const parsed = schema.safeParse(value);
if (parsed.success) return parsed.data;
throw new OAuthHttpError(400, message, "invalid_request");
};
const getEnvironment = ({
headers,
flags,
}: {
headers: Headers;
flags: MCPOAuthFlags;
}): OAuthEnvironment =>
parseRequestOption({
value:
headers.get("x-autumn-environment") ??
flags["oauth-environment"] ??
"sandbox",
schema: environmentSchema,
message: "Invalid x-autumn-environment",
});
const getStaticApiKey = ({
headers,
flags,
}: {
headers: Headers;
flags: MCPOAuthFlags;
}): string | undefined => {
const secretKey = headers.get("secret-key");
if (secretKey) return secretKey;
const bearer = getBearerToken({ headers });
if (bearer?.startsWith("am_")) return bearer;
return flags["oauth-enabled"] ? undefined : flags["secret-key"];
};
const principalFromSecret = ({
kind,
value,
}: {
kind: string;
value: string;
}) => {
const digest = createHash("sha256").update(value).digest("hex").slice(0, 32);
return `${kind}:${digest}`;
};
export const buildAuthForRequest = async ({
headers,
flags,
logger,
resourceUrl,
}: {
headers: Headers;
flags: MCPOAuthFlags;
logger: AuthLogger;
resourceUrl: string;
}): Promise<AutumnMcpAuth> => {
const env = getEnvironment({ headers, flags });
const xApiVersion = parseRequestOption({
value: headers.get("x-api-version") ?? flags["x-api-version"],
schema: xApiVersionSchema,
message: "Invalid x-api-version",
});
const failOpen = parseRequestOption({
value: headers.get("fail-open") ?? flags["fail-open"],
schema: failOpenSchema,
message: "Invalid fail-open",
});
const apiKey = parseRequestOption({
value: getStaticApiKey({ headers, flags }),
schema: secretKeySchema,
message: "Invalid secret-key",
});
if (apiKey) {
return {
apiKey,
authMethod: "secret-key",
env,
resource: resourceUrl,
principalId: principalFromSecret({ kind: "secret-key", value: apiKey }),
scopes: [...MCP_OAUTH_SCOPES],
serverURL: flags["server-url"],
xApiVersion,
failOpen,
};
}
const bearer = getBearerToken({ headers });
if (bearer) {
return {
apiKey: bearer,
authMethod: "oauth",
env,
resource: resourceUrl,
principalId: "oauth:unverified",
scopes: [...MCP_OAUTH_SCOPES],
serverURL: flags["server-url"],
xApiVersion,
failOpen,
};
}
if (flags["oauth-enabled"]) {
throw new OAuthHttpError(
401,
"Missing Autumn API key bearer token",
"invalid_token",
getWwwAuthenticateHeader({
resourceMetadataUrl: getProtectedResourceMetadataUrl({
resourceUrl,
}),
error: "invalid_token",
}),
);
}
logger.warning("Missing secret-key for MCP request");
throw new OAuthHttpError(401, "Missing secret-key", "invalid_token");
};

View File

@@ -0,0 +1,3 @@
export const MCP_PATH = "/mcp" as const;
export const PROTECTED_RESOURCE_METADATA_PATH =
"/.well-known/oauth-protected-resource/mcp";

View File

@@ -0,0 +1,71 @@
import { randomUUID } from "node:crypto";
import {
type createAutumnOperationsMCPServer,
} from "@autumn/mcp";
import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
import {
buildAuthForRequest,
} from "../auth/resolveRequestAuth.js";
import { OAuthHttpError } from "../auth/protectedResourceMetadata.js";
import type { LeafMcpContext, McpRouteOptions } from "../types.js";
type McpServer = ReturnType<typeof createAutumnOperationsMCPServer>;
type McpAuth = Awaited<ReturnType<typeof buildAuthForRequest>>;
const setIncomingAuth = ({
c,
auth,
}: {
c: LeafMcpContext;
auth: McpAuth;
}) => {
(c.env.incoming as typeof c.env.incoming & { auth?: McpAuth }).auth = auth;
};
const oauthErrorResponse = (c: LeafMcpContext, error: OAuthHttpError) => {
if (error.wwwAuthenticate) {
c.header("WWW-Authenticate", error.wwwAuthenticate);
}
return c.json(
{ error: error.error, error_description: error.message },
{ status: error.status as 400 | 401 | 403 },
);
};
export const createHandleMcp =
({
options,
path,
server,
}: {
options: McpRouteOptions;
path: string;
server: McpServer;
}) =>
async (c: LeafMcpContext) => {
let auth: McpAuth;
try {
auth = await buildAuthForRequest({
headers: c.req.raw.headers,
flags: options,
logger: options.logger,
resourceUrl: options.resourceUrl,
});
} catch (error) {
if (error instanceof OAuthHttpError) {
return oauthErrorResponse(c, error);
}
throw error;
}
setIncomingAuth({ c, auth });
await server.startHTTP({
url: new URL(c.req.url),
httpPath: path,
req: c.env.incoming,
res: c.env.outgoing,
options: { sessionIdGenerator: randomUUID },
});
return RESPONSE_ALREADY_SENT;
};

View File

@@ -0,0 +1,12 @@
import { getProtectedResourceMetadata } from "../auth/protectedResourceMetadata.js";
import type { LeafMcpContext, McpRouteOptions } from "../types.js";
export const createHandleProtectedResourceMetadata =
({ options }: { options: McpRouteOptions }) =>
(c: LeafMcpContext) =>
c.json(
getProtectedResourceMetadata({
resourceUrl: options.resourceUrl,
serverURL: options["server-url"],
}),
);

View File

@@ -1,78 +0,0 @@
import { randomUUID } from "node:crypto";
import type { AutumnLogger } from "@autumn/logging";
import {
buildAuthForRequest,
createAutumnOperationsMCPServer,
getAuthorizationServerMetadata,
getProtectedResourceMetadata,
type MCPServerFlags,
type OAuthEnvironment,
OAuthHttpError,
} from "@autumn/mcp";
import type { HttpBindings } from "@hono/node-server";
import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
import type { Context, Hono } from "hono";
export interface McpRouteOptions extends MCPServerFlags {
readonly "oauth-enabled": boolean;
readonly "oauth-environment": OAuthEnvironment;
readonly logger: AutumnLogger;
}
type AppContext = Context<{ Bindings: HttpBindings }>;
type McpPath = "/mcp";
type McpApp = Hono<{ Bindings: HttpBindings }>;
export function registerMcpRoutes(app: McpApp, options: McpRouteOptions) {
const mcpServer = createAutumnOperationsMCPServer();
app.get("/.well-known/oauth-protected-resource/mcp", (c) =>
c.json(getProtectedResourceMetadata(c.req.raw.headers, options, "/mcp")),
);
app.get("/.well-known/oauth-authorization-server", (c) =>
c.json(getAuthorizationServerMetadata(options)),
);
const handleMcp = async (
c: AppContext,
path: McpPath,
server: ReturnType<typeof createAutumnOperationsMCPServer>,
) => {
let auth: Awaited<ReturnType<typeof buildAuthForRequest>>;
try {
auth = await buildAuthForRequest(
c.req.raw.headers,
options,
options.logger,
path,
);
} catch (error) {
if (error instanceof OAuthHttpError) {
if (error.wwwAuthenticate) {
c.header("WWW-Authenticate", error.wwwAuthenticate);
}
return c.json(
{ error: error.error, error_description: error.message },
{ status: error.status as 401 | 403 },
);
}
throw error;
}
(c.env.incoming as typeof c.env.incoming & { auth?: typeof auth }).auth =
auth;
await server.startHTTP({
url: new URL(c.req.url),
httpPath: path,
req: c.env.incoming,
res: c.env.outgoing,
options: { sessionIdGenerator: randomUUID },
});
return RESPONSE_ALREADY_SENT;
};
app.all("/mcp", (c) => handleMcp(c, "/mcp", mcpServer));
return app;
}

View File

@@ -0,0 +1,29 @@
import { createAutumnOperationsMCPServer } from "@autumn/mcp";
import type { HttpBindings } from "@hono/node-server";
import { Hono } from "hono";
import { MCP_PATH, PROTECTED_RESOURCE_METADATA_PATH } from "./constants.js";
import { createHandleMcp } from "./handlers/handleMcp.js";
import { createHandleProtectedResourceMetadata } from "./handlers/handleProtectedResourceMetadata.js";
import type { McpRouteOptions } from "./types.js";
export const createMcpRouter = (options: McpRouteOptions) => {
const router = new Hono<{ Bindings: HttpBindings }>();
const mcpServer = createAutumnOperationsMCPServer();
router.get(
PROTECTED_RESOURCE_METADATA_PATH,
createHandleProtectedResourceMetadata({
options,
}),
);
router.all(
MCP_PATH,
createHandleMcp({
options,
path: MCP_PATH,
server: mcpServer,
}),
);
return router;
};

View File

@@ -0,0 +1,15 @@
import type { AutumnLogger } from "@autumn/logging";
import type { MCPServerFlags, OAuthEnvironment } from "@autumn/mcp";
import type { HttpBindings } from "@hono/node-server";
import type { Context, Hono } from "hono";
export interface McpRouteOptions extends MCPServerFlags {
readonly "oauth-enabled": boolean;
readonly "oauth-environment": OAuthEnvironment;
readonly logger: AutumnLogger;
readonly resourceUrl: string;
}
export type LeafMcpContext = Context<{ Bindings: HttpBindings }>;
export type LeafMcpRouter = Hono<{ Bindings: HttpBindings }>;
export type { MCPOAuthFlags } from "./auth/resolveRequestAuth.js";

View File

@@ -1,12 +1,14 @@
import { describe, expect, test } from "bun:test"; import { describe, expect, test } from "bun:test";
import { MCP_OAUTH_SCOPES } from "@autumn/mcp";
import { Scopes } from "@autumn/shared/scopeDefinitions"; import { Scopes } from "@autumn/shared/scopeDefinitions";
import { import {
buildAuthForRequest, buildAuthForRequest,
getProtectedResourceMetadata,
MCP_OAUTH_SCOPES,
type MCPOAuthFlags, type MCPOAuthFlags,
} from "../../../src/mcp/auth/resolveRequestAuth.js";
import {
getProtectedResourceMetadata,
type OAuthHttpError, type OAuthHttpError,
} from "../../../src/server/auth/oauth.js"; } from "../../../src/mcp/auth/protectedResourceMetadata.js";
const flags = { const flags = {
"oauth-enabled": true, "oauth-enabled": true,
@@ -18,6 +20,9 @@ const logger = {
warning: () => {}, warning: () => {},
} as never; } as never;
const resourceUrl = "http://localhost:2718/mcp";
const internalResourceUrl = "http://localhost:2718/internal/mcp";
describe("MCP OAuth auth resolution", () => { describe("MCP OAuth auth resolution", () => {
test("requests scopes required by public write tools", () => { test("requests scopes required by public write tools", () => {
expect(MCP_OAUTH_SCOPES).toEqual( expect(MCP_OAUTH_SCOPES).toEqual(
@@ -32,11 +37,12 @@ describe("MCP OAuth auth resolution", () => {
test("returns a WWW-Authenticate challenge without a bearer token", async () => { test("returns a WWW-Authenticate challenge without a bearer token", async () => {
await expect( await expect(
buildAuthForRequest( buildAuthForRequest({
new Headers({ host: "localhost:2718" }), headers: new Headers(),
flags as MCPOAuthFlags, flags: flags as MCPOAuthFlags,
logger, logger,
), resourceUrl,
}),
).rejects.toMatchObject({ ).rejects.toMatchObject({
status: 401, status: 401,
error: "invalid_token", error: "invalid_token",
@@ -47,12 +53,12 @@ describe("MCP OAuth auth resolution", () => {
test("returns an internal MCP resource challenge", async () => { test("returns an internal MCP resource challenge", async () => {
await expect( await expect(
buildAuthForRequest( buildAuthForRequest({
new Headers({ host: "localhost:2718" }), headers: new Headers(),
flags as MCPOAuthFlags, flags: flags as MCPOAuthFlags,
logger, logger,
"/internal/mcp", resourceUrl: internalResourceUrl,
), }),
).rejects.toMatchObject({ ).rejects.toMatchObject({
status: 401, status: 401,
error: "invalid_token", error: "invalid_token",
@@ -61,7 +67,7 @@ describe("MCP OAuth auth resolution", () => {
} satisfies Partial<OAuthHttpError>); } satisfies Partial<OAuthHttpError>);
}); });
test("rejects opaque bearer tokens without exchanging them", async () => { test("passes OAuth bearer tokens through without local verification", async () => {
const originalFetch = globalThis.fetch; const originalFetch = globalThis.fetch;
let fetchCalled = false; let fetchCalled = false;
const mockFetch = (async () => { const mockFetch = (async () => {
@@ -71,19 +77,23 @@ describe("MCP OAuth auth resolution", () => {
globalThis.fetch = mockFetch; globalThis.fetch = mockFetch;
try { try {
await expect( const auth = await buildAuthForRequest({
buildAuthForRequest( headers: new Headers({
new Headers({ authorization: "Bearer oauth_token",
authorization: "Bearer oauth_token", }),
host: "localhost:2718", flags: flags as MCPOAuthFlags,
}), logger,
flags as MCPOAuthFlags, resourceUrl,
logger, });
),
).rejects.toMatchObject({ expect(auth).toMatchObject({
status: 401, apiKey: "oauth_token",
error: "invalid_token", authMethod: "oauth",
} satisfies Partial<OAuthHttpError>); env: "sandbox",
principalId: "oauth:unverified",
resource: "http://localhost:2718/mcp",
serverURL: "http://localhost:8080",
});
expect(fetchCalled).toBe(false); expect(fetchCalled).toBe(false);
} finally { } finally {
globalThis.fetch = originalFetch; globalThis.fetch = originalFetch;
@@ -91,14 +101,14 @@ describe("MCP OAuth auth resolution", () => {
}); });
test("accepts a static secret-key when OAuth is enabled", async () => { test("accepts a static secret-key when OAuth is enabled", async () => {
const auth = await buildAuthForRequest( const auth = await buildAuthForRequest({
new Headers({ headers: new Headers({
host: "localhost:2718",
"secret-key": "am_sk_test_chat", "secret-key": "am_sk_test_chat",
}), }),
flags as MCPOAuthFlags, flags: flags as MCPOAuthFlags,
logger, logger,
); resourceUrl,
});
expect(auth.apiKey).toBe("am_sk_test_chat"); expect(auth.apiKey).toBe("am_sk_test_chat");
expect(auth.principalId).toStartWith("secret-key:"); expect(auth.principalId).toStartWith("secret-key:");
@@ -106,50 +116,49 @@ describe("MCP OAuth auth resolution", () => {
}); });
test("accepts an Autumn API key bearer token when OAuth is enabled", async () => { test("accepts an Autumn API key bearer token when OAuth is enabled", async () => {
const auth = await buildAuthForRequest( const auth = await buildAuthForRequest({
new Headers({ headers: new Headers({
authorization: "Bearer am_sk_test_chat", authorization: "Bearer am_sk_test_chat",
host: "localhost:2718",
}), }),
flags as MCPOAuthFlags, flags: flags as MCPOAuthFlags,
logger, logger,
); resourceUrl,
});
expect(auth.apiKey).toBe("am_sk_test_chat"); expect(auth.apiKey).toBe("am_sk_test_chat");
expect(auth.principalId).toStartWith("secret-key:"); expect(auth.principalId).toStartWith("secret-key:");
}); });
test("uses route-specific resource URLs", async () => { test("uses route-specific resource URLs", async () => {
const auth = await buildAuthForRequest( const auth = await buildAuthForRequest({
new Headers({ headers: new Headers({
authorization: "Bearer am_sk_test_chat", authorization: "Bearer am_sk_test_chat",
host: "localhost:2718",
}), }),
flags as MCPOAuthFlags, flags: flags as MCPOAuthFlags,
logger, logger,
"/internal/mcp", resourceUrl: internalResourceUrl,
); });
expect(auth.resource).toBe("http://localhost:2718/internal/mcp"); expect(auth.resource).toBe("http://localhost:2718/internal/mcp");
expect( expect(
getProtectedResourceMetadata( getProtectedResourceMetadata({
new Headers({ host: "localhost:2718" }), resourceUrl: internalResourceUrl,
flags as MCPOAuthFlags, serverURL: flags["server-url"],
"/internal/mcp", }).resource,
).resource,
).toBe("http://localhost:2718/internal/mcp"); ).toBe("http://localhost:2718/internal/mcp");
}); });
test("missing static secret-key returns the auth error path", async () => { test("missing static secret-key returns the auth error path", async () => {
await expect( await expect(
buildAuthForRequest( buildAuthForRequest({
new Headers({ host: "localhost:2718" }), headers: new Headers(),
{ flags: {
...flags, ...flags,
"oauth-enabled": false, "oauth-enabled": false,
} as MCPOAuthFlags, } as MCPOAuthFlags,
logger, logger,
), resourceUrl,
}),
).rejects.toMatchObject({ ).rejects.toMatchObject({
status: 401, status: 401,
error: "invalid_token", error: "invalid_token",

View File

@@ -87,6 +87,7 @@
"name": "@autumn/leaf", "name": "@autumn/leaf",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@autumn/auth": "workspace:*",
"@autumn/logging": "workspace:*", "@autumn/logging": "workspace:*",
"@autumn/mcp": "workspace:*", "@autumn/mcp": "workspace:*",
"@autumn/shared": "workspace:*", "@autumn/shared": "workspace:*",
@@ -241,6 +242,15 @@
"typescript": "^5", "typescript": "^5",
}, },
}, },
"packages/auth": {
"name": "@autumn/auth",
"version": "0.0.1",
"devDependencies": {
"@types/bun": "^1.2.13",
"@types/node": "^18.19.3",
"typescript": "~5.8.3",
},
},
"packages/autumn-js": { "packages/autumn-js": {
"name": "autumn-js", "name": "autumn-js",
"version": "1.2.17", "version": "1.2.17",
@@ -301,6 +311,7 @@
"name": "@autumn/mcp", "name": "@autumn/mcp",
"version": "0.0.1", "version": "0.0.1",
"dependencies": { "dependencies": {
"@autumn/auth": "workspace:*",
"@autumn/logging": "workspace:*", "@autumn/logging": "workspace:*",
"@autumn/shared": "workspace:*", "@autumn/shared": "workspace:*",
"@axiomhq/js": "^1.6.1", "@axiomhq/js": "^1.6.1",
@@ -395,6 +406,7 @@
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "^3.0.9", "@ai-sdk/anthropic": "^3.0.9",
"@anthropic-ai/sdk": "^0.32.1", "@anthropic-ai/sdk": "^0.32.1",
"@autumn/auth": "workspace:*",
"@autumn/ksuid": "workspace:*", "@autumn/ksuid": "workspace:*",
"@autumn/shared": "workspace:*", "@autumn/shared": "workspace:*",
"@autumn/stripe-sync": "workspace:*", "@autumn/stripe-sync": "workspace:*",
@@ -730,6 +742,8 @@
"@asyncapi/specs": ["@asyncapi/specs@6.8.1", "", { "dependencies": { "@types/json-schema": "^7.0.11" } }, "sha512-czHoAk3PeXTLR+X8IUaD+IpT+g+zUvkcgMDJVothBsan+oHN3jfcFcFUNdOPAAFoUCQN1hXF1dWuphWy05THlA=="], "@asyncapi/specs": ["@asyncapi/specs@6.8.1", "", { "dependencies": { "@types/json-schema": "^7.0.11" } }, "sha512-czHoAk3PeXTLR+X8IUaD+IpT+g+zUvkcgMDJVothBsan+oHN3jfcFcFUNdOPAAFoUCQN1hXF1dWuphWy05THlA=="],
"@autumn/auth": ["@autumn/auth@workspace:packages/auth"],
"@autumn/docs": ["@autumn/docs@workspace:apps/docs"], "@autumn/docs": ["@autumn/docs@workspace:apps/docs"],
"@autumn/ksuid": ["@autumn/ksuid@workspace:packages/ksuid"], "@autumn/ksuid": ["@autumn/ksuid@workspace:packages/ksuid"],
@@ -5724,7 +5738,7 @@
"typed-query-selector": ["typed-query-selector@2.12.2", "", {}, "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ=="], "typed-query-selector": ["typed-query-selector@2.12.2", "", {}, "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"typescript-eslint": ["typescript-eslint@8.59.4", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.4", "@typescript-eslint/parser": "8.59.4", "@typescript-eslint/typescript-estree": "8.59.4", "@typescript-eslint/utils": "8.59.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ=="], "typescript-eslint": ["typescript-eslint@8.59.4", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.59.4", "@typescript-eslint/parser": "8.59.4", "@typescript-eslint/typescript-estree": "8.59.4", "@typescript-eslint/utils": "8.59.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Rw6+44QNFaXtgHSjPy+Kw8hrJniMYzR85E9yLmOLcfZ91/rz+JXQbDTCmc6ccxMPY6K6PgAq26f0JCBfR7LIPQ=="],
@@ -6014,18 +6028,20 @@
"@asyncapi/parser/node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="], "@asyncapi/parser/node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="],
"@autumn/auth/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@autumn/leaf/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], "@autumn/leaf/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="],
"@autumn/leaf/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"@autumn/logging/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], "@autumn/logging/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@autumn/logging/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"@autumn/mcp/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="], "@autumn/mcp/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@autumn/mcp/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"@autumn/openapi/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="], "@autumn/openapi/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
"@autumn/scripts/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"@autumn/server/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="], "@autumn/server/@types/node": ["@types/node@25.8.0", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ=="],
"@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="], "@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="],
@@ -6034,8 +6050,12 @@
"@autumn/server/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="], "@autumn/server/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="],
"@autumn/server/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"@autumn/shared/@date-fns/utc": ["@date-fns/utc@2.1.0", "", {}, "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA=="], "@autumn/shared/@date-fns/utc": ["@date-fns/utc@2.1.0", "", {}, "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA=="],
"@autumn/shared/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"@autumn/vite/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="], "@autumn/vite/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="],
"@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="], "@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
@@ -6234,6 +6254,8 @@
"@infisical/sdk/@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.600.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.600.0", "@aws-sdk/client-sso": "3.598.0", "@aws-sdk/client-sts": "3.600.0", "@aws-sdk/credential-provider-cognito-identity": "3.600.0", "@aws-sdk/credential-provider-env": "3.598.0", "@aws-sdk/credential-provider-http": "3.598.0", "@aws-sdk/credential-provider-ini": "3.598.0", "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/credential-provider-process": "3.598.0", "@aws-sdk/credential-provider-sso": "3.598.0", "@aws-sdk/credential-provider-web-identity": "3.598.0", "@aws-sdk/types": "3.598.0", "@smithy/credential-provider-imds": "^3.1.1", "@smithy/property-provider": "^3.1.1", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-cC9uqmX0rgx1efiJGqeR+i0EXr8RQ5SAzH7M45WNBZpYiLEe6reWgIYJY9hmOxuaoMdWSi8kekuN3IjTIORRjw=="], "@infisical/sdk/@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.600.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.600.0", "@aws-sdk/client-sso": "3.598.0", "@aws-sdk/client-sts": "3.600.0", "@aws-sdk/credential-provider-cognito-identity": "3.600.0", "@aws-sdk/credential-provider-env": "3.598.0", "@aws-sdk/credential-provider-http": "3.598.0", "@aws-sdk/credential-provider-ini": "3.598.0", "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/credential-provider-process": "3.598.0", "@aws-sdk/credential-provider-sso": "3.598.0", "@aws-sdk/credential-provider-web-identity": "3.598.0", "@aws-sdk/types": "3.598.0", "@smithy/credential-provider-imds": "^3.1.1", "@smithy/property-provider": "^3.1.1", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-cC9uqmX0rgx1efiJGqeR+i0EXr8RQ5SAzH7M45WNBZpYiLEe6reWgIYJY9hmOxuaoMdWSi8kekuN3IjTIORRjw=="],
"@infisical/sdk/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], "@inquirer/core/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
"@inquirer/external-editor/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], "@inquirer/external-editor/iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
@@ -6946,8 +6968,6 @@
"@typespec/ts-http-runtime/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], "@typespec/ts-http-runtime/https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
"@useautumn/sdk/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
"@useautumn/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "@useautumn/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"@vercel/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "@vercel/sdk/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
@@ -7008,6 +7028,8 @@
"atmn/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="], "atmn/react": ["react@19.2.4", "", {}, "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ=="],
"atmn/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"atmn/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "atmn/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"autumn-js/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="], "autumn-js/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="],
@@ -7018,6 +7040,8 @@
"autumn-js/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "autumn-js/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
"autumn-js/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"autumn-js/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "autumn-js/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"ava/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="], "ava/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
@@ -7082,6 +7106,8 @@
"checkout/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="], "checkout/react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
"checkout/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
"clean-regexp/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], "clean-regexp/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
@@ -7544,6 +7570,8 @@
"sdk-test/react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="], "sdk-test/react-dom": ["react-dom@19.2.3", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.3" } }, "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg=="],
"sdk-test/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="], "send/mime": ["mime@1.6.0", "", { "bin": { "mime": "cli.js" } }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
@@ -7626,6 +7654,8 @@
"ts-to-zod/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "ts-to-zod/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"ts-to-zod/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"ts-to-zod/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "ts-to-zod/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="],
"tsc-alias/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="], "tsc-alias/commander": ["commander@9.5.0", "", {}, "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ=="],
@@ -7634,6 +7664,8 @@
"tshy/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "tshy/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"tshy/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"tsup/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "tsup/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"tsutils/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="], "tsutils/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
@@ -7788,6 +7820,8 @@
"@asyncapi/parser/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], "@asyncapi/parser/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"@autumn/auth/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"@autumn/leaf/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], "@autumn/leaf/@types/node/undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
"@autumn/logging/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], "@autumn/logging/@types/node/undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],

View File

@@ -27,6 +27,7 @@ COPY apps/sdk-test/package.json apps/sdk-test/
COPY apps/website/package.json apps/website/ COPY apps/website/package.json apps/website/
COPY packages/atmn/package.json packages/atmn/ COPY packages/atmn/package.json packages/atmn/
COPY packages/atmn-tests/package.json packages/atmn-tests/ COPY packages/atmn-tests/package.json packages/atmn-tests/
COPY packages/auth/package.json packages/auth/
COPY packages/autumn-js/package.json packages/autumn-js/ COPY packages/autumn-js/package.json packages/autumn-js/
COPY packages/ksuid/package.json packages/ksuid/ COPY packages/ksuid/package.json packages/ksuid/
COPY packages/logging/package.json packages/logging/ COPY packages/logging/package.json packages/logging/

View File

@@ -15,6 +15,7 @@
"apps/sdk-test", "apps/sdk-test",
"packages/atmn", "packages/atmn",
"packages/atmn-tests", "packages/atmn-tests",
"packages/auth",
"packages/logging", "packages/logging",
"packages/mcp", "packages/mcp",
"packages/sdk", "packages/sdk",
@@ -137,7 +138,7 @@
"site": "cd apps/website && bun dev && cd ../..", "site": "cd apps/website && bun dev && cd ../..",
"docs": "bun -F @autumn/docs dev", "docs": "bun -F @autumn/docs dev",
"docs:build": "bun -F @autumn/docs build", "docs:build": "bun -F @autumn/docs build",
"ts": "turbo run ts --filter=@autumn/server --filter=autumn-js --filter=@autumn/openapi --filter=atmn --filter=checkout --filter=@autumn/mcp --filter=@autumn/leaf", "ts": "turbo run ts --filter=@autumn/server --filter=autumn-js --filter=@autumn/openapi --filter=atmn --filter=checkout --filter=@autumn/auth --filter=@autumn/mcp --filter=@autumn/leaf",
"kill:ts": "while pgrep -f tsgo > /dev/null; do pkill -9 -f tsgo; sleep 0.1; done", "kill:ts": "while pgrep -f tsgo > /dev/null; do pkill -9 -f tsgo; sleep 0.1; done",
"atmn:build": "bun -F atmn build", "atmn:build": "bun -F atmn build",
"openapi:ts": "bun -F @autumn/openapi ts", "openapi:ts": "bun -F @autumn/openapi ts",

View File

@@ -0,0 +1,36 @@
{
"name": "@autumn/auth",
"version": "0.0.1",
"author": "Autumn",
"type": "module",
"sideEffects": false,
"exports": {
".": {
"types": "./src/index.ts",
"import": "./src/index.ts",
"default": "./src/index.ts"
},
"./utils": {
"types": "./src/utils/index.ts",
"import": "./src/utils/index.ts",
"default": "./src/utils/index.ts"
},
"./oauth": {
"types": "./src/oauth/index.ts",
"import": "./src/oauth/index.ts",
"default": "./src/oauth/index.ts"
}
},
"files": ["src"],
"scripts": {
"build": "tsc",
"ts": "tsc --noEmit",
"prepack": "bun run build",
"prepublishOnly": "bun run build"
},
"devDependencies": {
"@types/bun": "^1.2.13",
"@types/node": "^18.19.3",
"typescript": "~5.8.3"
}
}

View File

@@ -0,0 +1,2 @@
export * from "./oauth/index.js";
export * from "./utils/index.js";

View File

@@ -0,0 +1 @@
export * from "./oauthUrls.js";

View File

@@ -0,0 +1,32 @@
const trimTrailingSlash = (url: string) =>
url.endsWith("/") ? url.slice(0, -1) : url;
export const getOAuthIssuerUrl = ({
authPath = "/api/auth",
baseUrl,
}: {
authPath?: string;
baseUrl: string;
}): string => trimTrailingSlash(new URL(authPath, baseUrl).href);
export const getProtectedResourceMetadataUrl = ({
resourceUrl,
}: {
resourceUrl: string;
}): string => {
const url = new URL(resourceUrl);
const path = url.pathname === "/" ? "" : url.pathname;
return new URL(`/.well-known/oauth-protected-resource${path}`, url).href;
};
export const getWwwAuthenticateHeader = ({
error,
resourceMetadataUrl,
}: {
error?: string;
resourceMetadataUrl: string;
}): string => {
const params = [`resource_metadata="${resourceMetadataUrl}"`];
if (error) params.push(`error="${error}"`);
return `Bearer ${params.join(", ")}`;
};

View File

@@ -0,0 +1,13 @@
const BEARER_PREFIX = "Bearer ";
export const getBearerToken = ({
headers,
}: {
headers: Headers;
}): string | undefined => {
const authorization = headers.get("authorization");
if (!authorization?.startsWith(BEARER_PREFIX)) return undefined;
const token = authorization.slice(BEARER_PREFIX.length).trim();
return token.length ? token : undefined;
};

View File

@@ -0,0 +1 @@
export * from "./getBearerToken.js";

View File

@@ -0,0 +1,34 @@
{
"compilerOptions": {
"allowJs": true,
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"checkJs": true,
"declaration": true,
"declarationMap": true,
"esModuleInterop": true,
"exactOptionalPropertyTypes": false,
"forceConsistentCasingInFileNames": true,
"incremental": false,
"isolatedModules": true,
"lib": ["dom", "dom.iterable", "es2024"],
"module": "Preserve",
"moduleResolution": "bundler",
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": false,
"noImplicitReturns": false,
"noPropertyAccessFromIndexSignature": false,
"noUncheckedIndexedAccess": false,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noEmit": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"target": "es2022",
"types": ["bun", "node"],
"useUnknownInCatchVariables": true
},
"exclude": ["node_modules"],
"include": ["src/**/*.ts"]
}

View File

@@ -2,7 +2,7 @@
Mastra-backed MCP library for Autumn operations. Mastra-backed MCP library for Autumn operations.
The hosted runtime lives in `apps/leaf` (see `src/mcp/http.ts`) and exposes a The hosted runtime lives in `apps/leaf` (see `src/mcp/mcpRouter.ts`) and exposes a
Streamable HTTP MCP route: Streamable HTTP MCP route:
- `/mcp` - public, API-shaped operational tools. - `/mcp` - public, API-shaped operational tools.

View File

@@ -20,8 +20,9 @@
"prepublishOnly": "bun run build" "prepublishOnly": "bun run build"
}, },
"dependencies": { "dependencies": {
"@autumn/auth": "workspace:*",
"@autumn/logging": "workspace:*", "@autumn/logging": "workspace:*",
"@autumn/shared": "workspace:*", "@autumn/shared": "workspace:*",
"@axiomhq/js": "^1.6.1", "@axiomhq/js": "^1.6.1",
"@mastra/core": "^1.36.0", "@mastra/core": "^1.36.0",
"@mastra/mcp": "^1.8.0", "@mastra/mcp": "^1.8.0",

View File

@@ -14,11 +14,14 @@ export {
createConsoleLogger, createConsoleLogger,
} from "./console-logger.js"; } from "./console-logger.js";
export { export {
buildAuthForRequest, DEFAULT_API_VERSION,
getAuthorizationServerMetadata, DEFAULT_AUTUMN_API_URL,
getProtectedResourceMetadata, MCP_OAUTH_SCOPES,
} from "./constants.js";
export {
type AutumnMcpAuth,
environmentSchema,
type OAuthEnvironment, type OAuthEnvironment,
OAuthHttpError, } from "./server/auth/auth.js";
} from "./server/auth/oauth.js";
export type { MCPServerFlags } from "./server/flags.js"; export type { MCPServerFlags } from "./server/flags.js";
export { createAutumnOperationsMCPServer } from "./server/server.js"; export { createAutumnOperationsMCPServer } from "./server/server.js";

View File

@@ -4,7 +4,9 @@ import {
DEFAULT_API_VERSION, DEFAULT_API_VERSION,
DEFAULT_AUTUMN_API_URL, DEFAULT_AUTUMN_API_URL,
} from "../../constants.js"; } from "../../constants.js";
import { environmentSchema } from "./utils/schemas.js";
export const environmentSchema = z.enum(["sandbox", "live"]);
export type OAuthEnvironment = z.infer<typeof environmentSchema>;
/** /**
* Authenticated Autumn identity attached to every MCP request. Defined as a zod * Authenticated Autumn identity attached to every MCP request. Defined as a zod
@@ -13,6 +15,7 @@ import { environmentSchema } from "./utils/schemas.js";
*/ */
export const autumnMcpAuthSchema = z.object({ export const autumnMcpAuthSchema = z.object({
apiKey: z.string().min(1), apiKey: z.string().min(1),
authMethod: z.enum(["secret-key", "oauth"]).optional(),
env: environmentSchema, env: environmentSchema,
principalId: z.string(), principalId: z.string(),
resource: z.string(), resource: z.string(),

View File

@@ -1,112 +0,0 @@
import { MCP_OAUTH_SCOPES } from "../../constants.js";
import type { AutumnMcpAuth } from "./auth.js";
import { OAuthHttpError } from "./utils/errors.js";
import { principalFromSecret } from "./utils/principal.js";
import {
getEnvironment,
getStaticApiKey,
parseRequestOption,
} from "./utils/request.js";
import {
failOpenSchema,
type MCPOAuthFlags,
secretKeySchema,
xApiVersionSchema,
} from "./utils/schemas.js";
import {
getIssuerUrl,
getResourceUrl,
getWWWAuthenticate,
} from "./utils/urls.js";
// Public surface consumed via `./oauth.js` (index.ts, leaf, tests).
export { MCP_OAUTH_SCOPES } from "../../constants.js";
export { OAuthHttpError } from "./utils/errors.js";
export type { MCPOAuthFlags, OAuthEnvironment } from "./utils/schemas.js";
type AuthLogger = {
warning: (message: string, data?: Record<string, unknown>) => void;
};
export const getProtectedResourceMetadata = (
headers: Headers,
flags: MCPOAuthFlags,
resourcePath = "/mcp",
) => ({
resource: getResourceUrl({ headers, resourcePath }),
authorization_servers: [getIssuerUrl(flags)],
scopes_supported: [...MCP_OAUTH_SCOPES],
bearer_methods_supported: ["header"],
resource_name: "Autumn MCP",
});
export const getAuthorizationServerMetadata = (flags: MCPOAuthFlags) => {
const issuer = getIssuerUrl(flags);
return {
issuer,
authorization_endpoint: `${issuer}/oauth2/authorize`,
token_endpoint: `${issuer}/oauth2/token`,
registration_endpoint: `${issuer}/oauth2/register`,
revocation_endpoint: `${issuer}/oauth2/revoke`,
introspection_endpoint: `${issuer}/oauth2/introspect`,
response_types_supported: ["code"],
grant_types_supported: ["authorization_code", "refresh_token"],
token_endpoint_auth_methods_supported: [
"client_secret_post",
"client_secret_basic",
"none",
],
code_challenge_methods_supported: ["S256"],
scopes_supported: [...MCP_OAUTH_SCOPES],
};
};
export const buildAuthForRequest = async (
headers: Headers,
flags: MCPOAuthFlags,
logger: AuthLogger,
resourcePath = "/mcp",
): Promise<AutumnMcpAuth> => {
const env = getEnvironment({ headers, flags });
const resource = getResourceUrl({ headers, resourcePath });
const xApiVersion = parseRequestOption({
value: headers.get("x-api-version") ?? flags["x-api-version"],
schema: xApiVersionSchema,
message: "Invalid x-api-version",
});
const failOpen = parseRequestOption({
value: headers.get("fail-open") ?? flags["fail-open"],
schema: failOpenSchema,
message: "Invalid fail-open",
});
const apiKey = parseRequestOption({
value: getStaticApiKey({ headers, flags }),
schema: secretKeySchema,
message: "Invalid secret-key",
});
if (apiKey) {
return {
apiKey,
env,
resource,
principalId: principalFromSecret({ kind: "secret-key", value: apiKey }),
scopes: [...MCP_OAUTH_SCOPES],
serverURL: flags["server-url"],
xApiVersion,
failOpen,
};
}
if (flags["oauth-enabled"]) {
throw new OAuthHttpError(
401,
"Missing Autumn API key bearer token",
"invalid_token",
getWWWAuthenticate({ resourceUrl: resource, error: "invalid_token" }),
);
}
logger.warning("Missing secret-key for MCP request");
throw new OAuthHttpError(401, "Missing secret-key", "invalid_token");
};

View File

@@ -1,15 +0,0 @@
/**
* Error carrying the HTTP status and OAuth metadata the MCP HTTP layer needs to
* build a spec-compliant `WWW-Authenticate` response. Lives in its own module so
* both the request helpers and the OAuth flow can throw it without import cycles.
*/
export class OAuthHttpError extends Error {
constructor(
readonly status: number,
message: string,
readonly error = "invalid_token",
readonly wwwAuthenticate?: string,
) {
super(message);
}
}

View File

@@ -1,17 +0,0 @@
import { createHash } from "node:crypto";
/** Short, stable digest used to anonymise secrets inside principal ids. */
const hash = (value: string) =>
createHash("sha256").update(value).digest("hex").slice(0, 32);
/**
* Builds a principal id from a secret without leaking it, e.g.
* `secret-key:<digest>`.
*/
export const principalFromSecret = ({
kind,
value,
}: {
kind: string;
value: string;
}) => `${kind}:${hash(value)}`;

View File

@@ -1,67 +0,0 @@
import type * as z from "zod/v4";
import { OAuthHttpError } from "./errors.js";
import {
environmentSchema,
type MCPOAuthFlags,
type OAuthEnvironment,
} from "./schemas.js";
/**
* Validates a request-derived value against a schema, surfacing a 400 with a
* caller-supplied message instead of zod's default error shape.
*/
export const parseRequestOption = <T>({
value,
schema,
message,
}: {
value: unknown;
schema: z.ZodType<T>;
message: string;
}): T => {
const parsed = schema.safeParse(value);
if (parsed.success) return parsed.data;
throw new OAuthHttpError(400, message, "invalid_request");
};
/** Resolves the Autumn environment from the request header, then the flag. */
export const getEnvironment = ({
headers,
flags,
}: {
headers: Headers;
flags: MCPOAuthFlags;
}): OAuthEnvironment =>
parseRequestOption({
value:
headers.get("x-autumn-environment") ??
flags["oauth-environment"] ??
"sandbox",
schema: environmentSchema,
message: "Invalid x-autumn-environment",
});
/**
* Extracts a directly-supplied Autumn secret key (no OAuth exchange): a
* `secret-key` header, an `am_`-prefixed bearer token, or the configured flag
* when OAuth is disabled.
*/
export const getStaticApiKey = ({
headers,
flags,
}: {
headers: Headers;
flags: MCPOAuthFlags;
}): string | undefined => {
const secretKey = headers.get("secret-key");
if (secretKey) return secretKey;
const authorization = headers.get("authorization");
const bearer = authorization?.startsWith("Bearer ")
? authorization.slice("Bearer ".length)
: undefined;
if (bearer?.startsWith("am_")) return bearer;
return flags["oauth-enabled"] ? undefined : flags["secret-key"];
};

View File

@@ -1,22 +0,0 @@
import * as z from "zod/v4";
import { DEFAULT_API_VERSION } from "../../../constants.js";
import type { MCPServerFlags } from "../../flags.js";
export const environmentSchema = z.enum(["sandbox", "live"]);
export type OAuthEnvironment = z.infer<typeof environmentSchema>;
export const xApiVersionSchema = z.string().default(DEFAULT_API_VERSION);
export const failOpenSchema = z
.union([
z.boolean(),
z.enum(["true", "false"]).transform((v) => v === "true"),
])
.default(true);
export const secretKeySchema = z.string().min(1).optional();
export interface MCPOAuthFlags extends MCPServerFlags {
readonly "oauth-enabled"?: boolean | undefined;
readonly "oauth-environment"?: OAuthEnvironment | undefined;
}

View File

@@ -1,60 +0,0 @@
import { DEFAULT_AUTUMN_API_URL } from "../../../constants.js";
import { OAuthHttpError } from "./errors.js";
import type { MCPOAuthFlags } from "./schemas.js";
const trimTrailingSlash = (url: string) =>
url.endsWith("/") ? url.slice(0, -1) : url;
/** Host the client reached us on, honouring Autumn's proxy forwarding headers. */
const getForwardedHost = (headers: Headers) =>
headers.get("x-autumn-forwarded-host") ??
headers.get("x-forwarded-host") ??
headers.get("host");
const getForwardedProto = (headers: Headers) =>
headers.get("x-autumn-forwarded-proto") ??
headers.get("x-forwarded-proto") ??
"http";
/** Absolute URL of the MCP resource the current request is targeting. */
export const getResourceUrl = ({
headers,
resourcePath = "/mcp",
}: {
headers: Headers;
resourcePath?: string;
}): string => {
const host = getForwardedHost(headers);
if (!host) {
throw new OAuthHttpError(400, "Missing Host header", "invalid_request");
}
return new URL(resourcePath, `${getForwardedProto(headers)}://${host}`).href;
};
export const getProtectedResourceMetadataUrl = (
resourceUrl: string,
): string => {
const url = new URL(resourceUrl);
const path = url.pathname === "/" ? "" : url.pathname;
return new URL(`/.well-known/oauth-protected-resource${path}`, url).href;
};
export const getIssuerUrl = (flags: MCPOAuthFlags): string =>
trimTrailingSlash(
new URL("/api/auth", flags["server-url"] ?? DEFAULT_AUTUMN_API_URL).href,
);
export const getWWWAuthenticate = ({
resourceUrl,
error,
}: {
resourceUrl: string;
error?: string;
}): string => {
const params = [
`resource_metadata="${getProtectedResourceMetadataUrl(resourceUrl)}"`,
];
if (error) params.push(`error="${error}"`);
return `Bearer ${params.join(", ")}`;
};

View File

@@ -47,6 +47,7 @@
"dependencies": { "dependencies": {
"@ai-sdk/anthropic": "^3.0.9", "@ai-sdk/anthropic": "^3.0.9",
"@anthropic-ai/sdk": "^0.32.1", "@anthropic-ai/sdk": "^0.32.1",
"@autumn/auth": "workspace:*",
"@autumn/ksuid": "workspace:*", "@autumn/ksuid": "workspace:*",
"@autumn/shared": "workspace:*", "@autumn/shared": "workspace:*",
"@autumn/stripe-sync": "workspace:*", "@autumn/stripe-sync": "workspace:*",

View File

@@ -1,3 +1,4 @@
import { getBearerToken } from "@autumn/auth";
import { AuthType, ErrCode, type Feature, RecaseError } from "@autumn/shared"; import { AuthType, ErrCode, type Feature, RecaseError } from "@autumn/shared";
import type { Context, Next } from "hono"; import type { Context, Next } from "hono";
import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
@@ -29,12 +30,11 @@ export const secretKeyMiddleware = async (c: Context<HonoEnv>, next: Next) => {
return betterAuthMiddleware(c, next); return betterAuthMiddleware(c, next);
} }
const authHeader = const apiKey = getBearerToken({ headers: c.req.raw.headers });
c.req.header("authorization") || c.req.header("Authorization");
// Step 1 & 2: Check if Authorization header exists // Step 1 & 2: Check if Authorization header exists
// If from dashboard and no Bearer token, use Better Auth session instead // If from dashboard and no Bearer token, use Better Auth session instead
if (!authHeader || !authHeader.startsWith("Bearer ")) { if (!apiKey) {
throw new RecaseError({ throw new RecaseError({
message: "Secret key not found in Authorization header", message: "Secret key not found in Authorization header",
code: ErrCode.NoSecretKey, code: ErrCode.NoSecretKey,
@@ -42,9 +42,6 @@ export const secretKeyMiddleware = async (c: Context<HonoEnv>, next: Next) => {
}); });
} }
// Step 2: Extract and validate API key format
const apiKey = authHeader.split(" ")[1];
if (!apiKey.startsWith("am_")) { if (!apiKey.startsWith("am_")) {
throw new RecaseError({ throw new RecaseError({
message: `Invalid secret key: ${maskApiKey(apiKey)}`, message: `Invalid secret key: ${maskApiKey(apiKey)}`,