Skip to content

Latest commit

 

History

History
593 lines (484 loc) · 15.4 KB

File metadata and controls

593 lines (484 loc) · 15.4 KB

MCP Provider

The MCP (Model Context Protocol) provider allows you to call MCP tools directly from Visor checks without requiring an AI provider. This is useful for integrating external tools, services, or custom logic into your review workflows.

Overview

See also: Tools & Toolkits Guide for the complete guide on defining, organizing, and exposing tools — including type: api bundles, type: workflow tools, shared tools via extends, and toolkit references.

Unlike the AI provider's MCP support (which enhances AI models with additional tools), the standalone MCP provider directly invokes MCP tools and returns their results. This enables you to:

  • Call external APIs and services via MCP tools
  • Execute custom analysis tools
  • Integrate third-party MCP servers
  • Chain MCP tool outputs with other checks using dependencies

Transport Types

The MCP provider supports four transport mechanisms:

1. stdio (default)

Execute a local command that implements the MCP protocol over standard input/output.

steps:
  probe-analysis:
    type: mcp
    transport: stdio
    command: npx
    command_args: ["-y", "@probelabs/probe@latest", "mcp"]
    method: search_code
    methodArgs:
      query: "TODO"

Configuration:

  • command (required): Command to execute
  • command_args (optional): Array of command arguments
  • env (optional): Environment variables
  • workingDirectory (optional): Working directory for the command

Security: Commands are validated to prevent shell injection. Metacharacters like ;, |, &, `, $, (), {}, [] are rejected.

2. SSE (Server-Sent Events)

Connect to an MCP server via SSE (legacy transport).

steps:
  remote-analysis:
    type: mcp
    transport: sse
    url: https://mcp-server.example.com/sse
    headers:
      Authorization: "Bearer ${MCP_TOKEN}"
    method: analyze
    methodArgs:
      file: "{{ pr.files[0].filename }}"

Configuration:

  • url (required): SSE endpoint URL
  • headers (optional): HTTP headers for authentication

3. HTTP (Streamable HTTP)

Connect to an MCP server via modern Streamable HTTP transport.

steps:
  http-tool:
    type: mcp
    transport: http
    url: https://mcp-server.example.com/mcp
    sessionId: "my-session-123"  # Optional, server may generate one
    headers:
      Authorization: "Bearer ${MCP_TOKEN}"
    method: process
    methodArgs:
      data: "{{ pr.title }}"

Configuration:

  • url (required): HTTP endpoint URL
  • sessionId (optional): Session ID for stateful interactions
  • headers (optional): HTTP headers

Note: HTTP transport supports stateful sessions. The server may generate a session ID if not provided.

4. Custom (YAML-defined tools)

Use custom tools defined in your YAML configuration. See Custom Tools for full documentation.

tools:
  my-grep-tool:
    name: my-grep-tool
    description: Search for patterns in code
    inputSchema:
      type: object
      properties:
        pattern:
          type: string
      required: [pattern]
    exec: 'grep -rn "{{ args.pattern }}" src/'

steps:
  search-patterns:
    type: mcp
    transport: custom
    method: my-grep-tool
    methodArgs:
      pattern: "TODO"

Configuration:

  • transport: custom
  • method: Name of the tool defined in tools: section
  • methodArgs: Arguments to pass to the tool

Method Arguments

Static Arguments

Provide method arguments directly:

steps:
  search-todos:
    type: mcp
    command: npx
    command_args: ["-y", "@probelabs/probe@latest", "mcp"]
    method: search_code
    methodArgs:
      query: "TODO"
      limit: 10

Templated Arguments with Liquid

Use Liquid templates to build arguments from PR context:

steps:
  dynamic-search:
    type: mcp
    command: npx
    command_args: ["-y", "@probelabs/probe@latest", "mcp"]
    method: search_code
    argsTransform: |
      {
        "query": "{{ pr.title | split: ' ' | first }}",
        "files": [{% for file in pr.files %}"{{ file.filename }}"{% unless forloop.last %},{% endunless %}{% endfor %}]
      }

Template Context:

  • pr - PR metadata (number, title, author, branch, base)
  • files - Array of changed files
  • fileCount - Number of changed files
  • outputs - Results from dependent checks (see Dependencies)
  • env - Safe environment variables (CI_, GITHUB_, etc.)

Output Transformation

Liquid Transform

Transform MCP output using Liquid templates:

steps:
  format-results:
    type: mcp
    command: npx
    command_args: ["-y", "@probelabs/probe@latest", "mcp"]
    method: search_code
    methodArgs:
      query: "FIXME"
    transform: |
      {
        "count": {{ output.results | size }},
        "files": [{% for result in output.results %}"{{ result.file }}"{% unless forloop.last %},{% endunless %}{% endfor %}]
      }

Transform Context:

  • output - MCP method result
  • All context from argsTransform (pr, files, outputs, env)

JavaScript Transform

Apply JavaScript transformations in a secure sandbox:

steps:
  js-transform:
    type: mcp
    command: npx
    command_args: ["-y", "@probelabs/probe@latest", "mcp"]
    method: search_code
    methodArgs:
      query: "TODO"
    transform_js: |
      // Filter and map results
      output.results
        .filter(r => r.severity === 'high')
        .map(r => ({
          file: r.file,
          message: `TODO found: ${r.text}`
        }))

Available in sandbox:

  • Standard JavaScript: Array, String, Object, Math, JSON
  • Context variables: output, pr, files, outputs, env
  • Safe methods only (no eval, Function, require, etc.)

Note: Both transforms can be used together. Liquid runs first, then JavaScript.

Issue Extraction

The MCP provider automatically extracts issues from output in several formats:

Array of Issues

[
  {
    "file": "src/index.ts",
    "line": 42,
    "message": "Security vulnerability detected",
    "severity": "error",
    "category": "security"
  }
]

Object with Issues Property

{
  "issues": [...],
  "metadata": { "scanned": 15 }
}

The issues array is extracted and remaining properties are preserved in output.

Single Issue Object

{
  "file": "src/app.ts",
  "line": 10,
  "message": "Performance issue",
  "severity": "warning"
}

Supported Issue Fields:

  • message (required): Issue description (aliases: text, description, summary)
  • file: File path (aliases: path, filename, defaults to "system")
  • line: Line number (aliases: startLine, lineNumber, defaults to 0)
  • endLine: End line number (aliases: end_line, stopLine)
  • severity: info/warning/error/critical (aliases: level, priority, defaults to "warning")
  • category: security/performance/style/logic/documentation (aliases: type, group, defaults to "logic")
  • ruleId: Rule identifier (aliases: rule, id, check, defaults to "mcp")
  • suggestion: Suggested fix
  • replacement: Replacement code

Dependencies

Use outputs from other checks in MCP arguments:

steps:
  fetch-data:
    type: http_client
    url: https://api.example.com/issues

  analyze-issues:
    type: mcp
    depends_on: [fetch-data]
    command: npx
    command_args: ["-y", "@probelabs/probe@latest", "mcp"]
    method: analyze
    argsTransform: |
      {
        "issues": {{ outputs["fetch-data"] | json }}
      }

Outputs are available as:

  • outputs["check-name"] - Full result object or output property if present
  • Safe to use in both argsTransform and transform

Configuration Reference

Required Fields

  • type: mcp - Provider type
  • method - MCP tool/method name to call

Transport Configuration

stdio transport:

  • transport: stdio (optional, default)
  • command - Command to execute
  • command_args - Command arguments (optional)
  • env - Environment variables (optional)
  • workingDirectory - Working directory (optional)

sse transport:

  • transport: sse
  • url - SSE endpoint URL
  • headers - HTTP headers (optional)

http transport:

  • transport: http
  • url - HTTP endpoint URL
  • sessionId - Session ID (optional)
  • headers - HTTP headers (optional)

Method Configuration

  • methodArgs - Static method arguments (optional)
  • argsTransform - Liquid template for dynamic arguments (optional)
  • transform - Liquid template for output transformation (optional)
  • transform_js - JavaScript expression for output transformation (optional)

General (Check-Level Options)

These options are available to all check types, not just MCP:

  • timeout - Timeout in seconds (default: 60)
  • depends_on - Array of check names this depends on
  • if - Conditional execution (JavaScript expression)
  • on - Event filter (pr_opened, pr_updated, etc.)
  • tags - Array of tags for filtering
  • group - Comment group name
  • forEach - Run check for each item in a collection (see examples)

Real-World Examples

See examples/mcp-provider-example.yaml for comprehensive production-ready workflows.

Security Scanning with Semgrep

Detect vulnerabilities in changed code:

steps:
  semgrep-scan:
    type: mcp
    command: npx
    command_args: ["-y", "@semgrep/mcp"]
    method: scan
    methodArgs:
      paths: "{{ files | map: 'filename' | json }}"
      rules: ["security", "owasp-top-10"]

GitHub Issue Detection

Find related or duplicate issues:

steps:
  check-duplicates:
    type: mcp
    command: npx
    command_args: ["-y", "@modelcontextprotocol/server-github"]
    method: search_issues
    methodArgs:
      query: "{{ pr.title }}"
      state: "open"
    transform_js: |
      output.items
        .filter(issue => issue.number !== pr.number)
        .map(issue => ({
          file: 'github',
          line: 0,
          message: `Related: #${issue.number} - ${issue.title}`,
          severity: 'info',
          category: 'documentation'
        }))

Database Schema Validation

Verify migrations don't break schema:

steps:
  validate-schema:
    type: mcp
    command: npx
    command_args: ["-y", "@modelcontextprotocol/server-postgres"]
    if: "files.some(f => f.filename.includes('migrations/'))"
    method: query
    methodArgs:
      query: "SELECT * FROM information_schema.tables WHERE table_schema = 'public'"
    transform_js: |
      const criticalTables = ['users', 'sessions', 'payments'];
      const existing = output.rows.map(r => r.table_name);
      const missing = criticalTables.filter(t => !existing.includes(t));

      return missing.map(table => ({
        file: 'database',
        line: 0,
        message: `Critical table '${table}' missing after migration`,
        severity: 'error',
        category: 'logic'
      }));

Jira Ticket Validation

Ensure PR links to valid Jira ticket:

steps:
  jira-check:
    type: mcp
    command: npx
    command_args: ["-y", "@atlassian/mcp-server-jira"]
    method: get_issue
    argsTransform: |
      {
        "issueKey": "{{ pr.title | split: ' ' | first | upcase }}"
      }
    transform_js: |
      if (output.error || !output.fields) {
        return [{
          file: 'jira',
          line: 0,
          message: 'PR must reference valid Jira ticket (e.g., PROJ-123)',
          severity: 'error',
          category: 'documentation'
        }];
      }
      return [];

Slack Notifications

Alert team when critical issues found:

steps:
  notify-security:
    type: mcp
    depends_on: [semgrep-scan]
    command: npx
    command_args: ["-y", "@modelcontextprotocol/server-slack"]
    if: "outputs['semgrep-scan']?.issues?.filter(i => i.severity === 'error').length > 0"
    method: post_message
    argsTransform: |
      {
        "channel": "#security-alerts",
        "text": "Security Alert: PR #{{ pr.number }} has critical security issues"
      }

License Header Validation

Check all source files have license headers:

steps:
  check-licenses:
    type: mcp
    command: npx
    command_args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
    method: read_file
    forEach:
      items: "{{ files | map: 'filename' | json }}"
      itemVar: filepath
    methodArgs:
      path: "{{ filepath }}"
    transform_js: |
      const content = output.content || '';
      const hasLicense = content.includes('Copyright') || content.includes('SPDX-License');

      if (!hasLicense && filepath.match(/\.(ts|js|py|go)$/)) {
        return [{
          file: filepath,
          line: 1,
          message: 'Missing license header',
          severity: 'warning',
          category: 'documentation',
          suggestion: 'Add SPDX-License-Identifier comment'
        }];
      }
      return [];

Web Scraping with Puppeteer

Validate external documentation links:

steps:
  validate-links:
    type: mcp
    command: npx
    command_args: ["-y", "@modelcontextprotocol/server-puppeteer"]
    if: "files.some(f => f.filename.endsWith('.md'))"
    method: navigate
    methodArgs:
      url: "https://docs.example.com"
      waitUntil: "networkidle2"
    transform_js: |
      if (output.statusCode >= 400) {
        return [{
          file: 'documentation',
          line: 0,
          message: `Broken link: ${output.url} (${output.statusCode})`,
          severity: 'warning',
          category: 'documentation'
        }];
      }
      return [];

CVE Checking with Brave Search

Search for known vulnerabilities:

steps:
  check-cves:
    type: mcp
    command: npx
    command_args: ["-y", "@modelcontextprotocol/server-brave-search"]
    if: "files.some(f => f.filename.match(/package\\.json|requirements\\.txt/))"
    method: search
    argsTransform: |
      {
        "query": "CVE {{ pr.title }} vulnerability"
      }
    transform_js: |
      const cvePattern = /CVE-\d{4}-\d{4,7}/g;
      const results = output.web?.results || [];
      const cves = new Set();

      results.forEach(result => {
        const matches = result.description?.match(cvePattern) || [];
        matches.forEach(cve => cves.add(cve));
      });

      if (cves.size > 0) {
        return [{
          file: 'dependencies',
          line: 0,
          message: `Potential CVEs: ${Array.from(cves).join(', ')}`,
          severity: 'warning',
          category: 'security'
        }];
      }
      return [];

Security Considerations

  1. Command Validation: stdio commands are validated to prevent injection attacks
  2. Sandboxed JavaScript: transform_js runs in a secure sandbox without access to system resources
  3. Safe Environment: Only whitelisted environment variables are exposed (CI_, GITHUB_, etc.)
  4. URL Validation: Only http: and https: protocols are allowed for SSE/HTTP transports
  5. Timeout Protection: All MCP calls have configurable timeouts (default 60s)

Debugging

Enable debug mode to see MCP interactions:

visor --check my-mcp-check --debug

Debug output includes:

  • MCP server connection details
  • Available tools from the server
  • Method call arguments and results
  • Session IDs for HTTP transport
  • Transform errors and outputs

Related Documentation