chore: refactored mcp auth... again
This commit is contained in:
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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:*",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
34
apps/leaf/src/mcp/auth/protectedResourceMetadata.ts
Normal file
34
apps/leaf/src/mcp/auth/protectedResourceMetadata.ts
Normal 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",
|
||||||
|
});
|
||||||
167
apps/leaf/src/mcp/auth/resolveRequestAuth.ts
Normal file
167
apps/leaf/src/mcp/auth/resolveRequestAuth.ts
Normal 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");
|
||||||
|
};
|
||||||
3
apps/leaf/src/mcp/constants.ts
Normal file
3
apps/leaf/src/mcp/constants.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export const MCP_PATH = "/mcp" as const;
|
||||||
|
export const PROTECTED_RESOURCE_METADATA_PATH =
|
||||||
|
"/.well-known/oauth-protected-resource/mcp";
|
||||||
71
apps/leaf/src/mcp/handlers/handleMcp.ts
Normal file
71
apps/leaf/src/mcp/handlers/handleMcp.ts
Normal 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;
|
||||||
|
};
|
||||||
@@ -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"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
29
apps/leaf/src/mcp/mcpRouter.ts
Normal file
29
apps/leaf/src/mcp/mcpRouter.ts
Normal 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;
|
||||||
|
};
|
||||||
15
apps/leaf/src/mcp/types.ts
Normal file
15
apps/leaf/src/mcp/types.ts
Normal 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";
|
||||||
@@ -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",
|
||||||
48
bun.lock
48
bun.lock
@@ -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=="],
|
||||||
|
|||||||
@@ -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/
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
36
packages/auth/package.json
Normal file
36
packages/auth/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
2
packages/auth/src/index.ts
Normal file
2
packages/auth/src/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./oauth/index.js";
|
||||||
|
export * from "./utils/index.js";
|
||||||
1
packages/auth/src/oauth/index.ts
Normal file
1
packages/auth/src/oauth/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./oauthUrls.js";
|
||||||
32
packages/auth/src/oauth/oauthUrls.ts
Normal file
32
packages/auth/src/oauth/oauthUrls.ts
Normal 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(", ")}`;
|
||||||
|
};
|
||||||
13
packages/auth/src/utils/getBearerToken.ts
Normal file
13
packages/auth/src/utils/getBearerToken.ts
Normal 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;
|
||||||
|
};
|
||||||
1
packages/auth/src/utils/index.ts
Normal file
1
packages/auth/src/utils/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from "./getBearerToken.js";
|
||||||
34
packages/auth/tsconfig.json
Normal file
34
packages/auth/tsconfig.json
Normal 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"]
|
||||||
|
}
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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");
|
|
||||||
};
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)}`;
|
|
||||||
@@ -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"];
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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(", ")}`;
|
|
||||||
};
|
|
||||||
@@ -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:*",
|
||||||
|
|||||||
@@ -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)}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user