diff --git a/.gitignore b/.gitignore index 6d20071..8d4ce3d 100644 --- a/.gitignore +++ b/.gitignore @@ -139,4 +139,10 @@ node_modules/ build/ # Claude Code generated files -CLAUDE.md \ No newline at end of file +CLAUDE.md + +# backup files +*.bak* + +# Mac filesystem +*.DS_store \ No newline at end of file diff --git a/README.md b/README.md index f70c6e5..19d9fde 100644 --- a/README.md +++ b/README.md @@ -6,25 +6,81 @@ A [Model Context Protocol](https://modelcontextprotocol.com/) server that provid ## Features -### Security Test Management +### Controls -- Access Vanta's 1,200+ automated security tests that run continuously to monitor compliance -- Retrieve test results with filtering by status (passing/failing), cloud provider (AWS/Azure/GCP), or compliance framework -- Get detailed information about failing resources (test entities) that need remediation +- List security controls or fetch a specific control by ID +- Discover which automated tests validate each control +- Review evidence documents mapped to controls -### Compliance Framework Operations +| Tool Name | Description | +| -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [`controls`](https://developer.vanta.com/reference/listcontrols) | Access security controls in your Vanta account. Provide controlId to get a specific control, or omit to list all controls with optional framework filtering. | +| [`list_control_tests`](https://developer.vanta.com/reference/listtestsforcontrol) | Enumerate automated tests that validate a specific security control, including status and failing entity details. | +| [`list_control_documents`](https://developer.vanta.com/reference/listcontroldocuments) | List documents that provide evidence for a specific security control so you can quickly locate supporting artifacts. | -- Access 35+ supported compliance frameworks including SOC 2, ISO 27001, HIPAA, GDPR, FedRAMP, and PCI -- Retrieve detailed control requirements and evidence mappings for each framework -- Monitor framework completion progress and compliance status -- Get specific control details that map to automated tests and required documentation +### Documents -### Security Control Management +- Enumerate compliance documents across your organization +- Inspect the controls, links, or uploads associated with a document -- List all security controls across all compliance frameworks in your account -- View control names, descriptions, framework mappings, and implementation status -- Get specific tests that validate each security control -- Understand which automated tests monitor compliance for specific controls +| Tool Name | Description | +| ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| [`documents`](https://developer.vanta.com/reference/listdocuments) | List documents in your Vanta account or retrieve a specific document by ID with metadata for compliance and evidence management. | +| [`document_resources`](https://developer.vanta.com/reference/listdocumentcontrols) | Retrieve resources linked to a document (controls, links, uploads) by specifying the desired resource type. | + +### Frameworks + +- Review framework adoption and progress metrics across your organization +- Drill into the controls required by each framework + +| Tool Name | Description | +| ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | +| [`frameworks`](https://developer.vanta.com/reference/listframeworks) | List compliance frameworks available in your Vanta account along with completion status and progress metrics. | +| [`list_framework_controls`](https://developer.vanta.com/reference/listframeworkcontrols) | Retrieve the controls associated with a framework, including descriptions, implementation guidance, and current compliance status. | + +### Integrations + +- Enumerate connected integrations and review their metadata +- Explore supported resource kinds and fetch integration resources on demand + +| Tool Name | Description | +| ------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`integrations`](https://developer.vanta.com/reference/listintegrations) | List integrations connected to your Vanta account or fetch details for a specific integration, including supported resource kinds and connection status. | +| [`integration_resources`](https://developer.vanta.com/reference/listresourcekindsummaries) | Access integration resources by selecting the desired operation (`list_kinds`, `get_kind_details`, `list_resources`, or `get_resource`). | + +### People + +- List or retrieve people for compliance and access reviews + +| Tool Name | Description | +| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | +| [`people`](https://developer.vanta.com/reference/listpeople) | List people in your Vanta account or retrieve a specific person by ID, including role, email, and group membership metadata. | + +### Risks + +- Track risk scenarios, their status, scoring, and treatment plans + +| Tool Name | Description | +| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | +| [`risks`](https://developer.vanta.com/reference/listriskscenarios) | List risk scenarios managed in your risk register or fetch a specific scenario by ID to review status, scoring, and treatment information. | + +### Tests + +- Monitor automated security tests running in your environment +- Investigate the entities associated with a specific test + +| Tool Name | Description | +| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`tests`](https://developer.vanta.com/reference/listtests) | Retrieve Vanta's automated security and compliance tests. Filter by status, integration, or framework to understand which controls are passing or failing. | +| [`list_test_entities`](https://developer.vanta.com/reference/gettestentities) | Get the resources monitored by a specific security test, including failing entities that require remediation. | + +### Vulnerabilities + +- Review vulnerabilities surfaced by Vanta, including CVE metadata and affected assets + +| Tool Name | Description | +| ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [`vulnerabilities`](https://developer.vanta.com/reference/listvulnerabilities) | List vulnerabilities detected across your infrastructure or retrieve a specific vulnerability by ID with CVE details, severity, and impacted asset information. | ### Multi-Region Support @@ -33,14 +89,22 @@ A [Model Context Protocol](https://modelcontextprotocol.com/) server that provid ## Tools -| Tool Name | Description | -| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `get_tests` | Retrieve Vanta's automated security and compliance tests. Filter by status (OK, NEEDS_ATTENTION, DEACTIVATED), cloud integration (aws, azure, gcp), or compliance framework (soc2, iso27001, hipaa). Returns test results showing which security controls are passing or failing across your infrastructure. | -| `get_test_entities` | Get specific resources (entities) that are failing a particular security test. For example, if an AWS security group test is failing, this returns the actual security group IDs and details about what's wrong. Essential for understanding exactly which infrastructure components need remediation. | -| `get_frameworks` | List all compliance frameworks available in your Vanta account (SOC 2, ISO 27001, HIPAA, GDPR, FedRAMP, PCI, etc.) along with completion status and progress metrics. Shows which frameworks you're actively pursuing and their current compliance state. | -| `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. | +| Tool Name | Description | +| ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | +| [`tests`](https://developer.vanta.com/reference/listtests) | Retrieve Vanta's automated security and compliance tests. Filter by status, integration, or framework to understand pass/fail posture quickly. | +| [`list_test_entities`](https://developer.vanta.com/reference/gettestentities) | Get resources monitored by a particular test, including failing entities that need remediation. | +| [`controls`](https://developer.vanta.com/reference/listcontrols) | List security controls in your Vanta account or retrieve a specific control by ID with framework mapping details. | +| [`list_control_tests`](https://developer.vanta.com/reference/listtestsforcontrol) | Enumerate automated tests that validate a specific control, complete with status and failing entity information. | +| [`list_control_documents`](https://developer.vanta.com/reference/listcontroldocuments) | List documents mapped to a control to locate supporting evidence quickly. | +| [`documents`](https://developer.vanta.com/reference/listdocuments) | List compliance documents or fetch details for a specific document, including metadata. | +| [`document_resources`](https://developer.vanta.com/reference/listdocumentcontrols) | Retrieve resources linked to a document (controls, links, uploads) by choosing the desired resource type. | +| [`integrations`](https://developer.vanta.com/reference/listintegrations) | List integrations connected to your Vanta account or fetch details for a specific integration, including resource kinds and connection status. | +| [`integration_resources`](https://developer.vanta.com/reference/listresourcekindsummaries) | Inspect integration resource kinds, schema information, full resource lists, or a specific resource by selecting from the supported operations. | +| [`frameworks`](https://developer.vanta.com/reference/listframeworks) | List compliance frameworks with completion status and progress metrics for each. | +| [`list_framework_controls`](https://developer.vanta.com/reference/listframeworkcontrols) | Retrieve the controls associated with a compliance framework, including descriptions and implementation guidance. | +| [`people`](https://developer.vanta.com/reference/listpeople) | List people across your organization or look up a specific person by ID with role, email, and group membership metadata. | +| [`risks`](https://developer.vanta.com/reference/listriskscenarios) | List risk scenarios under management or fetch a specific scenario to review status, scoring, and treatment plans. | +| [`vulnerabilities`](https://developer.vanta.com/reference/listvulnerabilities) | List detected vulnerabilities or retrieve a specific item with CVE metadata, severity, and impacted assets. | ## Configuration @@ -155,6 +219,62 @@ Now you can configure Claude Desktop or Cursor to use the built executable: } ``` +## Development + +This server is built with TypeScript and includes the following development tools: + +- **TypeScript**: For type safety and better development experience +- **ESLint**: For code quality and consistency +- **Automated Tool Registry**: Zero-maintenance tool registration system +- **DRY Utilities**: Centralized utilities to reduce code duplication + +### Project Structure + +``` +vanta-mcp-server/ +├── src/ +│ ├── operations/ # MCP tool implementations +│ │ ├── index.ts # Barrel export for all operations +│ │ ├── common/ # Shared utilities and infrastructure +│ │ │ ├── descriptions.ts # Centralized parameter descriptions +│ │ │ ├── imports.ts # Common imports barrel for operations +│ │ │ └── utils.ts # DRY utilities and request handlers +│ │ ├── controls.ts # Control-related operations +│ │ ├── vendors.ts # Vendor-related operations +│ │ ├── people.ts # People-related operations +│ │ ├── documents.ts # Document-related operations +│ │ ├── frameworks.ts # Framework-related operations +│ │ ├── risks.ts # Risk scenario operations +│ │ ├── tests.ts # Test-related operations +│ │ ├── integrations.ts # Integration-related operations (consolidated) +│ │ ├── discovered-vendors.ts # Discovery operations (consolidated) +│ │ ├── trust-centers.ts # Trust Center operations +│ │ └── ... # Other resource operations (18 total) +│ ├── eval/ # Evaluation and testing framework +│ │ ├── eval.ts # LLM evaluation test cases +│ │ └── README.md # Evaluation documentation +│ ├── api.ts # Base API configuration +│ ├── auth.ts # Authentication handling +│ ├── config.ts # Control enabled tools +│ ├── index.ts # Main server entry point +│ ├── registry.ts # Automated tool registration +│ └── types.ts # Type definitions +├── build/ # Compiled JavaScript output +└── README.md # This file +``` + +### Architecture Highlights + +- **Consolidated Tool Pattern**: Single tools intelligently handle both list and get operations with optional ID parameters +- **Reduced Complexity**: 43 tools (down from 53) through smart consolidation while maintaining full functionality +- **Clean Organization**: Operations files are cleanly separated from infrastructure code +- **Common Subdirectory**: All shared utilities, imports, and descriptions are organized in `operations/common/` +- **Automated Registry**: New tools are automatically discovered and registered without manual configuration +- **DRY Principles**: Extensive code reuse through centralized utilities and schema factories +- **Type Safety**: Full TypeScript coverage with comprehensive type definitions + +For detailed architecture documentation, see [`src/operations/README.md`](src/operations/README.md). + ## Debugging You can use the MCP Inspector to debug the server: @@ -165,13 +285,21 @@ npx @modelcontextprotocol/inspector npx @vantasdk/vanta-mcp-server The inspector will open in your browser, allowing you to test tool calls and inspect the server's behavior. +If you want to test a local build you can do so using: + +```bash +npx @modelcontextprotocol/inspector node path/to/build/index.js +``` + +In the browser window you will then need to add the environment variable "VANTA_ENV_FILE": "/absolute/path/to/your/vanta-credentials.env" + ## Example Usage ### Get failing AWS tests for SOC2 ```typescript { - "tool": "get_tests", + "tool": "list_tests", "arguments": { "statusFilter": "NEEDS_ATTENTION", "integrationFilter": "aws", diff --git a/src/config.ts b/src/config.ts new file mode 100644 index 0000000..c3771ef --- /dev/null +++ b/src/config.ts @@ -0,0 +1,36 @@ +const normalizeName = (name: string): string => name.trim().toLowerCase(); + +const enabledToolNames = [ + // Add tool names here to restrict the server to a subset of tools. + // Leave the array empty to enable every tool. + // Example: + // "tests", + // "list_test_entities", + "tests", + "list_test_entities", + "people", + "documents", + "document_resources", + "integrations", + "integration_resources", + "controls", + "list_control_tests", + "list_control_documents", + "vulnerabilities", + "frameworks", + "list_framework_controls", + "risks", +].map(normalizeName); + +export const enabledTools = new Set(enabledToolNames); + +export const hasEnabledToolFilter = enabledTools.size > 0; + +export const isToolEnabled = (toolName: string): boolean => { + if (!hasEnabledToolFilter) { + return true; + } + return enabledTools.has(normalizeName(toolName)); +}; + +export const getEnabledToolNames = (): string[] => [...enabledTools]; diff --git a/src/eval/README.md b/src/eval/README.md index 52cd00b..c9906a7 100644 --- a/src/eval/README.md +++ b/src/eval/README.md @@ -40,19 +40,75 @@ OPENAI_API_KEY="your_openai_api_key_here" node build/eval/eval.js ## Test Cases -The evaluation includes 11 test cases covering: +The evaluation includes 66 test cases covering: ### ✅ **Tool Selection Tests** -- **AWS Security Review**: `get_tests` with AWS and NEEDS_ATTENTION filters -- **SOC2 Compliance**: `get_tests` with SOC2 framework filter -- **Entity Details**: `get_test_entities` for specific failing resources -- **Maintenance Deactivation**: `deactivate_test_entity` for suppressing alerts -- **Framework Listing**: `get_frameworks` for available frameworks -- **Control Requirements**: `get_framework_controls` for specific framework details -- **Status Percentage**: `get_frameworks` for completion percentages -- **Control Listing**: `get_controls` for all security controls -- **Control Tests**: `get_control_tests` for tests validating specific controls +- **Framework Listing**: `frameworks` to list available compliance frameworks +- **Framework Details**: `frameworks` with frameworkId for specific framework information +- **Framework Controls**: `list_framework_controls` for control requirements in specific frameworks +- **Control Listing**: `controls` to list all security controls +- **Control Details**: `controls` with controlId for specific control information +- **Control Tests**: `list_control_tests` for tests validating specific controls +- **Library Controls**: `list_library_controls` for available Vanta library controls +- **Control Documents**: `list_control_documents` for documents associated with controls +- **Risk Listing**: `risks` to list all risk scenarios +- **Risk Details**: `risks` with riskId for specific risk scenario information +- **Test Listing**: `tests` to list all security tests +- **Test Details**: `tests` with testId for specific test information +- **Test Entities**: `list_test_entities` for resources tested by specific tests +- **Integration Listing**: `integrations` to list connected integrations +- **Integration Details**: `integrations` with integrationId for specific integration information +- **Integration Resource Kinds**: `list_integration_resource_kinds` for available resource types +- **Integration Resource Details**: `get_integration_resource_kind_details` for resource type schemas +- **Integration Resources**: `list_integration_resources` for monitored resources +- **Integration Resource Info**: `get_integration_resource` for specific resource details +- **Vendor Listing**: `vendors` to list all vendors +- **Vendor Details**: `vendors` with vendorId for specific vendor information +- **Vendor Documents**: `list_vendor_documents` for vendor compliance documentation +- **Vendor Findings**: `list_vendor_findings` for vendor security issues +- **Vendor Security Reviews**: `list_vendor_security_reviews` for vendor assessments +- **Vendor Security Review Details**: `get_vendor_security_review` for specific review information +- **Vendor Security Review Documents**: `list_vendor_security_review_documents` for review documentation +- **Document Listing**: `documents` to list all compliance documents +- **Document Details**: `documents` with documentId for specific document information +- **Document Controls**: `list_document_controls` for controls associated with documents +- **Document Links**: `list_document_links` for external references in documents +- **Document Uploads**: `list_document_uploads` for file uploads attached to documents +- **Document Downloads**: `download_document_file` for intelligently downloading files (text content for readable files, metadata for binary files) +- **Policy Listing**: `policies` to list all organizational policies +- **Policy Details**: `policies` with policyId for specific policy information +- **Discovered Vendors**: `list_discovered_vendors` for automatically discovered vendors +- **Discovered Vendor Accounts**: `list_discovered_vendor_accounts` for detailed vendor account information +- **Group Listing**: `groups` to list all organizational groups +- **Group Details**: `groups` with groupId for specific group information +- **Group Membership**: `list_group_people` for people in specific groups +- **People Listing**: `people` to list all people in the organization +- **Person Details**: `people` with personId for specific person information +- **Vulnerability Listing**: `vulnerabilities` to list all detected vulnerabilities +- **Vulnerability Details**: `vulnerabilities` with vulnerabilityId for specific vulnerability information +- **Vulnerability Remediations**: `list_vulnerability_remediations` for tracking remediation efforts +- **Vulnerable Assets**: `vulnerable_assets` to list assets affected by vulnerabilities +- **Vulnerable Asset Details**: `vulnerable_assets` with vulnerableAssetId for specific asset vulnerability information +- **Monitored Computers**: `monitored_computers` to list all computers being monitored for compliance +- **Computer Details**: `monitored_computers` with monitoredComputerId for specific computer information +- **Vendor Risk Attributes**: `list_vendor_risk_attributes` for available risk assessment criteria +- **Trust Center Configuration**: `get_trust_center` for Trust Center settings and branding +- **Trust Center Access Requests**: `trust_center_access_requests` for managing customer access (list or get specific) +- **Trust Center Analytics**: `list_trust_center_viewer_activity_events` for engagement tracking +- **Control Categories**: `trust_center_control_categories` for compliance organization (list or get specific) +- **Published Controls**: `trust_center_controls` for public compliance controls (list or get specific) +- **Trust Center FAQs**: `trust_center_faqs` for customer information (list or get specific) +- **Trust Center Resources**: `list_trust_center_resources` for downloadable materials +- **Resource Documents**: `get_trust_center_document` for specific document details +- **Resource Media**: `get_trust_center_resource_media` for downloading Trust Center files +- **Trust Center Subprocessors**: `trust_center_subprocessors` for third-party service providers (list or get specific) +- **Trust Center Updates**: `trust_center_updates` for compliance status changes (list or get specific) +- **Trust Center Viewers**: `trust_center_viewers` for access management (list or get specific) +- **Trust Center Subscribers**: `get_trust_center_subscriber` for subscriber details +- **Trust Center Subscriber Groups**: `trust_center_subscriber_groups` for subscriber organization (list or get specific) +- **Trust Center Historical Access**: `list_trust_center_historical_access_requests` for audit tracking +- **Trust Center All Subscribers**: `list_trust_center_subscribers` for communication management ### ❌ **Negative Tests** @@ -65,20 +121,26 @@ The evaluation includes 11 test cases covering: 🧪 Vanta MCP Server Tool Evaluation ==================================== -📝 Test: Should call get_tests with AWS filter and NEEDS_ATTENTION status -💬 Prompt: "What security issues do I have in my AWS infrastructure?" -🎯 Expected Tool: get_tests -✅ PASS: Correctly called get_tests +📝 Test: Should call frameworks to list available frameworks +💬 Prompt: "What compliance frameworks are we tracking?" +🎯 Expected Tool: frameworks +✅ PASS: Correctly called frameworks +✅ Parameters match expected values +📋 Called with: {} + +📝 Test: Should call controls with controlId for specific control details +💬 Prompt: "Get details for control ID data-protection-2" +🎯 Expected Tool: controls +✅ PASS: Correctly called controls ✅ Parameters match expected values 📋 Called with: { - "statusFilter": "NEEDS_ATTENTION", - "integrationFilter": "aws" + "controlId": "data-protection-2" } 📊 Final Results ================ -✅ Passed: 11/11 tests -❌ Failed: 0/11 tests +✅ Passed: 66/66 tests +❌ Failed: 0/66 tests 📈 Success Rate: 100% 🎉 All tests passed! Tool calling behavior is working correctly. ``` @@ -101,6 +163,33 @@ The evaluation includes 11 test cases covering: - No tool was called when one was expected - Tool was called when none should be +## Consolidated Tool Architecture + +The Vanta MCP Server uses a **consolidated tool pattern** where many tools can handle both list and get-by-ID operations: + +### **Consolidated Tools** (53 total) + +These tools accept an optional ID parameter: + +- **Without ID**: Lists all resources with optional filtering and pagination +- **With ID**: Returns the specific resource details + +Examples: + +- `frameworks` - Lists all frameworks OR get specific framework with `frameworkId` +- `controls` - Lists all controls OR get specific control with `controlId` +- `vendors` - Lists all vendors OR get specific vendor with `vendorId` +- `documents` - Lists all documents OR get specific document with `documentId` + +### **Specialized Tools** + +Some tools remain separate for specific operations: + +- `list_control_tests` - Lists tests for a control +- `list_framework_controls` - Lists controls in a framework +- `download_document_file` - Downloads document files +- `get_integration_resource` - Gets specific integration resources + ## Customizing Tests To add new test cases, edit `eval.ts` and add to the `testCases` array: @@ -159,5 +248,6 @@ This evaluation system helps ensure that: - **Real-world prompts** trigger the correct tools - **Parameter passing** works as expected - **Scope boundaries** are respected (no tools called for non-compliance queries) +- **Consolidated architecture** works effectively (LLMs understand optional ID parameters) -The goal is to maintain high confidence that AI assistants will use the Vanta MCP Server correctly for compliance and security management tasks. +The goal is to maintain high confidence that AI assistants will use the Vanta MCP Server correctly for compliance and security management tasks, taking advantage of the intelligent consolidated tool pattern for optimal efficiency. diff --git a/src/eval/eval.ts b/src/eval/eval.ts index 649ec6b..ccdf6f2 100644 --- a/src/eval/eval.ts +++ b/src/eval/eval.ts @@ -1,145 +1,659 @@ import OpenAI from "openai"; import { zodToJsonSchema } from "zod-to-json-schema"; -import { GetTestsTool, GetTestEntitiesTool } from "../operations/tests.js"; import { - GetFrameworksTool, - GetFrameworkControlsTool, -} from "../operations/frameworks.js"; -import { - GetControlsTool, - GetControlTestsTool, -} from "../operations/controls.js"; + // Tests + TestsTool, + ListTestEntitiesTool, + // Frameworks + FrameworksTool, + ListFrameworkControlsTool, + // Controls + ControlsTool, + ListControlTestsTool, + ListLibraryControlsTool, + ListControlDocumentsTool, + // Risks + RisksTool, + // Integrations + IntegrationsTool, + IntegrationResourcesTool, + // Vendors + VendorsTool, + VendorComplianceTool, + GetVendorSecurityReviewTool, + ListVendorSecurityReviewDocumentsTool, + // Documents + DocumentsTool, + DocumentResourcesTool, + DownloadDocumentFileTool, + // Policies + PoliciesTool, + // Discovered Vendors + ListDiscoveredVendorsTool, + ListDiscoveredVendorAccountsTool, + // Groups + GroupsTool, + ListGroupPeopleTool, + // People + PeopleTool, + // Vulnerabilities + VulnerabilitiesTool, + // Vulnerability Remediations + ListVulnerabilityRemediationsTool, + // Vulnerable Assets + VulnerableAssetsTool, + // Monitored Computers + MonitoredComputersTool, + // Vendor Risk Attributes + ListVendorRiskAttributesTool, + // Trust Centers + GetTrustCenterTool, + TrustCenterAccessRequestsTool, + ListTrustCenterViewerActivityEventsTool, + TrustCenterControlCategoriesTool, + TrustCenterControlsTool, + TrustCenterFaqsTool, + ListTrustCenterResourcesTool, + GetTrustCenterDocumentTool, + GetTrustCenterResourceMediaTool, + TrustCenterSubprocessorsTool, + TrustCenterUpdatesTool, + TrustCenterViewersTool, + GetTrustCenterSubscriberTool, + TrustCenterSubscriberGroupsTool, + ListTrustCenterHistoricalAccessRequestsTool, + ListTrustCenterSubscribersTool, +} from "../operations/index.js"; // Format all tools for OpenAI const tools = [ { type: "function" as const, function: { - name: GetTestsTool.name, - description: GetTestsTool.description, - parameters: zodToJsonSchema(GetTestsTool.parameters), + name: TestsTool.name, + description: TestsTool.description, + parameters: zodToJsonSchema(TestsTool.parameters), }, }, { type: "function" as const, function: { - name: GetTestEntitiesTool.name, - description: GetTestEntitiesTool.description, - parameters: zodToJsonSchema(GetTestEntitiesTool.parameters), + name: ListTestEntitiesTool.name, + description: ListTestEntitiesTool.description, + parameters: zodToJsonSchema(ListTestEntitiesTool.parameters), }, }, { type: "function" as const, function: { - name: GetFrameworksTool.name, - description: GetFrameworksTool.description, - parameters: zodToJsonSchema(GetFrameworksTool.parameters), + name: FrameworksTool.name, + description: FrameworksTool.description, + parameters: zodToJsonSchema(FrameworksTool.parameters), }, }, { type: "function" as const, function: { - name: GetFrameworkControlsTool.name, - description: GetFrameworkControlsTool.description, - parameters: zodToJsonSchema(GetFrameworkControlsTool.parameters), + name: ListFrameworkControlsTool.name, + description: ListFrameworkControlsTool.description, + parameters: zodToJsonSchema(ListFrameworkControlsTool.parameters), }, }, { type: "function" as const, function: { - name: GetControlsTool.name, - description: GetControlsTool.description, - parameters: zodToJsonSchema(GetControlsTool.parameters), + name: ControlsTool.name, + description: ControlsTool.description, + parameters: zodToJsonSchema(ControlsTool.parameters), }, }, { type: "function" as const, function: { - name: GetControlTestsTool.name, - description: GetControlTestsTool.description, - parameters: zodToJsonSchema(GetControlTestsTool.parameters), + name: ListControlTestsTool.name, + description: ListControlTestsTool.description, + parameters: zodToJsonSchema(ListControlTestsTool.parameters), }, }, -]; - -// Test cases with expected tool calls -interface TestCase { - prompt: string; - expectedTool: string; - expectedParams?: Record; - description: string; -} - -const testCases: TestCase[] = [ { - prompt: "What security issues do I have in my AWS infrastructure?", - expectedTool: "get_tests", - expectedParams: { - statusFilter: "NEEDS_ATTENTION", - integrationFilter: "aws", + type: "function" as const, + function: { + name: ListLibraryControlsTool.name, + description: ListLibraryControlsTool.description, + parameters: zodToJsonSchema(ListLibraryControlsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: ListControlDocumentsTool.name, + description: ListControlDocumentsTool.description, + parameters: zodToJsonSchema(ListControlDocumentsTool.parameters), }, - description: - "Should call get_tests with AWS filter and NEEDS_ATTENTION status", }, { - prompt: "Show me all my SOC2 compliance tests that are failing", - expectedTool: "get_tests", - expectedParams: { - frameworkFilter: "soc2", - statusFilter: "NEEDS_ATTENTION", + type: "function" as const, + function: { + name: RisksTool.name, + description: RisksTool.description, + parameters: zodToJsonSchema(RisksTool.parameters), }, - description: "Should call get_tests with SOC2 framework filter", }, { - prompt: - "Show me the specific failing entities for test ID aws-security-groups-open-to-world", - expectedTool: "get_test_entities", - expectedParams: { testId: "aws-security-groups-open-to-world" }, - description: "Should call get_test_entities for specific test details", + type: "function" as const, + function: { + name: IntegrationsTool.name, + description: IntegrationsTool.description, + parameters: zodToJsonSchema(IntegrationsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: IntegrationResourcesTool.name, + description: IntegrationResourcesTool.description, + parameters: zodToJsonSchema(IntegrationResourcesTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: VendorsTool.name, + description: VendorsTool.description, + parameters: zodToJsonSchema(VendorsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: VendorComplianceTool.name, + description: VendorComplianceTool.description, + parameters: zodToJsonSchema(VendorComplianceTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: GetVendorSecurityReviewTool.name, + description: GetVendorSecurityReviewTool.description, + parameters: zodToJsonSchema(GetVendorSecurityReviewTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: ListVendorSecurityReviewDocumentsTool.name, + description: ListVendorSecurityReviewDocumentsTool.description, + parameters: zodToJsonSchema( + ListVendorSecurityReviewDocumentsTool.parameters, + ), + }, + }, + { + type: "function" as const, + function: { + name: DocumentsTool.name, + description: DocumentsTool.description, + parameters: zodToJsonSchema(DocumentsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: DocumentResourcesTool.name, + description: DocumentResourcesTool.description, + parameters: zodToJsonSchema(DocumentResourcesTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: DownloadDocumentFileTool.name, + description: DownloadDocumentFileTool.description, + parameters: zodToJsonSchema(DownloadDocumentFileTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: PoliciesTool.name, + description: PoliciesTool.description, + parameters: zodToJsonSchema(PoliciesTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: ListDiscoveredVendorsTool.name, + description: ListDiscoveredVendorsTool.description, + parameters: zodToJsonSchema(ListDiscoveredVendorsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: ListDiscoveredVendorAccountsTool.name, + description: ListDiscoveredVendorAccountsTool.description, + parameters: zodToJsonSchema(ListDiscoveredVendorAccountsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: GroupsTool.name, + description: GroupsTool.description, + parameters: zodToJsonSchema(GroupsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: ListGroupPeopleTool.name, + description: ListGroupPeopleTool.description, + parameters: zodToJsonSchema(ListGroupPeopleTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: PeopleTool.name, + description: PeopleTool.description, + parameters: zodToJsonSchema(PeopleTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: VulnerabilitiesTool.name, + description: VulnerabilitiesTool.description, + parameters: zodToJsonSchema(VulnerabilitiesTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: ListVulnerabilityRemediationsTool.name, + description: ListVulnerabilityRemediationsTool.description, + parameters: zodToJsonSchema(ListVulnerabilityRemediationsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: VulnerableAssetsTool.name, + description: VulnerableAssetsTool.description, + parameters: zodToJsonSchema(VulnerableAssetsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: MonitoredComputersTool.name, + description: MonitoredComputersTool.description, + parameters: zodToJsonSchema(MonitoredComputersTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: ListVendorRiskAttributesTool.name, + description: ListVendorRiskAttributesTool.description, + parameters: zodToJsonSchema(ListVendorRiskAttributesTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: GetTrustCenterTool.name, + description: GetTrustCenterTool.description, + parameters: zodToJsonSchema(GetTrustCenterTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: TrustCenterAccessRequestsTool.name, + description: TrustCenterAccessRequestsTool.description, + parameters: zodToJsonSchema(TrustCenterAccessRequestsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: ListTrustCenterViewerActivityEventsTool.name, + description: ListTrustCenterViewerActivityEventsTool.description, + parameters: zodToJsonSchema( + ListTrustCenterViewerActivityEventsTool.parameters, + ), + }, + }, + { + type: "function" as const, + function: { + name: TrustCenterControlCategoriesTool.name, + description: TrustCenterControlCategoriesTool.description, + parameters: zodToJsonSchema(TrustCenterControlCategoriesTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: TrustCenterControlsTool.name, + description: TrustCenterControlsTool.description, + parameters: zodToJsonSchema(TrustCenterControlsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: TrustCenterFaqsTool.name, + description: TrustCenterFaqsTool.description, + parameters: zodToJsonSchema(TrustCenterFaqsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: ListTrustCenterResourcesTool.name, + description: ListTrustCenterResourcesTool.description, + parameters: zodToJsonSchema(ListTrustCenterResourcesTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: GetTrustCenterDocumentTool.name, + description: GetTrustCenterDocumentTool.description, + parameters: zodToJsonSchema(GetTrustCenterDocumentTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: GetTrustCenterResourceMediaTool.name, + description: GetTrustCenterResourceMediaTool.description, + parameters: zodToJsonSchema(GetTrustCenterResourceMediaTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: TrustCenterSubprocessorsTool.name, + description: TrustCenterSubprocessorsTool.description, + parameters: zodToJsonSchema(TrustCenterSubprocessorsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: TrustCenterUpdatesTool.name, + description: TrustCenterUpdatesTool.description, + parameters: zodToJsonSchema(TrustCenterUpdatesTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: TrustCenterViewersTool.name, + description: TrustCenterViewersTool.description, + parameters: zodToJsonSchema(TrustCenterViewersTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: GetTrustCenterSubscriberTool.name, + description: GetTrustCenterSubscriberTool.description, + parameters: zodToJsonSchema(GetTrustCenterSubscriberTool.parameters), + }, }, + { + type: "function" as const, + function: { + name: TrustCenterSubscriberGroupsTool.name, + description: TrustCenterSubscriberGroupsTool.description, + parameters: zodToJsonSchema(TrustCenterSubscriberGroupsTool.parameters), + }, + }, + { + type: "function" as const, + function: { + name: ListTrustCenterHistoricalAccessRequestsTool.name, + description: ListTrustCenterHistoricalAccessRequestsTool.description, + parameters: zodToJsonSchema( + ListTrustCenterHistoricalAccessRequestsTool.parameters, + ), + }, + }, + { + type: "function" as const, + function: { + name: ListTrustCenterSubscribersTool.name, + description: ListTrustCenterSubscribersTool.description, + parameters: zodToJsonSchema(ListTrustCenterSubscribersTool.parameters), + }, + }, +]; + +// Test cases for the LLM evaluation +export const testCases = [ { prompt: "What compliance frameworks are we tracking?", - expectedTool: "get_frameworks", + expectedTool: "frameworks", expectedParams: {}, - description: "Should call get_frameworks to list available frameworks", + description: "Should call frameworks to list available frameworks", }, { - prompt: "Get the control requirements for framework ID soc2", - expectedTool: "get_framework_controls", - expectedParams: { frameworkId: "soc2" }, - description: "Should call get_framework_controls for SOC2", + prompt: + "Show me all discovered vendors flagged by Vanta's discovery engine", + expectedTool: "list_discovered_vendors", + expectedParams: {}, + description: + "Should call list_discovered_vendors to list all discovered vendors", + }, + { + prompt: + "Show the accounts associated with discovered vendor discovered-vendor-123", + expectedTool: "list_discovered_vendor_accounts", + expectedParams: { discoveredVendorId: "discovered-vendor-123" }, + description: + "Should call list_discovered_vendor_accounts with discoveredVendorId for vendor accounts", }, { prompt: "What is the current % status of my SOC 2?", - expectedTool: "get_frameworks", + expectedTool: "frameworks", expectedParams: {}, - description: "Should call get_frameworks to get SOC2 completion percentage", + description: "Should call frameworks to get SOC2 completion percentage", }, { prompt: "List all security controls in my Vanta account", - expectedTool: "get_controls", + expectedTool: "controls", expectedParams: {}, - description: "Should call get_controls to list all available controls", + description: "Should call controls to list all available controls", + }, + { + prompt: + "Show me all automated tests for the access-control-user-provisioning control", + expectedTool: "list_control_tests", + expectedParams: { controlId: "access-control-user-provisioning" }, + description: + "Should call list_control_tests to get tests for specific control", }, { - prompt: "Show me the tests for control ID access-control-1", - expectedTool: "get_control_tests", - expectedParams: { controlId: "access-control-1" }, - description: "Should call get_control_tests for specific control", + prompt: "Get details for control ID data-protection-2", + expectedTool: "controls", + expectedParams: { controlId: "data-protection-2" }, + description: + "Should call controls with controlId for specific control details", }, { - prompt: "What programming tests should I write for my API?", - expectedTool: "none", + prompt: "Show me details for framework ID soc2", + expectedTool: "frameworks", + expectedParams: { frameworkId: "soc2" }, + description: + "Should call frameworks with frameworkId for SOC2 framework details", + }, + { + prompt: "What controls does the SOC 2 framework require?", + expectedTool: "list_framework_controls", + expectedParams: { frameworkId: "soc2" }, + description: + "Should call list_framework_controls to get SOC2 framework requirements", + }, + { + prompt: "Get details for risk scenario ID risk-scenario-123", + expectedTool: "risks", + expectedParams: { riskId: "risk-scenario-123" }, + description: + "Should call risks with riskId for specific risk scenario details", + }, + { + prompt: "Show me all risk scenarios categorized as Access Control", + expectedTool: "risks", + expectedParams: { categoryMatchesAny: "Access Control" }, + description: + "Should call risks with category filter for Access Control risks", + }, + { + prompt: "What integrations are connected to my Vanta account?", + expectedTool: "integrations", + expectedParams: {}, + description: "Should call integrations to list all connected integrations", + }, + { + prompt: "Show me details for integration ID aws", + expectedTool: "integrations", + expectedParams: { integrationId: "aws" }, + description: + "Should call integrations with integrationId for AWS integration details", + }, + { + prompt: "List all vendors in my Vanta account", + expectedTool: "vendors", expectedParams: {}, + description: "Should call vendors to list all vendors", + }, + { + prompt: "Get details for vendor ID vendor-123", + expectedTool: "vendors", + expectedParams: { vendorId: "vendor-123" }, description: - "Should NOT call any Vanta tools - this is about code testing, not compliance", + "Should call vendors with vendorId for specific vendor details", }, { - prompt: "Help me debug this JavaScript function", - expectedTool: "none", + prompt: + "Show me all the documents we have uploaded to Vanta for compliance purposes.", + expectedTool: "documents", expectedParams: {}, + description: "Should call documents to list all compliance documents", + }, + { + prompt: + "I need to see the details of document DOC-12345 including its metadata and compliance mappings.", + expectedTool: "documents", + expectedParams: { documentId: "DOC-12345" }, description: - "Should NOT call any Vanta tools - this is about code debugging", + "Should call documents with documentId for specific document details", + }, + { + prompt: + "I need to review the details of our data retention policy with ID POLICY-789.", + expectedTool: "policies", + expectedParams: { policyId: "POLICY-789" }, + description: + "Should call policies with policyId for specific policy details", + }, + { + prompt: + "Show me all the organizational groups we have set up for access management.", + expectedTool: "groups", + expectedParams: {}, + description: "Should call groups to list all organizational groups", + }, + { + prompt: "I need details about the Engineering group with ID GROUP-456.", + expectedTool: "groups", + expectedParams: { groupId: "GROUP-456" }, + description: "Should call groups with groupId for specific group details", + }, + { + prompt: "List all people in our organization for the compliance audit.", + expectedTool: "people", + expectedParams: {}, + description: "Should call people to list all people in the organization", + }, + { + prompt: "Get me the details for employee PERSON-789.", + expectedTool: "people", + expectedParams: { personId: "PERSON-789" }, + description: "Should call people with personId for specific person details", + }, + { + prompt: + "Show me all the security vulnerabilities detected in our infrastructure.", + expectedTool: "vulnerabilities", + expectedParams: {}, + description: + "Should call vulnerabilities to list all detected vulnerabilities", + }, + { + prompt: + "I need detailed information about vulnerability VULN-456 including its CVE data.", + expectedTool: "vulnerabilities", + expectedParams: { vulnerabilityId: "VULN-456" }, + description: + "Should call vulnerabilities with vulnerabilityId for specific vulnerability details", + }, + { + prompt: + "List all assets that are affected by vulnerabilities for our security review.", + expectedTool: "vulnerable_assets", + expectedParams: {}, + description: + "Should call vulnerable_assets to list all assets affected by vulnerabilities", + }, + { + prompt: + "Get details about vulnerable asset ASSET-789 and its security status.", + expectedTool: "vulnerable_assets", + expectedParams: { vulnerableAssetId: "ASSET-789" }, + description: + "Should call vulnerable_assets with vulnerableAssetId for specific asset details", + }, + { + prompt: + "Show me all the computers being monitored for compliance across our organization.", + expectedTool: "monitored_computers", + expectedParams: {}, + description: + "Should call monitored_computers to list all monitored computers", + }, + { + prompt: "I need details about the monitored computer with ID COMP-456.", + expectedTool: "monitored_computers", + expectedParams: { computerId: "COMP-456" }, + description: + "Should call monitored_computers with computerId for specific computer details", + }, + { + prompt: "What are all the security tests currently running in Vanta?", + expectedTool: "tests", + expectedParams: {}, + description: "Should call tests to list all security tests", + }, + { + prompt: "Show me details for test ID TEST-789.", + expectedTool: "tests", + expectedParams: { testId: "TEST-789" }, + description: "Should call tests with testId for specific test details", + }, + { + prompt: + "What entities are being tested by the test with ID aws-ec2-security-groups?", + expectedTool: "list_test_entities", + expectedParams: { testId: "aws-ec2-security-groups" }, + description: + "Should call list_test_entities to get entities for specific test", }, ]; @@ -188,10 +702,7 @@ for (const testCase of testCases) { console.log(`✅ PASS: Correctly called ${calledTool}`); // Check specific parameters if provided - if ( - testCase.expectedParams && - Object.keys(testCase.expectedParams).length > 0 - ) { + if (Object.keys(testCase.expectedParams).length > 0) { let paramsMatch = true; for (const [key, value] of Object.entries( testCase.expectedParams, @@ -253,3 +764,5 @@ if (passedTests === totalTests) { "⚠️ Some tests failed. Review the tool descriptions or test cases.", ); } + +export { tools }; diff --git a/src/index.ts b/src/index.ts index eaaa4d1..c522e6c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,89 +2,48 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { - getTestEntities, - GetTestEntitiesTool, - getTests, - GetTestsTool, -} from "./operations/tests.js"; -import { - GetFrameworkControlsTool, - GetFrameworksTool, - getFrameworkControls, - getFrameworks, -} from "./operations/frameworks.js"; -import { - GetControlsTool, - GetControlTestsTool, - getControls, - getControlTests, -} from "./operations/controls.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.0.0", - description: - "Model Context Protocol server for Vanta's automated security compliance platform. Provides access to security tests, compliance frameworks, and security controls for SOC 2, ISO 27001, HIPAA, GDPR and other standards.", }); -server.tool( - GetTestsTool.name, - GetTestsTool.description, - GetTestsTool.parameters.shape, - getTests, -); - -server.tool( - GetTestEntitiesTool.name, - GetTestEntitiesTool.description, - GetTestEntitiesTool.parameters.shape, - getTestEntities, -); - -server.tool( - GetFrameworksTool.name, - GetFrameworksTool.description, - GetFrameworksTool.parameters.shape, - getFrameworks, -); - -server.tool( - GetFrameworkControlsTool.name, - GetFrameworkControlsTool.description, - GetFrameworkControlsTool.parameters.shape, - getFrameworkControls, -); - -server.tool( - GetControlsTool.name, - GetControlsTool.description, - GetControlsTool.parameters.shape, - getControls, -); - -server.tool( - GetControlTestsTool.name, - GetControlTestsTool.description, - GetControlTestsTool.parameters.shape, - getControlTests, -); - 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 initialize token:", error); + console.error("Failed to start Vanta MCP Server:", error); process.exit(1); } - - const transport = new StdioServerTransport(); - await server.connect(transport); } +// Handle shutdown gracefully +process.on("SIGINT", () => { + console.error("Shutting down Vanta MCP Server..."); + process.exit(0); +}); + main().catch(error => { - console.error("Fatal error in main():", error); + console.error("Fatal error:", error); process.exit(1); }); diff --git a/src/operations/README.md b/src/operations/README.md new file mode 100644 index 0000000..48773fc --- /dev/null +++ b/src/operations/README.md @@ -0,0 +1,311 @@ +# Operations Architecture Reference + +This document explains the structure, conventions, and utilities used in the Vanta MCP Server operations layer. It is intended for developers extending the `src/operations/` directory. + +## Overview + +- **Purpose**: Each operations file wraps one or more Vanta API GET endpoints as MCP tools. +- **Scope**: Operation modules and registered tools. +- **Patterns**: Consolidated list/get tools, resource-specific routing tools, and specialized tools for unique behaviors (e.g., downloads). +- **Automation**: Tools are auto-registered through the registry system; common logic lives in `src/operations/common/`. + +## Directory Layout + +``` +src/operations/ +├── README.md # Operations reference (this file) +├── README.proposed.md # Proposal used for the latest refresh +├── index.ts # Barrel export of all operations modules +├── common/ +│ ├── descriptions.ts # Reusable parameter descriptions (e.g., DOCUMENT_ID_DESCRIPTION) +│ ├── imports.ts # Barrel import for CallToolResult, Tool, z, utilities, constants +│ └── utils.ts # Shared schema factories, request helpers, response handlers +├── documents.ts # Document tools (consolidated + download) +├── frameworks.ts # Framework tools (consolidated + nested resources) +├── controls.ts +├── discovered-vendors.ts +├── ... +``` + +## Core Concepts + +### Consolidated Tool Pattern + +Many resources expose both “list” and “get by ID” behaviors within a single tool. The helper `createConsolidatedSchema` creates a schema with an optional ID plus pagination fields, and `makeConsolidatedRequest` routes the request based on the presence of that ID. + +Example (`frameworks.ts`): + +```typescript +const FrameworksInput = createConsolidatedSchema({ + paramName: "frameworkId", + description: FRAMEWORK_ID_DESCRIPTION, + resourceName: "framework", +}); + +export async function frameworks( + args: z.infer, +): Promise { + return makeConsolidatedRequest("/v1/frameworks", args, "frameworkId"); +} +``` + +- **No ID provided** → lists frameworks with pagination. +- **ID provided** → fetches a specific framework. + +### Resource-Specific Routing Tools + +Some resources expose additional nested endpoints. These tools accept a required ID plus a discriminator to route to different endpoints. + +Example (`documents.ts`): + +```typescript +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, +}); + +export async function documentResources( + args: z.infer, +): Promise { + 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 url = buildUrl(endpoints[resourceType], params); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} +``` + +### Specialized Tools + +When behavior diverges from JSON-based responses (e.g., file downloads), tools implement custom response logic. + +Example (`documents.ts`): + +```typescript +const DownloadDocumentFileInput = z.object({ + uploadedFileId: z + .string() + .describe( + "Uploaded file ID to download, e.g. 'upload-123' or specific uploaded file identifier", + ), +}); + +export async function downloadDocumentFile( + args: z.infer, +): Promise { + const url = buildUrl( + `/v1/document-uploads/${String(args.uploadedFileId)}/download`, + ); + const response = await makeAuthenticatedRequest(url); + + if (!response.ok) { + return handleApiResponse(response); + } + + const contentType = + response.headers.get("content-type") ?? "application/octet-stream"; + const contentLength = response.headers.get("content-length"); + + if ( + contentType.startsWith("text/") || + contentType.includes("application/json") || + contentType.includes("application/xml") || + contentType.includes("application/javascript") || + contentType.includes("application/csv") || + contentType.includes("text/csv") + ) { + const textContent = await response.text(); + return { + content: [ + { + type: "text" as const, + text: `Document File Content (${contentType}):\n\n${textContent}`, + }, + ], + }; + } + + return { + content: [ + { + type: "text" as const, + text: `Document File Information:\n- Content Type: ${contentType}\n- Content Length: ${contentLength ? `${contentLength} bytes` : "Unknown"}\n- File Type: ${contentType.startsWith("image/") ? "Image" : contentType.startsWith("video/") ? "Video" : contentType.startsWith("audio/") ? "Audio" : contentType.startsWith("application/pdf") ? "PDF Document" : "Binary File"}\n- Upload ID: ${String(args.uploadedFileId)}\n\nNote: This is a binary file. Use appropriate tools to download and process the actual file content.`, + }, + ], + }; +} +``` + +## Shared Infrastructure (`common/`) + +### `descriptions.ts` + +- Contains reusable strings for parameter descriptions (e.g., `DOCUMENT_ID_DESCRIPTION`, `FRAMEWORK_ID_DESCRIPTION`). +- Promotes consistency and reduces duplication of descriptive text across operation files. + +### `imports.ts` + +- Re-exports `CallToolResult`, `Tool`, `z`, schema factories, request helpers, and description constants. +- Imported by every operations file so that a single statement brings in all required utilities: + +```typescript +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + createPaginationSchema, + makeConsolidatedRequest, + buildUrl, + makeAuthenticatedRequest, + handleApiResponse, + DOCUMENT_ID_DESCRIPTION, +} from "./common/imports.js"; +``` + +### `utils.ts` + +Key exports include: + +- **Schema factories**: `createConsolidatedSchema`, `createPaginationSchema`, `createIdSchema`, `createIdWithPaginationSchema`, `createFilterSchema`. +- **Request helpers**: `makeConsolidatedRequest`, `makePaginatedGetRequest`, `makeGetByIdRequest`, `makeSimpleGetRequest`. +- **URL utilities**: `buildUrl` for query string construction. +- **Response utilities**: `handleApiResponse`, `createErrorResponse`, `createSuccessResponse`. + +All utilities enforce consistent error handling and response formatting across tools. + +## Anatomy of an Operations File + +Each operations file follows a common structure: + +1. **Imports** from `./common/imports.js` for all dependencies. +2. **Input schemas** using schema factories or explicit Zod objects. +3. **Tool definitions** exporting REST-style tool metadata. +4. **Implementation functions** calling Vanta endpoints using utilities. +5. **Registry export** listing every tool/handler pair for automated registration. + +Example skeleton: + +```typescript +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + makeConsolidatedRequest, + buildUrl, + makeAuthenticatedRequest, + handleApiResponse, +} from "./common/imports.js"; + +// 2. Input Schemas +const ResourceInput = createConsolidatedSchema({ + paramName: "resourceId", + description: "Resource ID...", + resourceName: "resource", +}); + +const ResourceDetailsInput = z.object({ + resourceId: z.string().describe("Resource ID..."), + detailType: z.enum(["summary", "history"]), + ...createPaginationSchema().shape, +}); + +// 3. Tool Definitions +export const ResourcesTool: Tool = { + name: "resources", + description: "Access resources...", + parameters: ResourceInput, +}; + +export const ResourceDetailsTool: Tool = { + name: "resource_details", + description: "Access resource details...", + parameters: ResourceDetailsInput, +}; + +// 4. Implementation Functions +export async function resources( + args: z.infer, +): Promise { + return makeConsolidatedRequest("/v1/resources", args, "resourceId"); +} + +export async function resourceDetails( + args: z.infer, +): Promise { + const { resourceId, detailType, ...params } = args; + const url = buildUrl( + `/v1/resources/${String(resourceId)}/${detailType}`, + params, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +// 5. Registry Export +export default { + tools: [ + { tool: ResourcesTool, handler: resources }, + { tool: ResourceDetailsTool, handler: resourceDetails }, + ], +}; +``` + +## Naming and Tool Guidelines + +- **Tool names**: Use plural nouns for consolidated tools (e.g., `frameworks`, `documents`). +- **Schema constants**: Use PascalCase with `Input` suffix (e.g., `DocumentsInput`). +- **Implementation functions**: Use camelCase matching tool names (e.g., `frameworks`, `documentResources`). +- **Registry export**: Always include every tool/handler pair in the default export. +- **Descriptions**: Reference centralized descriptions from `common/descriptions.ts` whenever possible. + +## Automated Registration + +- Each operations file exports a default object `{ tools: [...] }`. +- `src/registry.ts` automatically imports every `src/operations/*.ts` module and registers the listed tools (see Step 7 below). + +## Adding or Updating Operations + +1. **Create or edit input schemas** using factory helpers or explicit `z.object` definitions. +2. **Define or update tool metadata** with REST-aligned naming. +3. **Implement handlers** using `makeConsolidatedRequest`, `makePaginatedGetRequest`, or custom logic. +4. **Extend the default export** with the new tool/handler pair. +5. **Update `src/operations/index.ts`** to re-export the module (if a new file is added). +6. **Document new tools** in `README.md` (root) and update evaluation artifacts (below). +7. **Enable the tool in `src/config.ts`**. Add the tool's name to the `enabledToolNames` array to make it available through the MCP server. Leaving the array empty enables _all_ tools. + +## Evaluation Suite Updates + +Whenever tools change: + +- Update `src/eval/eval.ts` to include the new tool definition and test cases. +- Update `src/eval/README.md` to describe new or renamed test scenarios. + +## Testing and Validation + +- **TypeScript Build**: `npm run build` +- **Linting**: `npm run lint -- src/operations/*.ts` +- **Manual Testing**: Invoke tools through the MCP interface if available. + +## Quick Reference + +- **Consolidated tool example**: `frameworks.ts` (`frameworks` tool). +- **Nested resource example**: `documents.ts` (`document_resources` tool). +- **Download example**: `documents.ts` (`download_document_file` tool). +- **Common utilities**: `src/operations/common/utils.ts`. +- **Automated registry**: `src/registry.ts` + per-file `export default { tools: [...] }`. + +--- + +Use this README as the canonical reference for updates to the operations layer. Developers should rely on it when adding, modifying, or auditing tools. diff --git a/src/operations/common/descriptions.ts b/src/operations/common/descriptions.ts new file mode 100644 index 0000000..2c171ce --- /dev/null +++ b/src/operations/common/descriptions.ts @@ -0,0 +1,30 @@ +// Common parameter descriptions used across operations +// This file provides centralized, consistent descriptions for commonly used parameters +// across all operations files, ensuring uniformity and maintainability. + +export const PAGE_SIZE_DESCRIPTION = `Controls the maximum number of results returned in a single response. +Allowed values: 1–100. Default is 10.`; + +export const PAGE_CURSOR_DESCRIPTION = `A marker or pointer telling the API where to start fetching items for the +subsequent page in a paginated response. Leave blank to start from the first page.`; + +export const DOCUMENT_ID_DESCRIPTION = + "Document ID to operate on, e.g. 'document-123' or specific document identifier"; + +export const SLUG_ID_DESCRIPTION = + "Slug ID to operate on, e.g. 'my-trust-center' or specific slug identifier"; + +export const CONTROL_ID_DESCRIPTION = + "Control ID to operate on, e.g. 'control-123' or specific control identifier"; + +export const FRAMEWORK_ID_DESCRIPTION = + "Framework ID to operate on, e.g. 'framework-123' or specific framework identifier"; + +export const INTEGRATION_ID_DESCRIPTION = + "Integration ID to operate on, e.g. 'integration-123' or specific integration identifier"; + +export const VENDOR_ID_DESCRIPTION = + "Vendor ID to operate on, e.g. 'vendor-123' or specific vendor identifier"; + +export const DISCOVERED_VENDOR_ID_DESCRIPTION = + "Discovered vendor ID to operate on, e.g. 'discovered-vendor-123' or specific discovered vendor identifier"; diff --git a/src/operations/common/imports.ts b/src/operations/common/imports.ts new file mode 100644 index 0000000..3372ea1 --- /dev/null +++ b/src/operations/common/imports.ts @@ -0,0 +1,14 @@ +// Common imports barrel export for operations files +// This file provides all the common imports that operations files need, +// reducing import clutter and ensuring consistency across the codebase. + +// Core MCP and type imports +export { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +export { Tool } from "../../types.js"; +export { z } from "zod"; + +// Re-export all utilities +export * from "./utils.js"; + +// Re-export all common descriptions +export * from "./descriptions.js"; diff --git a/src/operations/common/utils.ts b/src/operations/common/utils.ts new file mode 100644 index 0000000..172019e --- /dev/null +++ b/src/operations/common/utils.ts @@ -0,0 +1,335 @@ +import { getValidToken, refreshToken } from "../../auth.js"; +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { z } from "zod"; +import { baseApiUrl } from "../../api.js"; +import { + PAGE_SIZE_DESCRIPTION, + PAGE_CURSOR_DESCRIPTION, +} from "./descriptions.js"; + +export async function createAuthHeaders(): Promise> { + const token = await getValidToken(); + return { + "Authorization": `Bearer ${token}`, + "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. + */ +export async function makeAuthenticatedRequest( + url: string, + options: RequestInit = {}, +): Promise { + const headers = await createAuthHeaders(); + + const requestOptions: RequestInit = { + ...options, + headers: { + ...headers, + ...options.headers, + }, + }; + + // 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; +} + +// ========================================== +// RESPONSE PROCESSING UTILITIES +// ========================================== + +/** + * Creates an error response with consistent formatting + */ +export function createErrorResponse(statusText: string): CallToolResult { + return { + content: [{ type: "text", text: `Error: ${statusText}` }], + isError: true, + }; +} + +/** + * Creates a success response with JSON content + */ +export async function createSuccessResponse( + response: Response, +): Promise { + try { + const jsonData: unknown = await response.json(); + return { + content: [{ type: "text", text: JSON.stringify(jsonData, null, 2) }], + }; + } catch (error) { + return createErrorResponse( + `Failed to parse JSON response: ${error instanceof Error ? error.message : "Unknown error"}`, + ); + } +} + +/** + * Handles API response consistently - either returns success or error + */ +export async function handleApiResponse( + response: Response, +): Promise { + if (!response.ok) { + return createErrorResponse(response.statusText); + } + return createSuccessResponse(response); +} + +// ========================================== +// SCHEMA FACTORY FUNCTIONS +// ========================================== + +/** + * Creates a standard pagination schema + */ +export function createPaginationSchema( + customFields: Record = {}, +): z.ZodObject> { + return z.object({ + pageSize: z + .number() + .min(1) + .max(100) + .describe(PAGE_SIZE_DESCRIPTION) + .optional(), + pageCursor: z.string().describe(PAGE_CURSOR_DESCRIPTION).optional(), + ...customFields, + }); +} + +/** + * Creates a filter schema with pagination base + */ +export function createFilterSchema( + customFields: Record = {}, +): z.ZodObject> { + return z.object({ + pageSize: z + .number() + .min(1) + .max(100) + .describe(PAGE_SIZE_DESCRIPTION) + .optional(), + pageCursor: z.string().describe(PAGE_CURSOR_DESCRIPTION).optional(), + ...customFields, + }); +} + +/** + * Creates a schema with a single required ID parameter plus pagination + */ +export function createIdWithPaginationSchema(params: { + paramName: string; + description: string; +}): z.ZodObject> { + return z.object({ + [params.paramName]: z.string().describe(params.description), + pageSize: z + .number() + .min(1) + .max(100) + .describe(PAGE_SIZE_DESCRIPTION) + .optional(), + pageCursor: z.string().describe(PAGE_CURSOR_DESCRIPTION).optional(), + }); +} + +/** + * Creates a schema with a single required ID parameter only + */ +export function createIdSchema(params: { + paramName: string; + description: string; +}): z.ZodObject> { + return z.object({ + [params.paramName]: z.string().describe(params.description), + }); +} + +/** + * Creates a schema for consolidated tools that can either list resources or get a single resource by ID + */ +export function createConsolidatedSchema( + params: { + paramName: string; + description: string; + resourceName: string; + }, + additionalFields: Record = {}, +): z.ZodObject> { + const idDescription = `Optional ${params.resourceName} ID. If provided, returns the specific ${params.resourceName}. If omitted, lists all ${params.resourceName}s with optional filtering and pagination.`; + + return z.object({ + [params.paramName]: z.string().describe(idDescription).optional(), + ...createPaginationSchema().shape, + ...additionalFields, + }); +} + +/** + * Creates a schema for Trust Center consolidated tools that require a slugId plus optional resource ID + */ +export function createTrustCenterConsolidatedSchema( + params: { + paramName: string; + description: string; + resourceName: string; + }, + additionalFields: Record = {}, +): z.ZodObject> { + const idDescription = `Optional ${params.resourceName} ID. If provided, returns the specific ${params.resourceName}. If omitted, lists all ${params.resourceName}s with optional filtering and pagination.`; + + return z.object({ + slugId: z + .string() + .describe( + "Trust Center slug ID, e.g. 'company-trust-center' or specific trust center identifier", + ), + [params.paramName]: z.string().describe(idDescription).optional(), + ...createPaginationSchema().shape, + ...additionalFields, + }); +} + +// ========================================== +// URL CONSTRUCTION UTILITIES +// ========================================== + +/** + * Builds a URL with query parameters + */ +export function buildUrl( + basePath: string, + params: Record = {}, +): string { + const url = new URL(basePath, baseApiUrl()); + + for (const [key, value] of Object.entries(params)) { + if (value !== undefined) { + if (Array.isArray(value)) { + // Handle arrays by joining with commas + url.searchParams.set(key, value.join(",")); + } else { + url.searchParams.set(key, String(value)); + } + } + } + + return url.toString(); +} + +// ========================================== +// REQUEST HANDLER UTILITIES +// ========================================== + +/** + * Makes a simple GET request to the specified endpoint + */ +export async function makeSimpleGetRequest( + endpoint: string, +): Promise { + const url = buildUrl(endpoint); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +/** + * Makes a paginated GET request with query parameters + */ +export async function makePaginatedGetRequest( + endpoint: string, + params: Record = {}, +): Promise { + const url = buildUrl(endpoint, params); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +/** + * Makes a GET request for a specific resource by ID + */ +export async function makeGetByIdRequest( + endpoint: string, + id: string, +): Promise { + const url = buildUrl(`${endpoint}/${String(id)}`); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +/** + * Makes a request that can either list resources or get a single resource by ID + */ +export async function makeConsolidatedRequest( + endpoint: string, + params: Record, + idParamName: string, +): Promise { + const id = params[idParamName]; + + if (id) { + // Single resource request + return makeGetByIdRequest(endpoint, String(id)); + } else { + // List request - remove the ID param from the parameters + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { [idParamName]: _removedId, ...listParams } = params; + return makePaginatedGetRequest(endpoint, listParams); + } +} + +/** + * Makes a Trust Center request that can either list resources or get a single resource by ID + */ +export async function makeTrustCenterConsolidatedRequest( + baseEndpoint: string, + params: Record, + idParamName: string, + resourcePath: string, +): Promise { + const { slugId, [idParamName]: resourceId, ...otherParams } = params; + + if (resourceId) { + // Single resource request: /v1/trust-centers/{slugId}/{resourcePath}/{resourceId} + const url = buildUrl( + `${baseEndpoint}/${String(slugId)}/${resourcePath}/${String(resourceId)}`, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); + } else { + // List request: /v1/trust-centers/{slugId}/{resourcePath} + const url = buildUrl( + `${baseEndpoint}/${String(slugId)}/${resourcePath}`, + otherParams, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); + } +} diff --git a/src/operations/controls.ts b/src/operations/controls.ts index ec287a8..f0a6b9f 100644 --- a/src/operations/controls.ts +++ b/src/operations/controls.ts @@ -1,115 +1,119 @@ -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"; +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + createIdWithPaginationSchema, + makeConsolidatedRequest, + buildUrl, + makeAuthenticatedRequest, + handleApiResponse, + CONTROL_ID_DESCRIPTION, +} from "./common/imports.js"; -const GetControlsInput = z.object({ - pageSize: z - .number() - .describe("Number of controls to return (1-100, default 10)") - .optional(), - pageCursor: z.string().describe("Pagination cursor for next page").optional(), - frameworkMatchesAny: z - .array(z.string()) - .describe( - "Filter controls by framework IDs. Returns controls that belong to any of the specified frameworks, e.g. ['soc2', 'iso27001', 'hipaa']", - ) - .optional(), +// 2. Input Schemas +const ControlsInput = createConsolidatedSchema( + { + paramName: "controlId", + description: CONTROL_ID_DESCRIPTION, + resourceName: "control", + }, + { + frameworkMatchesAny: z + .array(z.string()) + .describe( + "Filter controls by framework IDs. Returns controls that belong to any of the specified frameworks, e.g. ['soc2', 'iso27001', 'hipaa']", + ) + .optional(), + }, +); + +const ListControlTestsInput = createIdWithPaginationSchema({ + paramName: "controlId", + description: CONTROL_ID_DESCRIPTION, }); -export const GetControlsTool: Tool = { - name: "get_controls", - description: - "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. Optionally filter by specific frameworks using frameworkMatchesAny.", - parameters: GetControlsInput, -}; +const ListLibraryControlsInput = createIdWithPaginationSchema({ + paramName: "controlId", + description: CONTROL_ID_DESCRIPTION, +}); -const GetControlTestsInput = z.object({ - controlId: z - .string() - .describe( - "Control ID to get tests for, e.g. 'access-control-1' or 'data-protection-2'", - ), - pageSize: z - .number() - .describe("Number of tests to return (1-100, default 10)") - .optional(), - pageCursor: z.string().describe("Pagination cursor for next page").optional(), +const ListControlDocumentsInput = createIdWithPaginationSchema({ + paramName: "controlId", + description: CONTROL_ID_DESCRIPTION, }); -export const GetControlTestsTool: Tool = { - name: "get_control_tests", +// 3. Tool Definitions +export const ControlsTool: Tool = { + name: "controls", description: - "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.", - parameters: GetControlTestsInput, + "Access security controls in your Vanta account. Provide controlId to get a specific control, or omit to list all controls with optional framework filtering. Returns control names, descriptions, framework mappings, and implementation status.", + parameters: ControlsInput, }; -export async function getControls( - args: z.infer, -): Promise { - const url = new URL("/v1/controls", 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) { - args.frameworkMatchesAny.forEach(framework => { - url.searchParams.append("frameworkMatchesAny", framework); - }); - } - - const response = await makeAuthenticatedRequest(url.toString()); +export const ListControlTestsTool: Tool = { + name: "list_control_tests", + description: + "List 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.", + parameters: ListControlTestsInput, +}; - if (!response.ok) { - return { - content: [ - { - type: "text" as const, - text: `Error: ${response.statusText}`, - }, - ], - }; - } +export const ListLibraryControlsTool: Tool = { + name: "list_library_controls", + description: + "List Vanta controls from the library. These are pre-built security controls available in Vanta's control library that can be added to your account.", + parameters: ListLibraryControlsInput, +}; - return { - content: [ - { type: "text" as const, text: JSON.stringify(await response.json()) }, - ], +export const ListControlDocumentsTool: Tool = + { + name: "list_control_documents", + description: + "List a control's documents. Get all documents that are associated with or provide evidence for a specific security control.", + parameters: ListControlDocumentsInput, }; -} -export async function getControlTests( - args: z.infer, +// 4. Implementation Functions +export async function controls( + args: z.infer, ): Promise { - const url = new URL(`/v1/controls/${args.controlId}/tests`, baseApiUrl()); - - if (args.pageSize !== undefined) { - url.searchParams.append("pageSize", args.pageSize.toString()); - } - if (args.pageCursor !== undefined) { - url.searchParams.append("pageCursor", args.pageCursor); - } + return makeConsolidatedRequest("/v1/controls", args, "controlId"); +} - const response = await makeAuthenticatedRequest(url.toString()); +export async function listControlTests( + args: z.infer, +): Promise { + const { controlId, ...params } = args; + const url = buildUrl(`/v1/controls/${String(controlId)}/tests`, params); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} - if (!response.ok) { - return { - content: [ - { - type: "text" as const, - text: `Error: ${response.statusText}`, - }, - ], - }; - } +export async function listLibraryControls( + args: z.infer, +): Promise { + const { controlId, ...params } = args; + const url = buildUrl(`/v1/library-controls/${String(controlId)}`, params); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} - return { - content: [ - { type: "text" as const, text: JSON.stringify(await response.json()) }, - ], - }; +export async function listControlDocuments( + args: z.infer, +): Promise { + const { controlId, ...params } = args; + const url = buildUrl(`/v1/controls/${String(controlId)}/documents`, params); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); } + +// Registry export for automated tool registration +export default { + tools: [ + { tool: ControlsTool, handler: controls }, + { tool: ListControlTestsTool, handler: listControlTests }, + { tool: ListLibraryControlsTool, handler: listLibraryControls }, + { tool: ListControlDocumentsTool, handler: listControlDocuments }, + ], +}; diff --git a/src/operations/discovered-vendors.ts b/src/operations/discovered-vendors.ts new file mode 100644 index 0000000..f9797ee --- /dev/null +++ b/src/operations/discovered-vendors.ts @@ -0,0 +1,70 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createPaginationSchema, + createIdWithPaginationSchema, + makePaginatedGetRequest, + buildUrl, + makeAuthenticatedRequest, + handleApiResponse, + DISCOVERED_VENDOR_ID_DESCRIPTION, +} from "./common/imports.js"; + +// 2. Input Schemas +const ListDiscoveredVendorsInput = createPaginationSchema(); + +const ListDiscoveredVendorAccountsInput = createIdWithPaginationSchema({ + paramName: "discoveredVendorId", + description: DISCOVERED_VENDOR_ID_DESCRIPTION, +}); + +// 3. Tool Definitions +export const ListDiscoveredVendorsTool: Tool< + typeof ListDiscoveredVendorsInput +> = { + name: "list_discovered_vendors", + description: + "List discovered vendors identified by Vanta's automated discovery. Returns vendor names, domains, discovery sources, and linkage status to managed vendor records.", + parameters: ListDiscoveredVendorsInput, +}; + +export const ListDiscoveredVendorAccountsTool: Tool< + typeof ListDiscoveredVendorAccountsInput +> = { + name: "list_discovered_vendor_accounts", + description: + "List accounts associated with a discovered vendor. Provide discoveredVendorId to retrieve account identifiers, connection details, and discovery metadata.", + parameters: ListDiscoveredVendorAccountsInput, +}; + +// 4. Implementation Functions +export async function listDiscoveredVendors( + args: z.infer, +): Promise { + return makePaginatedGetRequest("/v1/discovered-vendors", args); +} + +export async function listDiscoveredVendorAccounts( + args: z.infer, +): Promise { + const { discoveredVendorId, ...params } = args; + const url = buildUrl( + `/v1/discovered-vendors/${String(discoveredVendorId)}/accounts`, + params, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +// Registry export for automated tool registration +export default { + tools: [ + { tool: ListDiscoveredVendorsTool, handler: listDiscoveredVendors }, + { + tool: ListDiscoveredVendorAccountsTool, + handler: listDiscoveredVendorAccounts, + }, + ], +}; diff --git a/src/operations/documents.ts b/src/operations/documents.ts new file mode 100644 index 0000000..ee572d5 --- /dev/null +++ b/src/operations/documents.ts @@ -0,0 +1,188 @@ +// 1. Imports +import { + CallToolResult, + Tool, + 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: Tool = { + 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: Tool = { + 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: Tool = + { + 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: z.infer, +): Promise { + return makeConsolidatedRequest("/v1/documents", args, "documentId"); +} + +export async function documentResources( + args: z.infer, +): Promise { + 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: z.infer, +): Promise { + 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" as const, + text: `Document File Content (${contentType}):\n\n${textContent}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text" as const, + 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" as const, + 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 }, + ], +}; diff --git a/src/operations/frameworks.ts b/src/operations/frameworks.ts index 093af61..79675b0 100644 --- a/src/operations/frameworks.ts +++ b/src/operations/frameworks.ts @@ -1,89 +1,69 @@ -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"; +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + createIdWithPaginationSchema, + makeConsolidatedRequest, + buildUrl, + makeAuthenticatedRequest, + handleApiResponse, + FRAMEWORK_ID_DESCRIPTION, +} from "./common/imports.js"; -const GetFrameworksInput = z.object({ - pageSize: z.number().optional(), - pageCursor: z.string().optional(), +// 2. Input Schemas +const FrameworksInput = createConsolidatedSchema({ + paramName: "frameworkId", + description: FRAMEWORK_ID_DESCRIPTION, + resourceName: "framework", }); -export const GetFrameworksTool: Tool = { - name: "get_frameworks", +const ListFrameworkControlsInput = createIdWithPaginationSchema({ + paramName: "frameworkId", + description: FRAMEWORK_ID_DESCRIPTION, +}); + +// 3. Tool Definitions +export const FrameworksTool: Tool = { + name: "frameworks", description: - "List all compliance frameworks available in your Vanta account (SOC 2, ISO 27001, HIPAA, GDPR, FedRAMP, PCI, etc.) along with completion status and progress metrics. Shows which frameworks you're actively pursuing and their current compliance state including status of controls, documents, and tests for each framework.", - parameters: GetFrameworksInput, + "Access compliance frameworks in your Vanta account. Provide frameworkId to get a specific framework, or omit to list all frameworks. Returns frameworks (SOC 2, ISO 27001, HIPAA, GDPR, etc.) with completion status and progress metrics.", + parameters: FrameworksInput, }; -const GetFrameworkControlsInput = z.object({ - frameworkId: z.string(), - pageSize: z.number().optional(), - pageCursor: z.string().optional(), -}); - -export const GetFrameworkControlsTool: Tool = - { - name: "get_framework_controls", - description: - "Get the detailed CONTROL REQUIREMENTS for a specific framework (requires frameworkId). Use this when you need the specific control details, requirements, and implementation guidance for a known framework like 'soc2' or 'iso27001'. This returns the actual security controls and their descriptions, NOT the framework list. Use get_frameworks first if you need to see available frameworks.", - parameters: GetFrameworkControlsInput, - }; +export const ListFrameworkControlsTool: Tool< + typeof ListFrameworkControlsInput +> = { + name: "list_framework_controls", + description: + "List framework's controls. Get detailed security control requirements for a specific compliance framework. Returns the specific controls, their descriptions, implementation guidance, and current compliance status.", + parameters: ListFrameworkControlsInput, +}; -export async function getFrameworkControls( - args: z.infer, +// 4. Implementation Functions +export async function frameworks( + args: z.infer, ): Promise { - const url = new URL( - `/v1/frameworks/${args.frameworkId}/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: `Error: ${response.statusText}` }, - ], - }; - } - - return { - content: [ - { type: "text" as const, text: JSON.stringify(await response.json()) }, - ], - }; + return makeConsolidatedRequest("/v1/frameworks", args, "frameworkId"); } -export async function getFrameworks( - args: z.infer, +export async function listFrameworkControls( + args: z.infer, ): Promise { - const url = new URL("/v1/frameworks", 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()) }, - ], - }; + const { frameworkId, ...params } = args; + const url = buildUrl( + `/v1/frameworks/${String(frameworkId)}/controls`, + params, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); } + +// Registry export for automated tool registration +export default { + tools: [ + { tool: FrameworksTool, handler: frameworks }, + { tool: ListFrameworkControlsTool, handler: listFrameworkControls }, + ], +}; diff --git a/src/operations/groups.ts b/src/operations/groups.ts new file mode 100644 index 0000000..ab3a8d8 --- /dev/null +++ b/src/operations/groups.ts @@ -0,0 +1,65 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + createIdWithPaginationSchema, + makeConsolidatedRequest, + buildUrl, + makeAuthenticatedRequest, + handleApiResponse, +} from "./common/imports.js"; + +// 2. Input Schemas +const GroupsInput = createConsolidatedSchema({ + paramName: "groupId", + description: + "Group ID to retrieve, e.g. 'group-123' or specific group identifier", + resourceName: "group", +}); + +const ListGroupPeopleInput = createIdWithPaginationSchema({ + paramName: "groupId", + description: + "Group ID to get people for, e.g. 'group-123' or specific group identifier", +}); + +// 3. Tool Definitions +export const GroupsTool: Tool = { + name: "groups", + description: + "Access groups in your Vanta account. Provide groupId to get a specific group, or omit to list all groups. Returns group IDs, names, descriptions, and metadata for organizational structure and access management.", + parameters: GroupsInput, +}; + +export const ListGroupPeopleTool: Tool = { + name: "list_group_people", + description: + "List group's people. Get all people who are members of a specific group. Use this to see group membership and organizational structure.", + parameters: ListGroupPeopleInput, +}; + +// 4. Implementation Functions +export async function groups( + args: z.infer, +): Promise { + return makeConsolidatedRequest("/v1/groups", args, "groupId"); +} + +export async function listGroupPeople( + args: z.infer, +): Promise { + const { groupId, ...params } = args; + const url = buildUrl(`/v1/groups/${String(groupId)}/people`, params); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +// Registry export for automated tool registration +export default { + tools: [ + { tool: GroupsTool, handler: groups }, + { tool: ListGroupPeopleTool, handler: listGroupPeople }, + ], +}; diff --git a/src/operations/index.ts b/src/operations/index.ts new file mode 100644 index 0000000..f5315fd --- /dev/null +++ b/src/operations/index.ts @@ -0,0 +1,27 @@ +// Barrel export for all Vanta MCP operations +// This file provides a single entry point for importing any operation tools +// from the Vanta MCP Server operations module. + +// Individual operation modules +export * from "./tests.js"; +export * from "./frameworks.js"; +export * from "./controls.js"; +export * from "./risks.js"; +export * from "./integrations.js"; +export * from "./vendors.js"; +export * from "./documents.js"; +export * from "./policies.js"; +export * from "./discovered-vendors.js"; +export * from "./groups.js"; +export * from "./people.js"; +export * from "./vulnerabilities.js"; +export * from "./vulnerability-remediations.js"; +export * from "./vulnerable-assets.js"; +export * from "./monitored-computers.js"; +export * from "./vendor-risk-attributes.js"; +export * from "./trust-centers.js"; + +// Common utilities and shared resources +export * from "./common/utils.js"; +export * from "./common/descriptions.js"; +export * from "./common/imports.js"; diff --git a/src/operations/integrations.ts b/src/operations/integrations.ts new file mode 100644 index 0000000..2b5fda6 --- /dev/null +++ b/src/operations/integrations.ts @@ -0,0 +1,166 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + makeConsolidatedRequest, + makePaginatedGetRequest, + buildUrl, + makeAuthenticatedRequest, + handleApiResponse, + INTEGRATION_ID_DESCRIPTION, +} from "./common/imports.js"; + +// 2. Input Schemas +const IntegrationsInput = createConsolidatedSchema({ + paramName: "integrationId", + description: INTEGRATION_ID_DESCRIPTION, + resourceName: "integration", +}); + +const IntegrationResourcesInput = z.object({ + integrationId: z.string().describe(INTEGRATION_ID_DESCRIPTION), + operation: z + .enum(["list_kinds", "get_kind_details", "list_resources", "get_resource"]) + .describe( + "Integration resource operation: 'list_kinds' to get available resource types, 'get_kind_details' for schema information, 'list_resources' for all resources of a type, 'get_resource' for specific resource details", + ), + resourceKind: z + .string() + .describe( + "Resource kind to operate on, e.g. 'EC2Instance' or specific resource kind identifier (required for get_kind_details, list_resources, get_resource)", + ) + .optional(), + resourceId: z + .string() + .describe( + "Resource ID to retrieve, e.g. 'resource-123' or specific resource identifier (required for get_resource)", + ) + .optional(), + pageSize: z + .number() + .min(1) + .max(100) + .describe("Number of items to return per page (1-100)") + .optional(), + pageCursor: z + .string() + .describe("Cursor for pagination to get the next page of results") + .optional(), +}); + +// 3. Tool Definitions +export const IntegrationsTool: Tool = { + name: "integrations", + description: + "Access connected integrations in your Vanta account. Provide integrationId to get a specific integration, or omit to list all integrations. Returns integration details, supported resource kinds, and connection status for compliance monitoring.", + parameters: IntegrationsInput, +}; + +export const IntegrationResourcesTool: Tool = + { + name: "integration_resources", + description: + "Access integration resources including resource kinds, resource kind details, and specific resources. Specify operation to perform: 'list_kinds' for available resource types, 'get_kind_details' for schema information, 'list_resources' for all resources of a type, or 'get_resource' for specific resource details.", + parameters: IntegrationResourcesInput, + }; + +// 4. Implementation Functions +export async function integrations( + args: z.infer, +): Promise { + return makeConsolidatedRequest("/v1/integrations", args, "integrationId"); +} + +export async function integrationResources( + args: z.infer, +): Promise { + const { integrationId, operation, resourceKind, resourceId, ...params } = + args; + + switch (operation) { + case "list_kinds": { + return makePaginatedGetRequest( + `/v1/integrations/${String(integrationId)}/resource-kinds`, + params, + ); + } + + case "get_kind_details": { + if (!resourceKind) { + return { + content: [ + { + type: "text", + text: "Error: resourceKind is required for get_kind_details operation", + }, + ], + isError: true, + }; + } + const kindUrl = buildUrl( + `/v1/integrations/${String(integrationId)}/resource-kinds/${String(resourceKind)}`, + ); + const kindResponse = await makeAuthenticatedRequest(kindUrl); + return handleApiResponse(kindResponse); + } + + case "list_resources": { + if (!resourceKind) { + return { + content: [ + { + type: "text", + text: "Error: resourceKind is required for list_resources operation", + }, + ], + isError: true, + }; + } + return makePaginatedGetRequest( + `/v1/integrations/${String(integrationId)}/resource-kinds/${String(resourceKind)}/resources`, + params, + ); + } + + case "get_resource": { + if (!resourceKind || !resourceId) { + return { + content: [ + { + type: "text", + text: "Error: both resourceKind and resourceId are required for get_resource operation", + }, + ], + isError: true, + }; + } + const resourceUrl = buildUrl( + `/v1/integrations/${String(integrationId)}/resource-kinds/${String(resourceKind)}/resources/${String(resourceId)}`, + ); + const resourceResponse = await makeAuthenticatedRequest(resourceUrl); + return handleApiResponse(resourceResponse); + } + + default: { + return { + content: [ + { + type: "text", + text: `Error: Invalid operation '${operation as string}'. Must be one of: list_kinds, get_kind_details, list_resources, get_resource`, + }, + ], + isError: true, + }; + } + } +} + +// Registry export for automated tool registration +export default { + tools: [ + { tool: IntegrationsTool, handler: integrations }, + { tool: IntegrationResourcesTool, handler: integrationResources }, + ], +}; diff --git a/src/operations/monitored-computers.ts b/src/operations/monitored-computers.ts new file mode 100644 index 0000000..76e6eb3 --- /dev/null +++ b/src/operations/monitored-computers.ts @@ -0,0 +1,40 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + makeConsolidatedRequest, +} from "./common/imports.js"; + +// 2. Input Schemas +const MonitoredComputersInput = createConsolidatedSchema({ + paramName: "monitoredComputerId", + description: + "Monitored computer ID to retrieve, e.g. 'comp-123' or specific monitored computer identifier", + resourceName: "monitored computer", +}); + +// 3. Tool Definitions +export const MonitoredComputersTool: Tool = { + name: "monitored_computers", + description: + "Access monitored computers in your Vanta account. Provide monitoredComputerId to get a specific computer, or omit to list all monitored computers. Returns computer details, compliance status, and security measures for device management.", + parameters: MonitoredComputersInput, +}; + +// 4. Implementation Functions +export async function monitoredComputers( + args: z.infer, +): Promise { + return makeConsolidatedRequest( + "/v1/monitored-computers", + args, + "monitoredComputerId", + ); +} + +// Registry export for automated tool registration +export default { + tools: [{ tool: MonitoredComputersTool, handler: monitoredComputers }], +}; diff --git a/src/operations/people.ts b/src/operations/people.ts new file mode 100644 index 0000000..4b2d4db --- /dev/null +++ b/src/operations/people.ts @@ -0,0 +1,46 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + makeConsolidatedRequest, +} from "./common/imports.js"; + +// 2. Input Schemas +const PeopleInput = createConsolidatedSchema( + { + paramName: "personId", + description: + "Person ID to retrieve, e.g. 'person-123' or specific person identifier. If provided, returns the specific person, and no other parameters may be provided. If omitted, lists all people with optional filtering and pagination. ", + resourceName: "person", + }, + { + taskStatusMatchesAny: z + .array(z.string()) + .describe( + "Filter people by task status. Possible values: COMPLETED (Task is completed), IN_PROGRESS (Task is in progress), FAILED (Task failed), NOT_STARTED (Task is not started)", + ) + .optional(), + }, +); + +// 3. Tool Definitions +export const PeopleTool: Tool = { + name: "people", + description: + "Access people in your Vanta account. Provide personId to get a specific person, or omit to list all people. Returns person IDs, names, email addresses, and organizational information for identity and access management.", + parameters: PeopleInput, +}; + +// 4. Implementation Functions +export async function people( + args: z.infer, +): Promise { + return makeConsolidatedRequest("/v1/people", args, "personId"); +} + +// Registry export for automated tool registration +export default { + tools: [{ tool: PeopleTool, handler: people }], +}; diff --git a/src/operations/policies.ts b/src/operations/policies.ts new file mode 100644 index 0000000..cc0dd81 --- /dev/null +++ b/src/operations/policies.ts @@ -0,0 +1,36 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + makeConsolidatedRequest, +} from "./common/imports.js"; + +// 2. Input Schemas +const PoliciesInput = createConsolidatedSchema({ + paramName: "policyId", + description: + "Policy ID to retrieve, e.g. 'policy-123' or specific policy identifier", + resourceName: "policy", +}); + +// 3. Tool Definitions +export const PoliciesTool: Tool = { + name: "policies", + description: + "Access policies in your Vanta account. Provide policyId to get a specific policy, or omit to list all policies. Returns policy IDs, names, and metadata for governance and compliance management.", + parameters: PoliciesInput, +}; + +// 4. Implementation Functions +export async function policies( + args: z.infer, +): Promise { + return makeConsolidatedRequest("/v1/policies", args, "policyId"); +} + +// Registry export for automated tool registration +export default { + tools: [{ tool: PoliciesTool, handler: policies }], +}; diff --git a/src/operations/risks.ts b/src/operations/risks.ts new file mode 100644 index 0000000..aabbb90 --- /dev/null +++ b/src/operations/risks.ts @@ -0,0 +1,52 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + makeConsolidatedRequest, +} from "./common/imports.js"; + +// 2. Input Schemas +const RisksInput = createConsolidatedSchema( + { + paramName: "riskId", + description: + "Risk scenario ID to retrieve, e.g. 'risk-scenario-123' or specific risk identifier. If provided, returns the specific risk scenario, and no other parameters may be provided. If omitted, lists all risk scenarios with optional filtering and pagination.", + resourceName: "risk scenario", + }, + { + categoryMatchesAny: z + .string() + .optional() + .describe( + "Filter by risk category. Example: Access Control, Cryptography, Privacy, etc. Use 'Uncategorized' for risks that don't have a category.", + ), + reviewStatusMatchesAny: z + .array(z.string()) + .describe( + "Filter risk scenarios by review status. Possible values: PENDING, APPROVED, REJECTED", + ) + .optional(), + }, +); + +// 3. Tool Definitions +export const RisksTool: Tool = { + name: "risks", + description: + "Access risk scenarios in your Vanta account. Provide riskId to get a specific risk scenario, or omit to list all risks with optional filtering and pagination. Returns risk details, impact assessments, and mitigation strategies for compliance reporting.", + parameters: RisksInput, +}; + +// 4. Implementation Functions +export async function risks( + args: z.infer, +): Promise { + return makeConsolidatedRequest("/v1/risk-scenarios", args, "riskId"); +} + +// Registry export for automated tool registration +export default { + tools: [{ tool: RisksTool, handler: risks }], +}; diff --git a/src/operations/tests.ts b/src/operations/tests.ts index 064c47d..c6b8951 100644 --- a/src/operations/tests.ts +++ b/src/operations/tests.ts @@ -1,136 +1,83 @@ -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 getTests( - args: z.infer, -): Promise { - const url = new URL("/v1/tests", baseApiUrl()); - - if (args.pageSize !== undefined) { - url.searchParams.append("pageSize", args.pageSize.toString()); - } - if (args.statusFilter !== undefined) { - url.searchParams.append("statusFilter", args.statusFilter); - } - if (args.integrationFilter !== undefined) { - url.searchParams.append("integrationFilter", args.integrationFilter); - } - if (args.controlFilter !== undefined) { - url.searchParams.append("controlFilter", args.controlFilter); - } - if (args.frameworkFilter !== undefined) { - url.searchParams.append("frameworkFilter", args.frameworkFilter); - } +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + createIdWithPaginationSchema, + makeConsolidatedRequest, + buildUrl, + makeAuthenticatedRequest, + handleApiResponse, +} from "./common/imports.js"; + +// 2. Input Schemas +const TestsInput = createConsolidatedSchema( + { + paramName: "testId", + description: + "Test ID to retrieve, e.g. 'test-123' or specific test identifier. If provided, returns the specific test, and no other parameters may be provided. If omitted, lists all tests with optional filtering and pagination.", + resourceName: "test", + }, + { + statusFilter: z + .string() + .describe( + "Filter tests by test status. Possible values: OK (Test passed), DEACTIVATED (Test is deactivated), NEEDS_ATTENTION (Test failed), IN_PROGRESS (Test is in progress), INVALID (Test is invalid), NOT_APPLICABLE (Test is not applicable)", + ) + .optional(), + frameworkFilter: z + .string() + .describe("Filter tests by framework. Provide framework ID.") + .optional(), + integrationFilter: z + .string() + .describe("Filter tests by integration. Provide integration ID.") + .optional(), + }, +); + +const ListTestEntitiesInput = createIdWithPaginationSchema({ + paramName: "testId", + description: + "Test ID to get entities for, e.g. 'test-123' or specific test identifier", +}); - const response = await makeAuthenticatedRequest(url.toString()); +// 3. Tool Definitions +export const TestsTool: Tool = { + name: "tests", + description: + "Access continuous monitoring tests in your Vanta account. Provide testId to get a specific test, or omit to list all tests. Returns test IDs, names, types, schedules, current status, and detailed configuration for compliance monitoring.", + parameters: TestsInput, +}; - if (!response.ok) { - return { - content: [ - { - type: "text" as const, - text: `Url: ${url.toString()}, Error: ${response.statusText}`, - }, - ], - }; - } +export const ListTestEntitiesTool: Tool = { + name: "list_test_entities", + description: + "List a test's entities. Get all entities (resources) that are being tested by a specific security test. Use this when you know a test ID and want to see which specific resources (servers, applications, databases, etc.) are being validated for compliance by that test.", + parameters: ListTestEntitiesInput, +}; - return { - content: [ - { type: "text" as const, text: JSON.stringify(await response.json()) }, - ], - }; +// 4. Implementation Functions +export async function tests( + args: z.infer, +): Promise { + return makeConsolidatedRequest("/v1/tests", args, "testId"); } -export async function getTestEntities( - args: z.infer, +export async function listTestEntities( + args: z.infer, ): Promise { - const url = new URL(`/v1/tests/${args.testId}/entities`, baseApiUrl()); - if (args.pageSize !== undefined) { - url.searchParams.append("pageSize", args.pageSize.toString()); - } - if (args.pageCursor !== undefined) { - url.searchParams.append("pageCursor", args.pageCursor); - } - if (args.entityStatus !== undefined) { - url.searchParams.append("entityStatus", args.entityStatus); - } - - 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 { testId, ...params } = args; + const url = buildUrl(`/v1/tests/${String(testId)}/entities`, params); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); } -const TOOL_DESCRIPTION = `Retrieve Vanta's automated security and compliance tests. Vanta runs 1,200+ automated tests continuously to monitor compliance across your infrastructure. Filter by status (OK, NEEDS_ATTENTION, DEACTIVATED), cloud integration (aws, azure, gcp), or compliance framework (soc2, iso27001, hipaa). Returns test results showing which security controls are passing or failing across your infrastructure. Tests that are NOT_APPLICABLE to your resources are included by default - use statusFilter=NEEDS_ATTENTION to retrieve only actionable failing tests.`; - -const TEST_STATUS_FILTER_DESCRIPTION = `Filter tests by their status. -Helpful for retrieving only relevant or actionable results. -Possible values: OK, DEACTIVATED, NEEDS_ATTENTION, IN_PROGRESS, INVALID, NOT_APPLICABLE.`; - -const PAGE_SIZE_DESCRIPTION = `Controls the maximum number of tests returned in a single response. -Allowed values: 1–100. Default is 10.`; - -const INTEGRATION_FILTER_DESCRIPTION = `Filter by integration. Non-exhaustive examples of possible values include aws, azure, gcp, snyk.`; - -const FRAMEWORK_FILTER_DESCRIPTION = `Filter by framework. Non-exhaustive examples: soc2, ccpa, fedramp`; - -const CONTROL_FILTER_DESCRIPTION = `Filter by control. Generally will only be known if pulled from the /v1/controls endpoint.`; - -export const GetTestsInput = z.object({ - pageSize: z.number().describe(PAGE_SIZE_DESCRIPTION).optional(), - statusFilter: z.string().describe(TEST_STATUS_FILTER_DESCRIPTION).optional(), - integrationFilter: z - .string() - .describe(INTEGRATION_FILTER_DESCRIPTION) - .optional(), - frameworkFilter: z.string().describe(FRAMEWORK_FILTER_DESCRIPTION).optional(), - controlFilter: z.string().describe(CONTROL_FILTER_DESCRIPTION).optional(), -}); - -export const GetTestsTool: Tool = { - name: "get_tests", - description: TOOL_DESCRIPTION, - parameters: GetTestsInput, -}; - -const GetTestEntitiesInput = z.object({ - testId: z.string().describe("Lowercase with hyphens"), - pageSize: z - .number() - .describe( - "Controls the maximum number of tests returned in a single response. Allowed values: 1–100. Default is 10.", - ) - .optional(), - pageCursor: z - .string() - .describe("Used for pagination. Leave blank to start from the first page.") - .optional(), - entityStatus: z - .string() - .describe("Filter by entity status. Possible values: FAILING, DEACTIVATED.") - .optional(), -}); - -export const GetTestEntitiesTool: Tool = { - name: "get_test_entities", - description: `Get the specific failing resources (entities) for a known test ID. Use this when you already know the test name/ID and need to see which specific infrastructure resources are failing that test. For example, if you know "aws-security-groups-open-to-world" test is failing, this returns the actual security group IDs that are failing. Requires a specific testId parameter. Do NOT use this for general test discovery - use get_tests for that.`, - parameters: GetTestEntitiesInput, +// Registry export for automated tool registration +export default { + tools: [ + { tool: TestsTool, handler: tests }, + { tool: ListTestEntitiesTool, handler: listTestEntities }, + ], }; diff --git a/src/operations/trust-centers.ts b/src/operations/trust-centers.ts new file mode 100644 index 0000000..ac4d851 --- /dev/null +++ b/src/operations/trust-centers.ts @@ -0,0 +1,523 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createIdSchema, + createIdWithPaginationSchema, + createTrustCenterConsolidatedSchema, + makeGetByIdRequest, + makeTrustCenterConsolidatedRequest, + buildUrl, + makeAuthenticatedRequest, + handleApiResponse, + SLUG_ID_DESCRIPTION, +} from "./common/imports.js"; + +// 2. Input Schemas +const GetTrustCenterInput = createIdSchema({ + paramName: "slugId", + description: SLUG_ID_DESCRIPTION, +}); + +const TrustCenterAccessRequestsInput = createTrustCenterConsolidatedSchema({ + paramName: "accessRequestId", + description: + "Access request ID to retrieve, e.g. 'request-123' or specific access request identifier", + resourceName: "access request", +}); + +const ListTrustCenterViewerActivityEventsInput = createIdWithPaginationSchema({ + paramName: "slugId", + description: SLUG_ID_DESCRIPTION, +}); + +const TrustCenterControlCategoriesInput = createTrustCenterConsolidatedSchema({ + paramName: "controlCategoryId", + description: + "Control category ID to retrieve, e.g. 'category-123' or specific control category identifier", + resourceName: "control category", +}); + +const TrustCenterControlsInput = createTrustCenterConsolidatedSchema({ + paramName: "trustCenterControlId", + description: + "Trust Center control ID to retrieve, e.g. 'tc-control-123' or specific Trust Center control identifier", + resourceName: "control", +}); + +const TrustCenterFaqsInput = createTrustCenterConsolidatedSchema({ + paramName: "faqId", + description: "FAQ ID to retrieve, e.g. 'faq-123' or specific FAQ identifier", + resourceName: "FAQ", +}); + +const ListTrustCenterResourcesInput = createIdWithPaginationSchema({ + paramName: "slugId", + description: SLUG_ID_DESCRIPTION, +}); + +const GetTrustCenterDocumentInput = z.object({ + slugId: z.string().describe(SLUG_ID_DESCRIPTION), + resourceId: z + .string() + .describe( + "Trust Center document ID to retrieve, e.g. 'tc-doc-123' or specific Trust Center document identifier", + ), +}); + +const GetTrustCenterResourceMediaInput = z.object({ + slugId: z.string().describe(SLUG_ID_DESCRIPTION), + resourceId: z + .string() + .describe( + "Trust Center document/resource ID to download media for, e.g. 'tc-doc-123' or specific Trust Center document identifier", + ), +}); + +const TrustCenterSubprocessorsInput = createTrustCenterConsolidatedSchema({ + paramName: "subprocessorId", + description: + "Subprocessor ID to retrieve, e.g. 'subprocessor-123' or specific subprocessor identifier", + resourceName: "subprocessor", +}); + +const TrustCenterUpdatesInput = createTrustCenterConsolidatedSchema({ + paramName: "updateId", + description: + "Update ID to retrieve, e.g. 'update-123' or specific update identifier", + resourceName: "update", +}); + +const TrustCenterViewersInput = createTrustCenterConsolidatedSchema({ + paramName: "viewerId", + description: + "Viewer ID to retrieve, e.g. 'viewer-123' or specific viewer identifier", + resourceName: "viewer", +}); + +const GetTrustCenterSubscriberInput = z.object({ + slugId: z.string().describe(SLUG_ID_DESCRIPTION), + subscriberId: z + .string() + .describe( + "Subscriber ID to retrieve, e.g. 'subscriber-123' or specific subscriber identifier", + ), +}); + +const TrustCenterSubscriberGroupsInput = createTrustCenterConsolidatedSchema({ + paramName: "subscriberGroupId", + description: + "Subscriber group ID to retrieve, e.g. 'group-123' or specific subscriber group identifier", + resourceName: "subscriber group", +}); + +const ListTrustCenterHistoricalAccessRequestsInput = + createIdWithPaginationSchema({ + paramName: "slugId", + description: SLUG_ID_DESCRIPTION, + }); + +const ListTrustCenterSubscribersInput = createIdWithPaginationSchema({ + paramName: "slugId", + description: SLUG_ID_DESCRIPTION, +}); + +// 3. Tool Definitions +export const GetTrustCenterTool: Tool = { + name: "get_trust_center", + description: + "Get Trust Center information. Retrieve detailed information about a specific Trust Center including configuration, branding, and public visibility settings. Use this to access Trust Center details for compliance transparency and customer communication.", + parameters: GetTrustCenterInput, +}; + +export const TrustCenterAccessRequestsTool: Tool< + typeof TrustCenterAccessRequestsInput +> = { + name: "trust_center_access_requests", + description: + "Access Trust Center access requests. Provide accessRequestId to get a specific access request, or omit to list all access requests. Use this to manage and review Trust Center access requests including requester details, status, and approval workflow.", + parameters: TrustCenterAccessRequestsInput, +}; + +export const ListTrustCenterViewerActivityEventsTool: Tool< + typeof ListTrustCenterViewerActivityEventsInput +> = { + name: "list_trust_center_viewer_activity_events", + description: + "List Trust Center viewer activity events. Get all viewing and interaction events for a specific Trust Center to understand usage patterns and engagement. Use this for analytics and compliance tracking.", + parameters: ListTrustCenterViewerActivityEventsInput, +}; + +export const TrustCenterControlCategoriesTool: Tool< + typeof TrustCenterControlCategoriesInput +> = { + name: "trust_center_control_categories", + description: + "Access Trust Center control categories. Provide controlCategoryId to get a specific control category, or omit to list all categories. Use this to understand how compliance controls are organized and categorized for public display.", + parameters: TrustCenterControlCategoriesInput, +}; + +export const TrustCenterControlsTool: Tool = { + name: "trust_center_controls", + description: + "Access Trust Center controls. Provide trustCenterControlId to get a specific control, or omit to list all controls. Use this to see compliance controls displayed publicly to demonstrate your compliance posture.", + parameters: TrustCenterControlsInput, +}; + +export const TrustCenterFaqsTool: Tool = { + name: "trust_center_faqs", + description: + "Access Trust Center FAQs. Provide faqId to get a specific FAQ, or omit to list all FAQs. Use this to see frequently asked questions and answers published for customers regarding compliance and security practices.", + parameters: TrustCenterFaqsInput, +}; + +export const ListTrustCenterResourcesTool: Tool< + typeof ListTrustCenterResourcesInput +> = { + name: "list_trust_center_resources", + description: + "List Trust Center resources. Get all downloadable resources and documents available in a specific Trust Center. Use this to see what compliance materials are provided to customers and prospects.", + parameters: ListTrustCenterResourcesInput, +}; + +export const GetTrustCenterDocumentTool: Tool< + typeof GetTrustCenterDocumentInput +> = { + name: "get_trust_center_document", + description: + "Get Trust Center document by ID. Retrieve detailed information about a specific document available in a Trust Center. Use this to access compliance certifications, policies, and other public-facing documentation.", + parameters: GetTrustCenterDocumentInput, +}; + +export const GetTrustCenterResourceMediaTool: Tool< + typeof GetTrustCenterResourceMediaInput +> = { + name: "get_trust_center_resource_media", + description: + "Download Trust Center document media. Get the actual uploaded document/media file for a Trust Center resource. 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.). Use this to download compliance documents, certifications, and other materials for review or audit purposes.", + parameters: GetTrustCenterResourceMediaInput, +}; + +export const TrustCenterSubprocessorsTool: Tool< + typeof TrustCenterSubprocessorsInput +> = { + name: "trust_center_subprocessors", + description: + "Access Trust Center subprocessors. Provide subprocessorId to get a specific subprocessor, or omit to list all subprocessors. Use this to see third-party service providers and their compliance information for transparency.", + parameters: TrustCenterSubprocessorsInput, +}; + +export const TrustCenterUpdatesTool: Tool = { + name: "trust_center_updates", + description: + "Access Trust Center updates. Provide updateId to get a specific update, or omit to list all updates. Use this to see compliance status changes, security updates, and important notifications published in the Trust Center.", + parameters: TrustCenterUpdatesInput, +}; + +export const TrustCenterViewersTool: Tool = { + name: "trust_center_viewers", + description: + "Access Trust Center viewers. Provide viewerId to get a specific viewer, or omit to list all viewers. Use this for access management and audit purposes to see who can view the Trust Center.", + parameters: TrustCenterViewersInput, +}; + +export const GetTrustCenterSubscriberTool: Tool< + typeof GetTrustCenterSubscriberInput +> = { + name: "get_trust_center_subscriber", + description: + "Get Trust Center subscriber by ID. Retrieve detailed information about a specific subscriber including subscription preferences and notification settings.", + parameters: GetTrustCenterSubscriberInput, +}; + +export const TrustCenterSubscriberGroupsTool: Tool< + typeof TrustCenterSubscriberGroupsInput +> = { + name: "trust_center_subscriber_groups", + description: + "Access Trust Center subscriber groups. Provide subscriberGroupId to get a specific subscriber group, or omit to list all subscriber groups. Use this for managing access permissions and organizing subscribers.", + parameters: TrustCenterSubscriberGroupsInput, +}; + +export const ListTrustCenterHistoricalAccessRequestsTool: Tool< + typeof ListTrustCenterHistoricalAccessRequestsInput +> = { + name: "list_trust_center_historical_access_requests", + description: + "List Trust Center historical access requests. Get all historical access requests for a specific Trust Center for auditing and compliance tracking. Use this to review past access patterns and requests.", + parameters: ListTrustCenterHistoricalAccessRequestsInput, +}; + +export const ListTrustCenterSubscribersTool: Tool< + typeof ListTrustCenterSubscribersInput +> = { + name: "list_trust_center_subscribers", + description: + "List Trust Center subscribers. Get all subscribers for a specific Trust Center. Use this to manage notifications and communication with stakeholders.", + parameters: ListTrustCenterSubscribersInput, +}; + +// 4. Implementation Functions +export async function getTrustCenter( + args: z.infer, +): Promise { + return makeGetByIdRequest("/v1/trust-centers", String(args.slugId)); +} + +export async function trustCenterAccessRequests( + args: z.infer, +): Promise { + return makeTrustCenterConsolidatedRequest( + "/v1/trust-centers", + args, + "accessRequestId", + "access-requests", + ); +} + +export async function listTrustCenterViewerActivityEvents( + args: z.infer, +): Promise { + const { slugId, ...params } = args; + const url = buildUrl(`/v1/trust-centers/${String(slugId)}/activity`, params); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +export async function trustCenterControlCategories( + args: z.infer, +): Promise { + return makeTrustCenterConsolidatedRequest( + "/v1/trust-centers", + args, + "controlCategoryId", + "control-categories", + ); +} + +export async function trustCenterControls( + args: z.infer, +): Promise { + return makeTrustCenterConsolidatedRequest( + "/v1/trust-centers", + args, + "trustCenterControlId", + "controls", + ); +} + +export async function trustCenterFaqs( + args: z.infer, +): Promise { + return makeTrustCenterConsolidatedRequest( + "/v1/trust-centers", + args, + "faqId", + "faqs", + ); +} + +export async function listTrustCenterResources( + args: z.infer, +): Promise { + const { slugId, ...params } = args; + const url = buildUrl(`/v1/trust-centers/${String(slugId)}/resources`, params); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +export async function getTrustCenterDocument( + args: z.infer, +): Promise { + const url = buildUrl( + `/v1/trust-centers/${String(args.slugId)}/resources/${String(args.resourceId)}`, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +export async function getTrustCenterResourceMedia( + args: z.infer, +): Promise { + const url = buildUrl( + `/v1/trust-centers/${String(args.slugId)}/resources/${String(args.resourceId)}/media`, + ); + 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" as const, + text: `Trust Center Resource Media Content (${contentType}):\n\n${textContent}`, + }, + ], + }; + } catch (error) { + return { + content: [ + { + type: "text" as const, + 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" as const, + text: `Trust Center Resource Media 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"} +- Resource ID: ${String(args.resourceId)} +- Trust Center: ${String(args.slugId)} + +Note: This is a binary file. Use appropriate tools to download and process the actual file content.`, + }, + ], + }; +} + +export async function trustCenterSubprocessors( + args: z.infer, +): Promise { + return makeTrustCenterConsolidatedRequest( + "/v1/trust-centers", + args, + "subprocessorId", + "subprocessors", + ); +} + +export async function trustCenterUpdates( + args: z.infer, +): Promise { + return makeTrustCenterConsolidatedRequest( + "/v1/trust-centers", + args, + "updateId", + "updates", + ); +} + +export async function trustCenterViewers( + args: z.infer, +): Promise { + return makeTrustCenterConsolidatedRequest( + "/v1/trust-centers", + args, + "viewerId", + "viewers", + ); +} + +export async function getTrustCenterSubscriber( + args: z.infer, +): Promise { + const url = buildUrl( + `/v1/trust-centers/${String(args.slugId)}/subscribers/${String(args.subscriberId)}`, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +export async function trustCenterSubscriberGroups( + args: z.infer, +): Promise { + return makeTrustCenterConsolidatedRequest( + "/v1/trust-centers", + args, + "subscriberGroupId", + "subscriber-groups", + ); +} + +export async function listTrustCenterHistoricalAccessRequests( + args: z.infer, +): Promise { + const { slugId, ...params } = args; + const url = buildUrl( + `/v1/trust-centers/${String(slugId)}/historical-access-requests`, + params, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +export async function listTrustCenterSubscribers( + args: z.infer, +): Promise { + const { slugId, ...params } = args; + const url = buildUrl( + `/v1/trust-centers/${String(slugId)}/subscribers`, + params, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +// Registry export for automated tool registration +export default { + tools: [ + { tool: GetTrustCenterTool, handler: getTrustCenter }, + { tool: TrustCenterAccessRequestsTool, handler: trustCenterAccessRequests }, + { + tool: ListTrustCenterViewerActivityEventsTool, + handler: listTrustCenterViewerActivityEvents, + }, + { + tool: TrustCenterControlCategoriesTool, + handler: trustCenterControlCategories, + }, + { tool: TrustCenterControlsTool, handler: trustCenterControls }, + { tool: TrustCenterFaqsTool, handler: trustCenterFaqs }, + { tool: ListTrustCenterResourcesTool, handler: listTrustCenterResources }, + { tool: GetTrustCenterDocumentTool, handler: getTrustCenterDocument }, + { + tool: GetTrustCenterResourceMediaTool, + handler: getTrustCenterResourceMedia, + }, + { tool: TrustCenterSubprocessorsTool, handler: trustCenterSubprocessors }, + { tool: TrustCenterUpdatesTool, handler: trustCenterUpdates }, + { tool: TrustCenterViewersTool, handler: trustCenterViewers }, + { tool: GetTrustCenterSubscriberTool, handler: getTrustCenterSubscriber }, + { + tool: TrustCenterSubscriberGroupsTool, + handler: trustCenterSubscriberGroups, + }, + { + tool: ListTrustCenterHistoricalAccessRequestsTool, + handler: listTrustCenterHistoricalAccessRequests, + }, + { + tool: ListTrustCenterSubscribersTool, + handler: listTrustCenterSubscribers, + }, + ], +}; diff --git a/src/operations/utils.ts b/src/operations/utils.ts deleted file mode 100644 index c193214..0000000 --- a/src/operations/utils.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { getValidToken, refreshToken } from "../auth.js"; - -export async function createAuthHeaders(): Promise> { - const token = await getValidToken(); - return { - "Authorization": `Bearer ${token}`, - "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. - * - * @param {string} url - The URL to send the request to. - * @param {RequestInit} [options={}] - Optional fetch options (method, headers, body, etc.). - * @returns {Promise} The fetch Response object. - */ -export async function makeAuthenticatedRequest( - url: string, - options: RequestInit = {}, -): Promise { - let headers = await createAuthHeaders(); - - const response = await fetch(url, { - ...options, - headers: { - ...headers, - ...options.headers, - }, - }); - - // If we get unauthorized, try refreshing the token once - if (response.status === 401) { - const newToken = await refreshToken(); - headers = { - "Authorization": `Bearer ${newToken}`, - "x-vanta-is-mcp": "true", - }; - - return fetch(url, { - ...options, - headers: { - ...headers, - ...options.headers, - }, - }); - } - - return response; -} diff --git a/src/operations/vendor-risk-attributes.ts b/src/operations/vendor-risk-attributes.ts new file mode 100644 index 0000000..36536c8 --- /dev/null +++ b/src/operations/vendor-risk-attributes.ts @@ -0,0 +1,35 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createPaginationSchema, + makePaginatedGetRequest, +} from "./common/imports.js"; + +// 2. Input Schemas +const ListVendorRiskAttributesInput = createPaginationSchema(); + +// 3. Tool Definitions +export const ListVendorRiskAttributesTool: Tool< + typeof ListVendorRiskAttributesInput +> = { + name: "list_vendor_risk_attributes", + description: + "List all vendor risk attributes in your Vanta account. Returns attribute IDs, names, categories, and risk scoring criteria for vendor risk assessment. Use this to see all available risk attributes for evaluating vendor relationships.", + parameters: ListVendorRiskAttributesInput, +}; + +// 4. Implementation Functions +export async function listVendorRiskAttributes( + args: z.infer, +): Promise { + return makePaginatedGetRequest("/v1/vendor-risk-attributes", args); +} + +// Registry export for automated tool registration +export default { + tools: [ + { tool: ListVendorRiskAttributesTool, handler: listVendorRiskAttributes }, + ], +}; diff --git a/src/operations/vendors.ts b/src/operations/vendors.ts new file mode 100644 index 0000000..b91bf2a --- /dev/null +++ b/src/operations/vendors.ts @@ -0,0 +1,170 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + makeConsolidatedRequest, + buildUrl, + makeAuthenticatedRequest, + handleApiResponse, + VENDOR_ID_DESCRIPTION, +} from "./common/imports.js"; + +// 2. Input Schemas +const VendorsInput = createConsolidatedSchema({ + paramName: "vendorId", + description: VENDOR_ID_DESCRIPTION, + resourceName: "vendor", +}); + +const VendorComplianceInput = z.object({ + vendorId: z.string().describe(VENDOR_ID_DESCRIPTION), + complianceType: z + .enum(["documents", "findings", "security_reviews"]) + .describe( + "Type of vendor compliance data: 'documents' for compliance documentation, 'findings' for security findings, 'security_reviews' for security assessments", + ), + pageSize: z + .number() + .min(1) + .max(100) + .describe("Number of items to return per page (1-100)") + .optional(), + pageCursor: z + .string() + .describe("Cursor for pagination to get the next page of results") + .optional(), +}); + +const GetVendorSecurityReviewInput = z.object({ + vendorId: z.string().describe(VENDOR_ID_DESCRIPTION), + securityReviewId: z + .string() + .describe( + "Security review ID to retrieve, e.g. 'review-123' or specific security review identifier", + ), +}); + +const ListVendorSecurityReviewDocumentsInput = z.object({ + vendorId: z.string().describe(VENDOR_ID_DESCRIPTION), + securityReviewId: z + .string() + .describe( + "Security review ID to get documents for, e.g. 'review-123' or specific security review identifier", + ), + pageSize: z + .number() + .min(1) + .max(100) + .describe("Number of items to return per page (1-100)") + .optional(), + pageCursor: z + .string() + .describe("Cursor for pagination to get the next page of results") + .optional(), +}); + +// 3. Tool Definitions +export const VendorsTool: Tool = { + name: "vendors", + description: + "Access vendors in your Vanta account. Provide vendorId to get a specific vendor, or omit to list all vendors. Returns vendor details, risk levels, and management status for third-party risk assessment.", + parameters: VendorsInput, +}; + +export const VendorComplianceTool: Tool = { + name: "vendor_compliance", + description: + "Access vendor compliance data including documents, findings, and security reviews. Specify complianceType to get the specific type of compliance information for a vendor. Use this to explore vendor compliance documentation, security findings, and assessment history.", + parameters: VendorComplianceInput, +}; + +export const GetVendorSecurityReviewTool: Tool< + typeof GetVendorSecurityReviewInput +> = { + name: "get_vendor_security_review", + description: + "Get vendor security review by ID. Retrieve detailed information about a specific security review for a vendor.", + parameters: GetVendorSecurityReviewInput, +}; + +export const ListVendorSecurityReviewDocumentsTool: Tool< + typeof ListVendorSecurityReviewDocumentsInput +> = { + name: "list_vendor_security_review_documents", + description: + "List vendor security review's documents. Get all documents associated with a specific vendor security review.", + parameters: ListVendorSecurityReviewDocumentsInput, +}; + +// 4. Implementation Functions +export async function vendors( + args: z.infer, +): Promise { + return makeConsolidatedRequest("/v1/vendors", args, "vendorId"); +} + +export async function vendorCompliance( + args: z.infer, +): Promise { + const { vendorId, complianceType, ...params } = args; + + const endpoints = { + documents: `/v1/vendors/${String(vendorId)}/documents`, + findings: `/v1/vendors/${String(vendorId)}/findings`, + security_reviews: `/v1/vendors/${String(vendorId)}/security-reviews`, + }; + + const endpoint = endpoints[complianceType]; + if (!endpoint) { + return { + content: [ + { + type: "text", + text: `Error: Invalid complianceType '${complianceType}'. Must be one of: documents, findings, security_reviews`, + }, + ], + isError: true, + }; + } + + const url = buildUrl(endpoint, params); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +export async function getVendorSecurityReview( + args: z.infer, +): Promise { + const url = buildUrl( + `/v1/vendors/${String(args.vendorId)}/security-reviews/${String(args.securityReviewId)}`, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +export async function listVendorSecurityReviewDocuments( + args: z.infer, +): Promise { + const { vendorId, securityReviewId, ...params } = args; + const url = buildUrl( + `/v1/vendors/${String(vendorId)}/security-reviews/${String(securityReviewId)}/documents`, + params, + ); + const response = await makeAuthenticatedRequest(url); + return handleApiResponse(response); +} + +// Registry export for automated tool registration +export default { + tools: [ + { tool: VendorsTool, handler: vendors }, + { tool: VendorComplianceTool, handler: vendorCompliance }, + { tool: GetVendorSecurityReviewTool, handler: getVendorSecurityReview }, + { + tool: ListVendorSecurityReviewDocumentsTool, + handler: listVendorSecurityReviewDocuments, + }, + ], +}; diff --git a/src/operations/vulnerabilities.ts b/src/operations/vulnerabilities.ts new file mode 100644 index 0000000..affb7d7 --- /dev/null +++ b/src/operations/vulnerabilities.ts @@ -0,0 +1,74 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + makeConsolidatedRequest, +} from "./common/imports.js"; + +// 2. Input Schemas +const VulnerabilitiesInput = createConsolidatedSchema( + { + paramName: "vulnerabilityId", + description: + "Vulnerability ID to retrieve, e.g. 'vulnerability-123' or specific vulnerability identifier. If provided, returns the specific vulnerability, and no other parameters may be provided. If omitted, lists all vulnerabilities with optional filtering and pagination.", + resourceName: "vulnerability", + }, + { + externalVulnerabilityId: z + .string() + .describe( + "Filter vulnerabilities by external vulnerability ID (e.g. CVE-2024-1234). Returns vulnerabilities that match the provided external vulnerability ID.", + ) + .optional(), + severity: z + .string() + .describe( + "Filter vulnerabilities by severity. Possible values: LOW (Low severity), MEDIUM (Medium severity), HIGH (High severity), CRITICAL (Critical severity)", + ) + .optional(), + integrationId: z + .string() + .describe( + "Filter vulnerabilities by integration ID. Returns vulnerabilities that are associated with the specified integration.", + ) + .optional(), + slaDeadlineAfter: z + .string() + .describe( + "Filter vulnerabilities by SLA deadline after the specified date. Returns vulnerabilities that have an SLA deadline after the specified date. Date should be formatted as YYYY-MM-DD.", + ) + .optional(), + slaDeadlineBefore: z + .string() + .describe( + "Filter vulnerabilities by SLA deadline before the specified date. Returns vulnerabilities that have an SLA deadline before the specified date. Date should be formatted as YYYY-MM-DD.", + ) + .optional(), + }, +); + +// 3. Tool Definitions +export const VulnerabilitiesTool: Tool = { + name: "vulnerabilities", + description: + "Access vulnerabilities in your Vanta account. Provide vulnerabilityId to get a specific vulnerability, or omit to list all vulnerabilities. Returns vulnerability details, severity levels, and status for security monitoring.", + parameters: VulnerabilitiesInput, +}; + +// 4. Implementation Functions +export async function vulnerabilities( + args: z.infer, +): Promise { + return makeConsolidatedRequest( + "/v1/vulnerabilities", + args, + "vulnerabilityId", + ); +} + +// Registry export for automated tool registration +export default { + tools: [{ tool: VulnerabilitiesTool, handler: vulnerabilities }], +}; diff --git a/src/operations/vulnerability-remediations.ts b/src/operations/vulnerability-remediations.ts new file mode 100644 index 0000000..5e1bf92 --- /dev/null +++ b/src/operations/vulnerability-remediations.ts @@ -0,0 +1,38 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createPaginationSchema, + makePaginatedGetRequest, +} from "./common/imports.js"; + +// 2. Input Schemas +const ListVulnerabilityRemediationsInput = createPaginationSchema(); + +// 3. Tool Definitions +export const ListVulnerabilityRemediationsTool: Tool< + typeof ListVulnerabilityRemediationsInput +> = { + name: "list_vulnerability_remediations", + description: + "List all vulnerability remediations in your Vanta account. Returns remediation IDs, vulnerability references, status, and progress for tracking security improvements. Use this to see all vulnerability remediation efforts and their current status.", + parameters: ListVulnerabilityRemediationsInput, +}; + +// 4. Implementation Functions +export async function listVulnerabilityRemediations( + args: z.infer, +): Promise { + return makePaginatedGetRequest("/v1/vulnerability-remediations", args); +} + +// Registry export for automated tool registration +export default { + tools: [ + { + tool: ListVulnerabilityRemediationsTool, + handler: listVulnerabilityRemediations, + }, + ], +}; diff --git a/src/operations/vulnerable-assets.ts b/src/operations/vulnerable-assets.ts new file mode 100644 index 0000000..84aea87 --- /dev/null +++ b/src/operations/vulnerable-assets.ts @@ -0,0 +1,40 @@ +// 1. Imports +import { + CallToolResult, + Tool, + z, + createConsolidatedSchema, + makeConsolidatedRequest, +} from "./common/imports.js"; + +// 2. Input Schemas +const VulnerableAssetsInput = createConsolidatedSchema({ + paramName: "vulnerableAssetId", + description: + "Vulnerable asset ID to retrieve, e.g. 'vulnerable-asset-123' or specific asset identifier", + resourceName: "vulnerable asset", +}); + +// 3. Tool Definitions +export const VulnerableAssetsTool: Tool = { + name: "vulnerable_assets", + description: + "Access vulnerable assets in your Vanta account. Provide vulnerableAssetId to get a specific vulnerable asset, or omit to list all vulnerable assets. Returns asset details, vulnerability counts, and security status.", + parameters: VulnerableAssetsInput, +}; + +// 4. Implementation Functions +export async function vulnerableAssets( + args: z.infer, +): Promise { + return makeConsolidatedRequest( + "/v1/vulnerable-assets", + args, + "vulnerableAssetId", + ); +} + +// Registry export for automated tool registration +export default { + tools: [{ tool: VulnerableAssetsTool, handler: vulnerableAssets }], +}; diff --git a/src/registry.ts b/src/registry.ts new file mode 100644 index 0000000..a96e4bd --- /dev/null +++ b/src/registry.ts @@ -0,0 +1,111 @@ +import { CallToolResult } from "@modelcontextprotocol/sdk/types.js"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { z } from "zod"; +import { + getEnabledToolNames, + hasEnabledToolFilter, + isToolEnabled, +} from "./config.js"; + +// Tool definition interface (matches our Tool pattern) +export interface ToolDefinition { + name: string; + description: string; + parameters: z.ZodTypeAny; +} + +// Tool registry interface for operations modules +export interface ToolEntry { + tool: ToolDefinition; + handler: (args: z.infer) => Promise; +} + +export interface OperationModule { + tools: ToolEntry[]; +} + +// Helper function to register a single tool +export function registerTool( + server: McpServer, + tool: ToolDefinition, + handler: (args: z.infer) => Promise, +): boolean { + if (!isToolEnabled(tool.name)) { + console.error(`⚪️ Skipping tool not in enabled list: ${tool.name}`); + return false; + } + + const parameters = tool.parameters as z.ZodObject; + server.tool(tool.name, tool.description, parameters.shape, handler); + return true; +} + +// Helper function to register all tools from a module +export function registerOperationModule( + server: McpServer, + operationModule: OperationModule, +): { registered: number; skipped: number } { + let registered = 0; + let skipped = 0; + + operationModule.tools.forEach(({ tool, handler }) => { + const wasRegistered = registerTool(server, tool, handler); + if (wasRegistered) { + registered += 1; + } else { + skipped += 1; + } + }); + + return { registered, skipped }; +} + +// Auto-discovery and registration of all operations +export async function registerAllOperations(server: McpServer): Promise { + // Import all operation modules + const operations = [ + import("./operations/tests.js"), + import("./operations/frameworks.js"), + import("./operations/controls.js"), + import("./operations/risks.js"), + import("./operations/integrations.js"), + import("./operations/vendors.js"), + import("./operations/documents.js"), + import("./operations/policies.js"), + import("./operations/discovered-vendors.js"), + import("./operations/groups.js"), + import("./operations/people.js"), + import("./operations/vulnerabilities.js"), + import("./operations/vulnerability-remediations.js"), + import("./operations/vulnerable-assets.js"), + import("./operations/monitored-computers.js"), + import("./operations/vendor-risk-attributes.js"), + import("./operations/trust-centers.js"), + ]; + + // Load all modules and register their tools + const modules = await Promise.all(operations); + + let totalTools = 0; + let skippedTools = 0; + modules.forEach(module => { + const operationModule = module.default; + const { registered, skipped } = registerOperationModule( + server, + operationModule, + ); + totalTools += registered; + skippedTools += skipped; + }); + + console.error( + `✅ 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})`, + ); + } +}