Skip to content

Latest commit

 

History

History
311 lines (248 loc) · 11 KB

File metadata and controls

311 lines (248 loc) · 11 KB

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):

const FrameworksInput = createConsolidatedSchema({
  paramName: "frameworkId",
  description: FRAMEWORK_ID_DESCRIPTION,
  resourceName: "framework",
});

export async function frameworks(
  args: z.infer<typeof FrameworksInput>,
): Promise<CallToolResult> {
  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):

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<typeof DocumentResourcesInput>,
): Promise<CallToolResult> {
  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):

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<typeof DownloadDocumentFileInput>,
): Promise<CallToolResult> {
  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:
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:

// 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<typeof ResourceInput> = {
  name: "resources",
  description: "Access resources...",
  parameters: ResourceInput,
};

export const ResourceDetailsTool: Tool<typeof ResourceDetailsInput> = {
  name: "resource_details",
  description: "Access resource details...",
  parameters: ResourceDetailsInput,
};

// 4. Implementation Functions
export async function resources(
  args: z.infer<typeof ResourceInput>,
): Promise<CallToolResult> {
  return makeConsolidatedRequest("/v1/resources", args, "resourceId");
}

export async function resourceDetails(
  args: z.infer<typeof ResourceDetailsInput>,
): Promise<CallToolResult> {
  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.