From de5c24e778709f2c87316838ba6e631f562bd97c Mon Sep 17 00:00:00 2001 From: Taylor Hatfield <35178877+thatfield1@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:26:13 -0500 Subject: [PATCH 1/3] Added support for the Documents endpoints --- package-lock.json | 18 +++++- package.json | 1 + src/eval/eval.ts | 20 +++++++ src/index.ts | 20 +++++++ src/operations/documents.ts | 110 ++++++++++++++++++++++++++++++++++++ yarn.lock | 5 ++ 6 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 src/operations/documents.ts diff --git a/package-lock.json b/package-lock.json index bcd3931..b92a914 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,16 @@ { "name": "@vantasdk/vanta-mcp-server", - "version": "0.6.0", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@vantasdk/vanta-mcp-server", - "version": "0.6.0", + "version": "1.0.0", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.0", + "yarn": "^1.22.22", "zod": ">= 3" }, "bin": { @@ -2711,6 +2712,19 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, + "node_modules/yarn": { + "version": "1.22.22", + "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz", + "integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==", + "hasInstallScript": true, + "bin": { + "yarn": "bin/yarn.js", + "yarnpkg": "bin/yarn.js" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 5b02fc9..20869d3 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.6.0", + "yarn": "^1.22.22", "zod": ">= 3" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" diff --git a/src/eval/eval.ts b/src/eval/eval.ts index 649ec6b..d12482f 100644 --- a/src/eval/eval.ts +++ b/src/eval/eval.ts @@ -9,6 +9,10 @@ import { GetControlsTool, GetControlTestsTool, } from "../operations/controls.js"; +import { + GetDocumentsTool, + GetDocumentControlsTool +} from "../operations/documents.js"; // Format all tools for OpenAI const tools = [ @@ -60,6 +64,22 @@ const tools = [ parameters: zodToJsonSchema(GetControlTestsTool.parameters), }, }, + { + type: "function" as const, + function: { + name: GetDocumentsTool.name, + description: GetDocumentsTool.description, + parameters: zodToJsonSchema(GetDocumentsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: GetDocumentControlsTool.name, + description: GetDocumentControlsTool.description, + parameters: zodToJsonSchema(GetDocumentControlsTool.parameters), + }, + }, ]; // Test cases with expected tool calls diff --git a/src/index.ts b/src/index.ts index eaaa4d1..7feaef4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,12 @@ import { getControls, getControlTests, } from "./operations/controls.js"; +import { + getDocuments, + GetDocumentsTool, + getDocumentControls, + GetDocumentControlsTool, +} from "./operations/documents.js"; import { initializeToken } from "./auth.js"; const server = new McpServer({ @@ -71,6 +77,20 @@ server.tool( getControlTests, ); +server.tool( + GetDocumentsTool.name, + GetDocumentsTool.description, + GetDocumentsTool.parameters.shape, + getDocuments, +); + +server.tool( + GetDocumentControlsTool.name, + GetDocumentControlsTool.description, + GetDocumentControlsTool.parameters.shape, + getDocumentControls, +); + async function main() { try { await initializeToken(); diff --git a/src/operations/documents.ts b/src/operations/documents.ts new file mode 100644 index 0000000..6227331 --- /dev/null +++ b/src/operations/documents.ts @@ -0,0 +1,110 @@ +import { baseApiUrl } from "../api.js"; +import { z } from "zod"; +import { Tool } from "../types.js"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { makeAuthenticatedRequest } from "./utils.js"; + +export async function getDocuments( + args: z.infer, +): Promise { + const url = new URL("/v1/documents", baseApiUrl()); + + if (args.pageSize !== undefined) { + url.searchParams.append("pageSize", args.pageSize.toString()); + } + if (args.pageCursor !== undefined) { + url.searchParams.append("pageCursor", args.pageCursor); + } + if (args.frameworkMatchesAny !== undefined) { + url.searchParams.append("frameworkMatchesAny", args.frameworkMatchesAny); + } + if (args.statusMatchesAny !== undefined) { + url.searchParams.append("statusMatchesAny", args.statusMatchesAny); + } + + const response = await makeAuthenticatedRequest(url.toString()); + + if (!response.ok) { + return { + content: [ + { + type: "text" as const, + text: `Url: ${url.toString()}, Error: ${response.statusText}`, + }, + ], + }; + } + + return { + content: [ + { type: "text" as const, text: JSON.stringify(await response.json()) }, + ], + }; +} + +export async function getDocumentControls( + args: z.infer, +): Promise { + const url = new URL(`/v1/documents/${args.documentId}/controls`, 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: `Url: ${url.toString()}, Error: ${response.statusText}`, + }, + ], + }; + } + + return { + content: [ + { type: "text" as const, text: JSON.stringify(await response.json()) }, + ], + }; +} + +const TOOL_DESCRIPTION = `Retrieve Vanta's document requirements. Filter by status (OK, Needs document, Needs update, Not relevant) and/or compliance framework (soc2, iso27001, hipaa).`; + +const DOCUMENT_STATUS_FILTER_DESCRIPTION = `Filter documents by their status. +Helpful for retrieving only relevant or actionable results. +Possible values: OK, Needs document, Needs update, Not relevant.`; + +const PAGE_SIZE_DESCRIPTION = `Controls the maximum number of tests returned in a single response. +Allowed values: 1–100. Default is 10.`; + +const FRAMEWORK_FILTER_DESCRIPTION = `Filter by framework. Non-exhaustive examples: soc2, ccpa, fedramp`; + +export const GetDocumentsInput = z.object({ + pageSize: z.number().describe(PAGE_SIZE_DESCRIPTION).optional(), + pageCursor: z.string().describe("Used for pagination. Leave blank to start from the first page.").optional(), + statusMatchesAny: z.string().describe(DOCUMENT_STATUS_FILTER_DESCRIPTION).optional(), + frameworkMatchesAny: z.string().describe(FRAMEWORK_FILTER_DESCRIPTION).optional(), +}); + +export const GetDocumentsTool: Tool = { + name: "get_documents", + description: TOOL_DESCRIPTION, + parameters: GetDocumentsInput, +}; + +export const GetDocumentControlsInput = z.object({ + documentId: z.string().describe("Lowercase with hyphens"), + pageSize: z.number().describe(PAGE_SIZE_DESCRIPTION).optional(), + pageCursor: z.string().describe("Used for pagination. Leave blank to start from the first page.").optional(), +}); + +export const GetDocumentControlsTool: Tool = { + name: "get_document_controls", + description: "Get the controls associated to the specified document.", + parameters: GetDocumentControlsInput, +}; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 236c64e..00f74aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1567,6 +1567,11 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== +yarn@^1.22.22: + version "1.22.22" + resolved "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz" + integrity sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" From ac996308213414436291ad138b2fa0f4417ceef9 Mon Sep 17 00:00:00 2001 From: Taylor Hatfield <35178877+thatfield1@users.noreply.github.com> Date: Thu, 3 Jul 2025 15:14:20 -0500 Subject: [PATCH 2/3] cleaned up some accidental changes --- package-lock.json | 14 -------------- package.json | 1 - yarn.lock | 5 ----- 3 files changed, 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index b92a914..c3507c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,6 @@ "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.0", - "yarn": "^1.22.22", "zod": ">= 3" }, "bin": { @@ -2712,19 +2711,6 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "node_modules/yarn": { - "version": "1.22.22", - "resolved": "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz", - "integrity": "sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg==", - "hasInstallScript": true, - "bin": { - "yarn": "bin/yarn.js", - "yarnpkg": "bin/yarn.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 20869d3..5b02fc9 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,6 @@ }, "dependencies": { "@modelcontextprotocol/sdk": "^1.6.0", - "yarn": "^1.22.22", "zod": ">= 3" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" diff --git a/yarn.lock b/yarn.lock index 00f74aa..236c64e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1567,11 +1567,6 @@ wrappy@1: resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== -yarn@^1.22.22: - version "1.22.22" - resolved "https://registry.npmjs.org/yarn/-/yarn-1.22.22.tgz" - integrity sha512-prL3kGtyG7o9Z9Sv8IPfBNrWTDmXB4Qbes8A9rEzt6wkJV8mUvoirjU0Mp3GGAU06Y0XQyA3/2/RQFVuK7MTfg== - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz" From 94b82c71a70ffc2ef18157ca0e7ae1ec0517bf36 Mon Sep 17 00:00:00 2001 From: Taylor Hatfield <35178877+thatfield1@users.noreply.github.com> Date: Thu, 3 Jul 2025 15:18:58 -0500 Subject: [PATCH 3/3] fix: format code with Prettier --- src/eval/eval.ts | 8 ++++---- src/operations/documents.ts | 29 ++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/eval/eval.ts b/src/eval/eval.ts index d12482f..2dfc4d0 100644 --- a/src/eval/eval.ts +++ b/src/eval/eval.ts @@ -9,9 +9,9 @@ import { GetControlsTool, GetControlTestsTool, } from "../operations/controls.js"; -import { +import { GetDocumentsTool, - GetDocumentControlsTool + GetDocumentControlsTool, } from "../operations/documents.js"; // Format all tools for OpenAI @@ -64,7 +64,7 @@ const tools = [ parameters: zodToJsonSchema(GetControlTestsTool.parameters), }, }, - { + { type: "function" as const, function: { name: GetDocumentsTool.name, @@ -72,7 +72,7 @@ const tools = [ parameters: zodToJsonSchema(GetDocumentsTool.parameters), }, }, - { + { type: "function" as const, function: { name: GetDocumentControlsTool.name, diff --git a/src/operations/documents.ts b/src/operations/documents.ts index 6227331..146aa2a 100644 --- a/src/operations/documents.ts +++ b/src/operations/documents.ts @@ -14,7 +14,7 @@ export async function getDocuments( } if (args.pageCursor !== undefined) { url.searchParams.append("pageCursor", args.pageCursor); - } + } if (args.frameworkMatchesAny !== undefined) { url.searchParams.append("frameworkMatchesAny", args.frameworkMatchesAny); } @@ -45,7 +45,10 @@ export async function getDocuments( export async function getDocumentControls( args: z.infer, ): Promise { - const url = new URL(`/v1/documents/${args.documentId}/controls`, baseApiUrl()); + const url = new URL( + `/v1/documents/${args.documentId}/controls`, + baseApiUrl(), + ); if (args.pageSize !== undefined) { url.searchParams.append("pageSize", args.pageSize.toString()); } @@ -86,9 +89,18 @@ const FRAMEWORK_FILTER_DESCRIPTION = `Filter by framework. Non-exhaustive exampl export const GetDocumentsInput = z.object({ pageSize: z.number().describe(PAGE_SIZE_DESCRIPTION).optional(), - pageCursor: z.string().describe("Used for pagination. Leave blank to start from the first page.").optional(), - statusMatchesAny: z.string().describe(DOCUMENT_STATUS_FILTER_DESCRIPTION).optional(), - frameworkMatchesAny: z.string().describe(FRAMEWORK_FILTER_DESCRIPTION).optional(), + pageCursor: z + .string() + .describe("Used for pagination. Leave blank to start from the first page.") + .optional(), + statusMatchesAny: z + .string() + .describe(DOCUMENT_STATUS_FILTER_DESCRIPTION) + .optional(), + frameworkMatchesAny: z + .string() + .describe(FRAMEWORK_FILTER_DESCRIPTION) + .optional(), }); export const GetDocumentsTool: Tool = { @@ -100,11 +112,14 @@ export const GetDocumentsTool: Tool = { export const GetDocumentControlsInput = z.object({ documentId: z.string().describe("Lowercase with hyphens"), pageSize: z.number().describe(PAGE_SIZE_DESCRIPTION).optional(), - pageCursor: z.string().describe("Used for pagination. Leave blank to start from the first page.").optional(), + pageCursor: z + .string() + .describe("Used for pagination. Leave blank to start from the first page.") + .optional(), }); export const GetDocumentControlsTool: Tool = { name: "get_document_controls", description: "Get the controls associated to the specified document.", parameters: GetDocumentControlsInput, -}; \ No newline at end of file +};