Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,5 @@ node_modules/
build/

# Claude Code generated files
CLAUDE.md
CLAUDE.md
.vanta_env
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ A <a href="https://modelcontextprotocol.com/"> Model Context Protocol </a> serve
- Get specific tests that validate each security control
- Understand which automated tests monitor compliance for specific controls

### Document Management

- Access documents associated with specific security controls for evidence tracking
- List uploaded files and attachments for compliance documentation
- Download specific document files with proper authentication
- Track document upload status and file metadata for audit trails

### Multi-Region Support

- US, EU, and AUS regions with region-specific API endpoints
Expand All @@ -41,6 +48,9 @@ A <a href="https://modelcontextprotocol.com/"> Model Context Protocol </a> serve
| `get_framework_controls` | Get detailed security control requirements for a specific compliance framework. Returns the specific controls, their descriptions, implementation guidance, and current compliance status. Essential for understanding what security measures are required for each compliance standard. |
| `get_controls` | List all security controls across all frameworks in your Vanta account. Returns control names, descriptions, framework mappings, and current implementation status. Use this to see all available controls or to find a specific control ID for use with other tools. |
| `get_control_tests` | Get all automated tests that validate a specific security control. Use this when you know a control ID and want to see which specific tests monitor compliance for that control. Returns test details, current status, and any failing entities for the control's tests. |
| `get_control_documents` | List all documents associated with a specific security control. Returns document details including names, types, and upload status for evidence and documentation linked to the control. |
| `get_document_uploads` | List all uploaded files for a specific document. Returns file details including names, upload dates, and file IDs for files that have been uploaded to provide evidence for a document. |
| `download_document_file` | Download a specific file from a document. Provides access to the actual file content when you know both the document ID and uploaded file ID. Returns download information and URL for authenticated access. |

## Configuration

Expand All @@ -57,6 +67,19 @@ A <a href="https://modelcontextprotocol.com/"> Model Context Protocol </a> serve

> **Note:** Vanta currently only allows a single active access_token per Application today. [More info here](https://developer.vanta.com/docs/api-access-setup#authentication-and-token-retrieval)

### Usage locally

as this is a fork, you need to build the server and use it from local:
```json
"localVanta": {
"command": "/path_to/vanta-mcp-server/local-vanta-mcp",
"env": {
"VANTA_ENV_FILE": "/path_to/.vanta_env"
}
},
```


### Usage with Claude Desktop

Add the server to your `claude_desktop_config.json`:
Expand Down
4 changes: 4 additions & 0 deletions local-vanta-mcp
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node

// Local executable wrapper for Vanta MCP Server
import('./build/index.js');
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"start": "yarn build && node build/index.js",
"local-vanta-mcp": "yarn build && node build/index.js",
"eval": "tsc && node build/eval/eval.js",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
Expand Down
29 changes: 29 additions & 0 deletions src/eval/eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import {
GetControlsTool,
GetControlTestsTool,
} from "../operations/controls.js";
import {
GetControlDocumentsTool,
GetDocumentUploadsTool,
DownloadDocumentFileTool,
} from "../operations/documents.js";

// Format all tools for OpenAI
const tools = [
Expand Down Expand Up @@ -60,6 +65,30 @@ const tools = [
parameters: zodToJsonSchema(GetControlTestsTool.parameters),
},
},
{
type: "function" as const,
function: {
name: GetControlDocumentsTool.name,
description: GetControlDocumentsTool.description,
parameters: zodToJsonSchema(GetControlDocumentsTool.parameters),
},
},
{
type: "function" as const,
function: {
name: GetDocumentUploadsTool.name,
description: GetDocumentUploadsTool.description,
parameters: zodToJsonSchema(GetDocumentUploadsTool.parameters),
},
},
{
type: "function" as const,
function: {
name: DownloadDocumentFileTool.name,
description: DownloadDocumentFileTool.description,
parameters: zodToJsonSchema(DownloadDocumentFileTool.parameters),
},
},
];

// Test cases with expected tool calls
Expand Down
29 changes: 29 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ import {
getControls,
getControlTests,
} from "./operations/controls.js";
import {
GetControlDocumentsTool,
GetDocumentUploadsTool,
DownloadDocumentFileTool,
getControlDocuments,
getDocumentUploads,
downloadDocumentFile,
} from "./operations/documents.js";
import { initializeToken } from "./auth.js";

const server = new McpServer({
Expand Down Expand Up @@ -71,6 +79,27 @@ server.tool(
getControlTests,
);

server.tool(
GetControlDocumentsTool.name,
GetControlDocumentsTool.description,
GetControlDocumentsTool.parameters.shape,
getControlDocuments,
);

server.tool(
GetDocumentUploadsTool.name,
GetDocumentUploadsTool.description,
GetDocumentUploadsTool.parameters.shape,
getDocumentUploads,
);

server.tool(
DownloadDocumentFileTool.name,
DownloadDocumentFileTool.description,
DownloadDocumentFileTool.parameters.shape,
downloadDocumentFile,
);

async function main() {
try {
await initializeToken();
Expand Down
155 changes: 155 additions & 0 deletions src/operations/documents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import { baseApiUrl } from "../api.js";
import { Tool } from "../types.js";
import { z } from "zod";
import { makeAuthenticatedRequest } from "./utils.js";

const GetControlDocumentsInput = z.object({
controlId: z
.string()
.describe("The ID of the control to list documents for"),
pageSize: z
.number()
.describe("Number of documents to return (1-100, default 10)")
.optional(),
pageCursor: z.string().describe("Pagination cursor for next page").optional(),
});

export const GetControlDocumentsTool: Tool<typeof GetControlDocumentsInput> = {
name: "get_control_documents",
description:
"List all documents associated with a specific security control. Use this when you know a control ID and want to see which documents provide evidence or documentation for that control. Returns document details including names, types, and upload status.",
parameters: GetControlDocumentsInput,
};

const GetDocumentUploadsInput = z.object({
documentId: z
.string()
.describe("The ID of the document to list uploaded files for"),
pageSize: z
.number()
.describe("Number of uploads to return (1-100, default 10)")
.optional(),
pageCursor: z.string().describe("Pagination cursor for next page").optional(),
});

export const GetDocumentUploadsTool: Tool<typeof GetDocumentUploadsInput> = {
name: "get_document_uploads",
description:
"List all uploaded files for a specific document. Use this when you know a document ID and want to see which files have been uploaded to provide evidence for that document. Returns file details including names, upload dates, and file IDs.",
parameters: GetDocumentUploadsInput,
};

const DownloadDocumentFileInput = z.object({
documentId: z
.string()
.describe("The ID of the document containing the file"),
uploadedFileId: z
.string()
.describe("The ID of the specific uploaded file to download"),
});

export const DownloadDocumentFileTool: Tool<typeof DownloadDocumentFileInput> = {
name: "download_document_file",
description:
"Download a specific file from a document. Use this when you know both the document ID and the uploaded file ID and want to retrieve the actual file content. Returns the file content as binary data.",
parameters: DownloadDocumentFileInput,
};

export async function getControlDocuments(
args: z.infer<typeof GetControlDocumentsInput>,
): Promise<CallToolResult> {
const url = new URL(
`/v1/controls/${args.controlId}/documents`,
baseApiUrl(),
);
if (args.pageSize !== undefined) {
url.searchParams.append("pageSize", args.pageSize.toString());
}
if (args.pageCursor !== undefined) {
url.searchParams.append("pageCursor", args.pageCursor);
}

const response = await makeAuthenticatedRequest(url.toString());
if (!response.ok) {
return {
content: [
{ type: "text" as const, text: `Error: ${response.statusText}` },
],
};
}

return {
content: [
{ type: "text" as const, text: JSON.stringify(await response.json()) },
],
};
}

export async function getDocumentUploads(
args: z.infer<typeof GetDocumentUploadsInput>,
): Promise<CallToolResult> {
const url = new URL(
`/v1/documents/${args.documentId}/uploads`,
baseApiUrl(),
);
if (args.pageSize !== undefined) {
url.searchParams.append("pageSize", args.pageSize.toString());
}
if (args.pageCursor !== undefined) {
url.searchParams.append("pageCursor", args.pageCursor);
}

const response = await makeAuthenticatedRequest(url.toString());
if (!response.ok) {
return {
content: [
{ type: "text" as const, text: `Error: ${response.statusText}` },
],
};
}

return {
content: [
{ type: "text" as const, text: JSON.stringify(await response.json()) },
],
};
}

export async function downloadDocumentFile(
args: z.infer<typeof DownloadDocumentFileInput>,
): Promise<CallToolResult> {
const url = new URL(
`/v1/documents/${args.documentId}/uploads/${args.uploadedFileId}/media`,
baseApiUrl(),
);

const response = await makeAuthenticatedRequest(url.toString());
if (!response.ok) {
return {
content: [
{ type: "text" as const, text: `Error: ${response.statusText}` },
],
};
}

// For file downloads, we'll return information about the file rather than binary content
// since MCP tools typically work with text/JSON responses
const contentType = response.headers.get("content-type") || "unknown";
const contentLength = response.headers.get("content-length") || "unknown";

return {
content: [
{
type: "text" as const,
text: JSON.stringify({
message: "File download endpoint accessed successfully",
contentType,
contentLength,
downloadUrl: url.toString(),
note: "Use this URL with proper authentication to download the actual file content"
})
},
],
};
}
Loading