@vantasdk/vanta-mcp-server
Version:
Model Context Protocol server for Vanta's security compliance platform
136 lines (134 loc) • 6.1 kB
JavaScript
// 1. Imports
import { z, createConsolidatedSchema, createPaginationSchema, makeConsolidatedRequest, buildUrl, makeAuthenticatedRequest, handleApiResponse, DOCUMENT_ID_DESCRIPTION, } from "./common/imports.js";
// 2. Input Schemas
const DocumentsInput = createConsolidatedSchema({
paramName: "documentId",
description: DOCUMENT_ID_DESCRIPTION,
resourceName: "document",
}, {
frameworkMatchesAny: z
.array(z.string())
.describe("Filter documents by framework IDs. Returns documents that belong to any of the specified frameworks, e.g. ['soc2', 'iso27001', 'hipaa']")
.optional(),
statusMatchesAny: z
.array(z.string())
.describe("Filter documents by status. Possible values: Needs document, Needs update, Not relevant, OK.")
.optional(),
});
const DocumentResourcesInput = z.object({
documentId: z.string().describe(DOCUMENT_ID_DESCRIPTION),
resourceType: z
.enum(["controls", "links", "uploads"])
.describe("Type of document resource: 'controls' for associated controls, 'links' for external references, 'uploads' for attached files"),
...createPaginationSchema().shape,
});
const DownloadDocumentFileInput = z.object({
uploadedFileId: z
.string()
.describe("Uploaded file ID to download, e.g. 'upload-123' or specific uploaded file identifier"),
});
// 3. Tool Definitions
export const DocumentsTool = {
name: "documents",
description: "Access documents in your Vanta account. Provide documentId to get a specific document, or omit to list all documents. Returns document IDs, names, types, and metadata for compliance and evidence management.",
parameters: DocumentsInput,
};
export const DocumentResourcesTool = {
name: "document_resources",
description: "Access document-related resources including controls, links (i.e. hyperlinks), and uploads. Specify resourceType to get the specific type of resource associated with a document. Use this to explore what controls are linked to a document, what external references exist, or what files are attached (including the download link for those files).",
parameters: DocumentResourcesInput,
};
export const DownloadDocumentFileTool = {
name: "download_document_file",
description: "Download document file by upload ID. Get the actual uploaded document file. Intelligently handles different MIME types: returns text content for readable files (text/*, JSON, XML, CSV, JavaScript) and metadata information for binary files (images, videos, PDFs, etc.).",
parameters: DownloadDocumentFileInput,
};
// 4. Implementation Functions
export async function documents(args) {
return makeConsolidatedRequest("/v1/documents", args, "documentId");
}
export async function documentResources(args) {
const { documentId, resourceType, ...params } = args;
const endpoints = {
controls: `/v1/documents/${String(documentId)}/controls`,
links: `/v1/documents/${String(documentId)}/links`,
uploads: `/v1/documents/${String(documentId)}/uploads`,
};
const endpoint = endpoints[resourceType];
if (!endpoint) {
return {
content: [
{
type: "text",
text: `Error: Invalid resourceType '${resourceType}'. Must be one of: controls, links, uploads`,
},
],
isError: true,
};
}
const url = buildUrl(endpoint, params);
const response = await makeAuthenticatedRequest(url);
return handleApiResponse(response);
}
export async function downloadDocumentFile(args) {
const url = buildUrl(`/v1/document-uploads/${String(args.uploadedFileId)}/download`);
const response = await makeAuthenticatedRequest(url);
if (!response.ok) {
return handleApiResponse(response);
}
// Get the content type from the response headers
const contentType = response.headers.get("content-type") ?? "application/octet-stream";
const contentLength = response.headers.get("content-length");
// Handle text-based MIME types - return content that LLMs can process
if (contentType.startsWith("text/") ||
contentType.includes("application/json") ||
contentType.includes("application/xml") ||
contentType.includes("application/javascript") ||
contentType.includes("application/csv") ||
contentType.includes("text/csv")) {
try {
const textContent = await response.text();
return {
content: [
{
type: "text",
text: `Document File Content (${contentType}):\n\n${textContent}`,
},
],
};
}
catch (error) {
return {
content: [
{
type: "text",
text: `Error reading text content: ${error instanceof Error ? error.message : "Unknown error"}`,
},
],
isError: true,
};
}
}
// For binary files, return metadata about the file
return {
content: [
{
type: "text",
text: `Document File Information:
- Content Type: ${contentType}
- Content Length: ${contentLength ? `${contentLength} bytes` : "Unknown"}
- File Type: ${contentType.startsWith("image/") ? "Image" : contentType.startsWith("video/") ? "Video" : contentType.startsWith("audio/") ? "Audio" : contentType.startsWith("application/pdf") ? "PDF Document" : "Binary File"}
- Upload ID: ${String(args.uploadedFileId)}
Note: This is a binary file. Use appropriate tools to download and process the actual file content.`,
},
],
};
}
// Registry export for automated tool registration
export default {
tools: [
{ tool: DocumentsTool, handler: documents },
{ tool: DocumentResourcesTool, handler: documentResources },
{ tool: DownloadDocumentFileTool, handler: downloadDocumentFile },
],
};