diff --git a/package-lock.json b/package-lock.json index bcd3931..5683643 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "@vantasdk/vanta-mcp-server", - "version": "0.6.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@vantasdk/vanta-mcp-server", - "version": "0.6.0", + "version": "1.1.0", "license": "MIT", "dependencies": { + "@anthropic-ai/claude-code": "^2.1.7", "@modelcontextprotocol/sdk": "^1.6.0", "zod": ">= 3" }, @@ -30,6 +31,27 @@ "node": ">=18" } }, + "node_modules/@anthropic-ai/claude-code": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.1.7.tgz", + "integrity": "sha512-nETDGeuXk/pwUpja/dlVY8dch4doTtue381BjaNqWiMPSqpY4PPg8dDbTqAYpbkIi1sgli7met+okAeDsXVTpg==", + "bin": { + "claude": "cli.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "^0.33.5", + "@img/sharp-darwin-x64": "^0.33.5", + "@img/sharp-linux-arm": "^0.33.5", + "@img/sharp-linux-arm64": "^0.33.5", + "@img/sharp-linux-x64": "^0.33.5", + "@img/sharp-linuxmusl-arm64": "^0.33.5", + "@img/sharp-linuxmusl-x64": "^0.33.5", + "@img/sharp-win32-x64": "^0.33.5" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz", @@ -168,6 +190,276 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz", diff --git a/package.json b/package.json index 6a4eac3..0ecf944 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@vantasdk/vanta-mcp-server", - "version": "1.1.0", + "version": "2.0.0", "main": "index.js", "type": "module", "bin": "build/index.js", @@ -43,6 +43,8 @@ }, "devDependencies": { "@eslint/js": "^8.57.0", + "@types/cors": "^2.8.17", + "@types/express": "^5.0.0", "@types/node": "^22.13.17", "@typescript-eslint/eslint-plugin": "^7.2.0", "@typescript-eslint/parser": "^7.2.0", @@ -53,8 +55,12 @@ "typescript": "^5.8.2" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.6.0", - "zod": ">= 3" + "@anthropic-ai/claude-code": "^2.1.7", + "@modelcontextprotocol/sdk": "^1.25.2", + "cors": "^2.8.5", + "express": "^5.0.1", + "zod": "^3.25", + "zod-to-json-schema": "^3.25.1" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" } diff --git a/src/api.ts b/src/api.ts index b866406..8969aac 100644 --- a/src/api.ts +++ b/src/api.ts @@ -1,7 +1,8 @@ type BaseApiUrl = | "https://api.vanta.com" | "https://api.eu.vanta.com" - | "https://api.aus.vanta.com"; + | "https://api.aus.vanta.com" + | "http://127.0.0.1:10290"; export function baseApiUrl(): BaseApiUrl { if (process.env.REGION) { @@ -10,9 +11,9 @@ export function baseApiUrl(): BaseApiUrl { } else if (process.env.REGION === "aus") { return "https://api.aus.vanta.com"; } else if (process.env.REGION === "us") { - return "https://api.vanta.com"; + return "http://127.0.0.1:10290"; } throw new Error(`Invalid region: ${process.env.REGION}`); } - return "https://api.vanta.com"; + return "http://127.0.0.1:10290"; } diff --git a/src/auth.ts b/src/auth.ts deleted file mode 100644 index b790ae0..0000000 --- a/src/auth.ts +++ /dev/null @@ -1,127 +0,0 @@ -import fs from "node:fs"; -import { baseApiUrl } from "./api.js"; -import { z } from "zod"; - -const VANTA_API_SCOPE = "vanta-api.all:read"; - -interface OAuthCredentials { - client_id: string; - client_secret: string; -} - -interface TokenInfo { - token: string; - expiresAt: number; -} - -let currentToken: TokenInfo | null = null; - -const TokenResponseSchema = z.object({ - access_token: z.string(), - expires_in: z.number(), - token_type: z.string(), -}); - -/** - * Loads OAuth credentials from the file specified by the VANTA_ENV_FILE environment variable. - * Validates the file contents using a Zod schema. - * @throws {Error} If the environment variable is missing, the file cannot be read, or validation fails. - * @returns {OAuthCredentials} The loaded and validated credentials. - */ -function loadCredentials(): OAuthCredentials { - const envFile = process.env.VANTA_ENV_FILE; - if (!envFile) { - throw new Error("VANTA_ENV_FILE environment variable is required"); - } - - const CredentialsSchema = z.object({ - client_id: z.string(), - client_secret: z.string(), - }); - - try { - const data = fs.readFileSync(envFile, "utf8"); - const parsed = CredentialsSchema.parse(JSON.parse(data)); - return parsed; - } catch (error) { - throw new Error( - `Failed to load credentials from ${envFile}: ${String(error)}`, - ); - } -} - -/** - * Fetches a new OAuth token from the Vanta API using client credentials. - * Validates the response using a Zod schema. - * @throws {Error} If the fetch fails or the response is invalid. - * @returns {Promise} The token and its expiration time. - */ -async function fetchNewToken(): Promise { - const credentials = loadCredentials(); - - const response = await fetch(`${baseApiUrl()}/oauth/token`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - client_id: credentials.client_id, - client_secret: credentials.client_secret, - grant_type: "client_credentials", - scope: VANTA_API_SCOPE, - }), - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error( - `OAuth token request failed: ${response.status.toString()} ${response.statusText} - ${errorText}`, - ); - } - - const tokenResponse = TokenResponseSchema.parse(await response.json()); - - const expiresAt = Date.now() + tokenResponse.expires_in * 1000 - 60000; // Subtract 1 minute buffer - - return { - token: tokenResponse.access_token, - expiresAt, - }; -} - -/** - * Checks if the provided token is expired based on its expiration timestamp. - * @param {TokenInfo} tokenInfo - The token information to check. - * @returns {boolean} True if the token is expired, false otherwise. - */ -function isTokenExpired(tokenInfo: TokenInfo): boolean { - return Date.now() >= tokenInfo.expiresAt; -} - -/** - * Retrieves a valid OAuth token, refreshing it if necessary. - * @returns {Promise} The valid OAuth token. - */ -export async function getValidToken(): Promise { - if (!currentToken || isTokenExpired(currentToken)) { - currentToken = await fetchNewToken(); - } - - return currentToken.token; -} - -/** - * Forces a refresh of the OAuth token, retrieving a new one from the API. - * @returns {Promise} The new OAuth token. - */ -export async function refreshToken(): Promise { - currentToken = await fetchNewToken(); - return currentToken.token; -} - -/** - * Initializes the OAuth token by ensuring a valid token is available. - */ -export async function initializeToken(): Promise { - await getValidToken(); -} diff --git a/src/index.ts b/src/index.ts index 202c3b3..5a79426 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,49 +1,29 @@ #!/usr/bin/env node -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { registerAllOperations } from "./registry.js"; -import { initializeToken } from "./auth.js"; -import { getEnabledToolNames, hasEnabledToolFilter } from "./config.js"; - -const server = new McpServer({ - name: "vanta-mcp", - version: "1.1.0", -}); - -async function main() { - try { - await initializeToken(); - console.error("Token initialized successfully"); - - // Register all tools automatically - await registerAllOperations(server); - - if (hasEnabledToolFilter) { - const enabledTools = getEnabledToolNames(); - console.error( - `⚠️ Tools enabled via VANTA_MCP_ENABLED_TOOLS: ${enabledTools.join(", ")}`, - ); - } - - // Connect to stdio transport - const transport = new StdioServerTransport(); - await server.connect(transport); - - console.error("🚀 Vanta MCP Server started successfully!"); - } catch (error) { - console.error("Failed to start Vanta MCP Server:", error); - process.exit(1); - } +import { createHttpServer } from "./server/http.js"; + +const PORT = parseInt(process.env.PORT ?? "3000", 10); +const ISSUER_URL = process.env.ISSUER_URL ?? `http://localhost:${String(PORT)}`; +const VANTA_AUTH_SERVER = + process.env.VANTA_AUTH_SERVER ?? "https://app.vanta.com"; + +function main(): void { + const app = createHttpServer({ + issuerUrl: ISSUER_URL, + vantaAuthServer: VANTA_AUTH_SERVER, + }); + + app.listen(PORT, () => { + console.error(`Vanta MCP Server running on port ${String(PORT)}`); + console.error( + `Resource metadata: ${ISSUER_URL}/.well-known/oauth-protected-resource`, + ); + }); } -// Handle shutdown gracefully process.on("SIGINT", () => { console.error("Shutting down Vanta MCP Server..."); process.exit(0); }); -main().catch(error => { - console.error("Fatal error:", error); - process.exit(1); -}); +main(); diff --git a/src/operations/common/utils.ts b/src/operations/common/utils.ts index 172019e..8124bc7 100644 --- a/src/operations/common/utils.ts +++ b/src/operations/common/utils.ts @@ -1,4 +1,3 @@ -import { getValidToken, refreshToken } from "../../auth.js"; import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { baseApiUrl } from "../../api.js"; @@ -6,24 +5,29 @@ import { PAGE_SIZE_DESCRIPTION, PAGE_CURSOR_DESCRIPTION, } from "./descriptions.js"; +import { requestContext } from "../../server/request-context.js"; + +export function createAuthHeaders(): Record { + const context = requestContext.getStore(); + if (!context?.vantaToken) { + throw new Error("No authentication context available"); + } -export async function createAuthHeaders(): Promise> { - const token = await getValidToken(); return { - "Authorization": `Bearer ${token}`, + "Authorization": `Bearer ${context.vantaToken}`, "x-vanta-is-mcp": "true", }; } /** - * Makes an authenticated HTTP request using a bearer token from the Vanta MCP auth system. - * If the request returns a 401 Unauthorized, it will refresh the token and retry once. + * Makes an authenticated HTTP request using a bearer token from the request context. + * Token refresh is handled by the MCP client, not the server. */ export async function makeAuthenticatedRequest( url: string, options: RequestInit = {}, ): Promise { - const headers = await createAuthHeaders(); + const headers = createAuthHeaders(); const requestOptions: RequestInit = { ...options, @@ -33,29 +37,7 @@ export async function makeAuthenticatedRequest( }, }; - // Try the request with the current token - let response = await fetch(url, requestOptions); - - // If we get a 401, refresh the token and try again - if (response.status === 401) { - try { - await refreshToken(); - const newHeaders = await createAuthHeaders(); - const retryOptions: RequestInit = { - ...options, - headers: { - ...newHeaders, - ...options.headers, - }, - }; - response = await fetch(url, retryOptions); - } catch (refreshError) { - console.error("Failed to refresh token:", refreshError); - // Return the original 401 response - } - } - - return response; + return fetch(url, requestOptions); } // ========================================== diff --git a/src/registry.ts b/src/registry.ts index a96e4bd..3f36ee5 100644 --- a/src/registry.ts +++ b/src/registry.ts @@ -6,6 +6,7 @@ import { hasEnabledToolFilter, isToolEnabled, } from "./config.js"; +import { requestContext } from "./server/request-context.js"; // Tool definition interface (matches our Tool pattern) export interface ToolDefinition { @@ -17,7 +18,8 @@ export interface ToolDefinition { // Tool registry interface for operations modules export interface ToolEntry { tool: ToolDefinition; - handler: (args: z.infer) => Promise; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler: (args: any) => Promise; } export interface OperationModule { @@ -28,15 +30,26 @@ export interface OperationModule { export function registerTool( server: McpServer, tool: ToolDefinition, - handler: (args: z.infer) => Promise, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + handler: (args: any) => Promise, + vantaToken?: string, ): boolean { if (!isToolEnabled(tool.name)) { console.error(`⚪️ Skipping tool not in enabled list: ${tool.name}`); return false; } + // Wrap handler to run within request context if token is provided + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const wrappedHandler = vantaToken + ? // eslint-disable-next-line @typescript-eslint/no-explicit-any + (args: any) => requestContext.run({ vantaToken }, () => handler(args)) + : handler; + const parameters = tool.parameters as z.ZodObject; - server.tool(tool.name, tool.description, parameters.shape, handler); + // Cast to any to avoid type instantiation depth issues with new SDK + // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument + (server.tool as any)(tool.name, tool.description, parameters.shape, wrappedHandler); return true; } @@ -44,12 +57,13 @@ export function registerTool( export function registerOperationModule( server: McpServer, operationModule: OperationModule, + vantaToken?: string, ): { registered: number; skipped: number } { let registered = 0; let skipped = 0; operationModule.tools.forEach(({ tool, handler }) => { - const wasRegistered = registerTool(server, tool, handler); + const wasRegistered = registerTool(server, tool, handler, vantaToken); if (wasRegistered) { registered += 1; } else { @@ -61,7 +75,10 @@ export function registerOperationModule( } // Auto-discovery and registration of all operations -export async function registerAllOperations(server: McpServer): Promise { +export async function registerAllOperations( + server: McpServer, + vantaToken: string, +): Promise { // Import all operation modules const operations = [ import("./operations/tests.js"), @@ -93,19 +110,20 @@ export async function registerAllOperations(server: McpServer): Promise { const { registered, skipped } = registerOperationModule( server, operationModule, + vantaToken, ); totalTools += registered; skippedTools += skipped; }); console.error( - `✅ Registered ${String(totalTools)} tools from ${String(modules.length)} operation modules successfully`, + `Registered ${String(totalTools)} tools from ${String(modules.length)} operation modules successfully`, ); if (skippedTools > 0 && hasEnabledToolFilter) { const enabledList = getEnabledToolNames().join(", "); console.error( - `⚠️ Tools skipped because they are not enabled: ${String(skippedTools)} (enabled list: ${enabledList})`, + `Tools skipped because they are not enabled: ${String(skippedTools)} (enabled list: ${enabledList})`, ); } } diff --git a/src/server/http.ts b/src/server/http.ts new file mode 100644 index 0000000..8daa07d --- /dev/null +++ b/src/server/http.ts @@ -0,0 +1,173 @@ +import express, { Request, Response, NextFunction } from "express"; +import cors from "cors"; +import { randomUUID } from "node:crypto"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; +import { registerAllOperations } from "../registry.js"; + +const RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource"; +const AUTH_SERVER_METADATA_PATH = "/.well-known/oauth-authorization-server"; +const VANTA_SCOPES = ["vanta-api.all:read"]; + +interface AuthenticatedRequest extends Request { + vantaToken: string; +} + +export interface ServerConfig { + issuerUrl: string; + vantaAuthServer: string; +} + +// Vanta's actual token endpoint (proxied through this server) +const VANTA_TOKEN_ENDPOINT = "http://127.0.0.1:10290/oauth/token"; + +export function createHttpServer(config: ServerConfig) { + const app = express(); + app.use(cors()); + app.use(express.json()); + app.use(express.urlencoded({ extended: true })); + + // Log ALL requests to see what the client is doing + app.use((req: Request, _res: Response, next: NextFunction) => { + console.log(`[${req.method}] ${req.path}`); + next(); + }); + + // Protected Resource Metadata (RFC 9728) + // Points to this server as the issuer - we proxy Vanta's OAuth endpoints + app.get(RESOURCE_METADATA_PATH, (_req: Request, res: Response) => { + console.log('inside resource metadata'); + res.json({ + resource: config.issuerUrl, + authorization_servers: [{ issuer: config.issuerUrl }], + scopes_supported: VANTA_SCOPES, + }); + }); + + // Authorization Server Metadata (RFC 8414) + // Proxies to Vanta's actual OAuth endpoints + app.get(AUTH_SERVER_METADATA_PATH, (_req: Request, res: Response) => { + console.log('inside auth server metadata'); + + // Build authorization URL with extra query parameters + const authUrl = new URL("http://127.0.0.1:8080/oauth/authorize"); + authUrl.searchParams.set("source_id", "vanta-mcp-server"); + + res.json({ + issuer: config.issuerUrl, + authorization_endpoint: authUrl.toString(), + // Token endpoint points to our proxy, which adds extra params + token_endpoint: `${config.issuerUrl}/oauth/token`, + scopes_supported: VANTA_SCOPES, + response_types_supported: ["code"], + grant_types_supported: ["authorization_code", "refresh_token"], + }); + }); + + // Token endpoint proxy - adds extra parameters and forwards to Vanta + app.post("/oauth/token", async (req: Request, res: Response) => { + console.log("Proxying token request to Vanta"); + + // Add extra parameters to the request body + const bodyWithExtras = { + ...req.body, + source_id: "vanta-mcp-server", + // Add any other custom parameters here: + // custom_param: "value", + }; + + try { + const response = await fetch(VANTA_TOKEN_ENDPOINT, { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: new URLSearchParams(bodyWithExtras).toString(), + }); + + const data = await response.json() as Record; + console.log("Token proxy response:", data); + console.log("Token proxy response status:", response.status); + + // Add scope to response if not present (some clients require this) + if (response.ok && !data.scope) { + data.scope = VANTA_SCOPES.join(" "); + } + + res.status(response.status).json(data); + } catch (error) { + console.error("Token proxy error:", error); + res.status(500).json({ error: "Token proxy failed" }); + } + }); + + // Bearer token validation middleware + const requireAuth = (req: Request, res: Response, next: NextFunction) => { + const authHeader = req.headers.authorization; + if (!authHeader?.startsWith("Bearer ")) { + res + .status(401) + .set( + "WWW-Authenticate", + `Bearer resource_metadata="${config.issuerUrl}${RESOURCE_METADATA_PATH}"`, + ) + .send("Unauthorized"); + return; + } + (req as AuthenticatedRequest).vantaToken = authHeader.slice(7); + next(); + }; + + // Track active transports and servers by session ID + const sessions = new Map(); + + // MCP endpoint (Streamable HTTP) - handles both GET and POST + app.all("/mcp", requireAuth, async (req: Request, res: Response) => { + const { vantaToken } = req as AuthenticatedRequest; + console.log(`MCP endpoint called: ${req.method}`); + + // Check for existing session + const sessionId = req.headers["mcp-session-id"] as string | undefined; + console.log(`Session ID from header: ${sessionId}`); + + if (sessionId && sessions.has(sessionId)) { + // Existing session - reuse transport + console.log(`Reusing existing session: ${sessionId}`); + const session = sessions.get(sessionId)!; + await session.transport.handleRequest(req, res, req.body); + return; + } + + // New session - create server and transport + console.log("Creating new session"); + const server = new McpServer({ name: "vanta-mcp", version: "2.0.0" }); + await registerAllOperations(server, vantaToken); + + const transport = new StreamableHTTPServerTransport({ + sessionIdGenerator: () => randomUUID(), + }); + + // Connect server to transport BEFORE handling request + await server.connect(transport); + + // Handle the request - this will set the sessionId + await transport.handleRequest(req, res, req.body); + + // Store session AFTER handling request (sessionId is now set) + const newSessionId = transport.sessionId; + console.log(`New session ID after handling: ${newSessionId}`); + if (newSessionId) { + sessions.set(newSessionId, { transport, server }); + + transport.onclose = () => { + console.log(`Session ${newSessionId} closed`); + sessions.delete(newSessionId); + }; + } + }); + + return app; +} diff --git a/src/server/request-context.ts b/src/server/request-context.ts new file mode 100644 index 0000000..e974370 --- /dev/null +++ b/src/server/request-context.ts @@ -0,0 +1,7 @@ +import { AsyncLocalStorage } from "node:async_hooks"; + +interface RequestContext { + vantaToken: string; +} + +export const requestContext = new AsyncLocalStorage(); diff --git a/yarn.lock b/yarn.lock index 236c64e..ad68b5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,20 @@ # yarn lockfile v1 +"@anthropic-ai/claude-code@^2.1.7": + version "2.1.7" + resolved "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-2.1.7.tgz" + integrity sha512-nETDGeuXk/pwUpja/dlVY8dch4doTtue381BjaNqWiMPSqpY4PPg8dDbTqAYpbkIi1sgli7met+okAeDsXVTpg== + optionalDependencies: + "@img/sharp-darwin-arm64" "^0.33.5" + "@img/sharp-darwin-x64" "^0.33.5" + "@img/sharp-linux-arm" "^0.33.5" + "@img/sharp-linux-arm64" "^0.33.5" + "@img/sharp-linux-x64" "^0.33.5" + "@img/sharp-linuxmusl-arm64" "^0.33.5" + "@img/sharp-linuxmusl-x64" "^0.33.5" + "@img/sharp-win32-x64" "^0.33.5" + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.5.1" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz" @@ -29,11 +43,16 @@ minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@^8.57.0", "@eslint/js@8.57.1": +"@eslint/js@8.57.1", "@eslint/js@^8.57.0": version "8.57.1" resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz" integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q== +"@hono/node-server@^1.19.7": + version "1.19.9" + resolved "https://registry.yarnpkg.com/@hono/node-server/-/node-server-1.19.9.tgz#8f37119b1acf283fd3f6035f3d1356fdb97a09ac" + integrity sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw== + "@humanwhocodes/config-array@^0.13.0": version "0.13.0" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz" @@ -53,21 +72,116 @@ resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== -"@modelcontextprotocol/sdk@^1.6.0": - version "1.8.0" - resolved "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.8.0.tgz" - integrity sha512-e06W7SwrontJDHwCawNO5SGxG+nU9AAx+jpHHZqGl/WrDBdWOpvirC+s58VpJTB5QemI4jTRcjWT4Pt3Q1NPQQ== - dependencies: +"@img/sharp-darwin-arm64@^0.33.5": + version "0.33.5" + resolved "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz" + integrity sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.4" + +"@img/sharp-darwin-x64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz#e03d3451cd9e664faa72948cc70a403ea4063d61" + integrity sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.4" + +"@img/sharp-libvips-darwin-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz" + integrity sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg== + +"@img/sharp-libvips-darwin-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz#e0456f8f7c623f9dbfbdc77383caa72281d86062" + integrity sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ== + +"@img/sharp-libvips-linux-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz#979b1c66c9a91f7ff2893556ef267f90ebe51704" + integrity sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA== + +"@img/sharp-libvips-linux-arm@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz#99f922d4e15216ec205dcb6891b721bfd2884197" + integrity sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g== + +"@img/sharp-libvips-linux-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz#d4c4619cdd157774906e15770ee119931c7ef5e0" + integrity sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz#166778da0f48dd2bded1fa3033cee6b588f0d5d5" + integrity sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA== + +"@img/sharp-libvips-linuxmusl-x64@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz#93794e4d7720b077fcad3e02982f2f1c246751ff" + integrity sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw== + +"@img/sharp-linux-arm64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz#edb0697e7a8279c9fc829a60fc35644c4839bb22" + integrity sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.4" + +"@img/sharp-linux-arm@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz#422c1a352e7b5832842577dc51602bcd5b6f5eff" + integrity sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.5" + +"@img/sharp-linux-x64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz#d806e0afd71ae6775cc87f0da8f2d03a7c2209cb" + integrity sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.4" + +"@img/sharp-linuxmusl-arm64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz#252975b915894fb315af5deea174651e208d3d6b" + integrity sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.4" + +"@img/sharp-linuxmusl-x64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz#3f4609ac5d8ef8ec7dadee80b560961a60fd4f48" + integrity sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.4" + +"@img/sharp-win32-x64@^0.33.5": + version "0.33.5" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz#56f00962ff0c4e0eb93d34a047d29fa995e3e342" + integrity sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg== + +"@modelcontextprotocol/sdk@^1.25.2": + version "1.25.2" + resolved "https://registry.yarnpkg.com/@modelcontextprotocol/sdk/-/sdk-1.25.2.tgz#2284560b4e044b4ce5f328ee180931110cb8c5cf" + integrity sha512-LZFeo4F9M5qOhC/Uc1aQSrBHxMrvxett+9KLHt7OhcExtoiRN9DKgbZffMP/nxjutWDQpfMDfP3nkHI4X9ijww== + dependencies: + "@hono/node-server" "^1.19.7" + ajv "^8.17.1" + ajv-formats "^3.0.1" content-type "^1.0.5" cors "^2.8.5" - cross-spawn "^7.0.3" + cross-spawn "^7.0.5" eventsource "^3.0.2" + eventsource-parser "^3.0.0" express "^5.0.1" express-rate-limit "^7.5.0" - pkce-challenge "^4.1.0" + jose "^6.1.1" + json-schema-typed "^8.0.2" + pkce-challenge "^5.0.0" raw-body "^3.0.0" - zod "^3.23.8" - zod-to-json-schema "^3.24.1" + zod "^3.25 || ^4.0" + zod-to-json-schema "^3.25.0" "@nodelib/fs.scandir@2.1.5": version "2.1.5" @@ -77,7 +191,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -90,6 +204,52 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@types/body-parser@*": + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/cors@^2.8.17": + version "2.8.19" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.19.tgz#d93ea2673fd8c9f697367f5eeefc2bbfa94f0342" + integrity sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg== + dependencies: + "@types/node" "*" + +"@types/express-serve-static-core@^5.0.0": + version "5.1.1" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz#1a77faffee9572d39124933259be2523837d7eaa" + integrity sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^5.0.0": + version "5.0.6" + resolved "https://registry.yarnpkg.com/@types/express/-/express-5.0.6.tgz#2d724b2c990dcb8c8444063f3580a903f6d500cc" + integrity sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^5.0.0" + "@types/serve-static" "^2" + +"@types/http-errors@*": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== + "@types/node-fetch@^2.6.4": version "2.6.12" resolved "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz" @@ -112,6 +272,31 @@ dependencies: undici-types "~5.26.4" +"@types/qs@*": + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + +"@types/send@*": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/serve-static@^2": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-2.2.0.tgz#d4a447503ead0d1671132d1ab6bd58b805d8de6a" + integrity sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@typescript-eslint/eslint-plugin@^7.2.0": version "7.18.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz" @@ -127,7 +312,7 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^7.0.0", "@typescript-eslint/parser@^7.2.0": +"@typescript-eslint/parser@^7.2.0": version "7.18.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz" integrity sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg== @@ -218,7 +403,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0: +acorn@^8.9.0: version "8.14.1" resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz" integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg== @@ -230,6 +415,13 @@ agentkeepalive@^4.2.1: dependencies: humanize-ms "^1.2.1" +ajv-formats@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-3.0.1.tgz#3d5dc762bca17679c3c2ea7e90ad6b7532309578" + integrity sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ== + dependencies: + ajv "^8.0.0" + ajv@^6.12.4: version "6.12.6" resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz" @@ -240,6 +432,16 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.0, ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" @@ -309,7 +511,7 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -bytes@^3.1.2, bytes@3.1.2: +bytes@3.1.2, bytes@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== @@ -397,7 +599,7 @@ cors@^2.8.5: object-assign "^4" vary "^1" -cross-spawn@^7.0.2, cross-spawn@^7.0.3: +cross-spawn@^7.0.2, cross-spawn@^7.0.5: version "7.0.6" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz" integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== @@ -423,7 +625,7 @@ delayed-stream@~1.0.0: resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ== -depd@^2.0.0, depd@2.0.0: +depd@2.0.0, depd@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== @@ -516,7 +718,7 @@ eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -"eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@^8.56.0, eslint@^8.57.0, eslint@>=7.0.0: +eslint@^8.57.0: version "8.57.1" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz" integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== @@ -603,6 +805,11 @@ event-target-shim@^5.0.0: resolved "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== +eventsource-parser@^3.0.0: + version "3.0.6" + resolved "https://registry.yarnpkg.com/eventsource-parser/-/eventsource-parser-3.0.6.tgz#292e165e34cacbc936c3c92719ef326d4aeb4e90" + integrity sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg== + eventsource-parser@^3.0.1: version "3.0.1" resolved "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.1.tgz" @@ -620,7 +827,7 @@ express-rate-limit@^7.5.0: resolved "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz" integrity sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg== -"express@^4.11 || 5 || ^5.0.0-beta.1", express@^5.0.1: +express@^5.0.1: version "5.1.0" resolved "https://registry.npmjs.org/express/-/express-5.1.0.tgz" integrity sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA== @@ -679,6 +886,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-uri@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa" + integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA== + fastq@^1.6.0: version "1.19.1" resolved "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz" @@ -880,7 +1092,7 @@ hasown@^2.0.2: dependencies: function-bind "^1.1.2" -http-errors@^2.0.0, http-errors@2.0.0: +http-errors@2.0.0, http-errors@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz" integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== @@ -898,7 +1110,7 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -iconv-lite@^0.6.3, iconv-lite@0.6.3: +iconv-lite@0.6.3, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz" integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== @@ -973,6 +1185,11 @@ isexe@^2.0.0: resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +jose@^6.1.1: + version "6.1.3" + resolved "https://registry.yarnpkg.com/jose/-/jose-6.1.3.tgz#8453d7be88af7bb7d64a0481d6a35a0145ba3ea5" + integrity sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ== + js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz" @@ -990,6 +1207,16 @@ json-schema-traverse@^0.4.1: resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + +json-schema-typed@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-8.0.2.tgz#e98ee7b1899ff4a184534d1f167c288c66bbeff4" + integrity sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz" @@ -1050,16 +1277,16 @@ micromatch@^4.0.8: braces "^3.0.3" picomatch "^2.3.1" -mime-db@^1.54.0: - version "1.54.0" - resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" - integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== - mime-db@1.52.0: version "1.52.0" resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== +mime-db@^1.54.0: + version "1.54.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz" + integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ== + mime-types@^2.1.12: version "2.1.35" resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz" @@ -1074,21 +1301,7 @@ mime-types@^3.0.0, mime-types@^3.0.1: dependencies: mime-db "^1.54.0" -minimatch@^3.0.5: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.2: +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -1234,10 +1447,10 @@ picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -pkce-challenge@^4.1.0: - version "4.1.0" - resolved "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz" - integrity sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ== +pkce-challenge@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/pkce-challenge/-/pkce-challenge-5.0.1.tgz#3b4446865b17b1745e9ace2016a31f48ddf6230d" + integrity sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ== prelude-ls@^1.2.1: version "1.2.1" @@ -1289,6 +1502,11 @@ raw-body@^3.0.0: iconv-lite "0.6.3" unpipe "1.0.0" +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" @@ -1428,7 +1646,7 @@ slash@^3.0.0: resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== -statuses@^2.0.1, statuses@2.0.1: +statuses@2.0.1, statuses@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== @@ -1500,7 +1718,7 @@ type-is@^2.0.0, type-is@^2.0.1: media-typer "^1.1.0" mime-types "^3.0.0" -typescript@^5.8.2, typescript@>=4.2.0: +typescript@^5.8.2: version "5.8.2" resolved "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz" integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ== @@ -1572,12 +1790,17 @@ yocto-queue@^0.1.0: resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== -zod-to-json-schema@^3.24.1: - version "3.24.5" - resolved "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz" - integrity sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g== +zod-to-json-schema@^3.25.0, zod-to-json-schema@^3.25.1: + version "3.25.1" + resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz#7f24962101a439ddade2bf1aeab3c3bfec7d84ba" + integrity sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA== + +zod@^3.25: + version "3.25.76" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.76.tgz#26841c3f6fd22a6a2760e7ccb719179768471e34" + integrity sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ== -zod@^3.23.8, zod@^3.24.1, "zod@>= 3": - version "3.24.2" - resolved "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz" - integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ== +"zod@^3.25 || ^4.0": + version "4.3.5" + resolved "https://registry.yarnpkg.com/zod/-/zod-4.3.5.tgz#aeb269a6f9fc259b1212c348c7c5432aaa474d2a" + integrity sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==