@vantasdk/vanta-mcp-server
Version:
Model Context Protocol server for Vanta's security compliance platform
98 lines (97 loc) • 3.4 kB
JavaScript
import fs from "node:fs";
import { baseApiUrl } from "./api.js";
import { z } from "zod";
const VANTA_API_SCOPE = "vanta-api.all:read";
let currentToken = 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() {
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<TokenInfo>} The token and its expiration time.
*/
async function fetchNewToken() {
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) {
return Date.now() >= tokenInfo.expiresAt;
}
/**
* Retrieves a valid OAuth token, refreshing it if necessary.
* @returns {Promise<string>} The valid OAuth token.
*/
export async function getValidToken() {
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<string>} The new OAuth token.
*/
export async function refreshToken() {
currentToken = await fetchNewToken();
return currentToken.token;
}
/**
* Initializes the OAuth token by ensuring a valid token is available.
*/
export async function initializeToken() {
await getValidToken();
}