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.
See also: Tools & Toolkits Guide for the complete guide on defining, organizing, and exposing tools — including
type: apibundles,type: workflowtools, shared tools viaextends, 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
The MCP provider supports four transport mechanisms:
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 executecommand_args(optional): Array of command argumentsenv(optional): Environment variablesworkingDirectory(optional): Working directory for the command
Security: Commands are validated to prevent shell injection. Metacharacters like ;, |, &, `, $, (), {}, [] are rejected.
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 URLheaders(optional): HTTP headers for authentication
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 URLsessionId(optional): Session ID for stateful interactionsheaders(optional): HTTP headers
Note: HTTP transport supports stateful sessions. The server may generate a session ID if not provided.
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: custommethod: Name of the tool defined intools:sectionmethodArgs: Arguments to pass to the tool
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: 10Use 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 filesfileCount- Number of changed filesoutputs- Results from dependent checks (see Dependencies)env- Safe environment variables (CI_, GITHUB_, etc.)
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)
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.
The MCP provider automatically extracts issues from output in several formats:
[
{
"file": "src/index.ts",
"line": 42,
"message": "Security vulnerability detected",
"severity": "error",
"category": "security"
}
]{
"issues": [...],
"metadata": { "scanned": 15 }
}The issues array is extracted and remaining properties are preserved in output.
{
"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 fixreplacement: Replacement code
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 oroutputproperty if present- Safe to use in both
argsTransformandtransform
type: mcp- Provider typemethod- MCP tool/method name to call
stdio transport:
transport: stdio(optional, default)command- Command to executecommand_args- Command arguments (optional)env- Environment variables (optional)workingDirectory- Working directory (optional)
sse transport:
transport: sseurl- SSE endpoint URLheaders- HTTP headers (optional)
http transport:
transport: httpurl- HTTP endpoint URLsessionId- Session ID (optional)headers- HTTP headers (optional)
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)
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 onif- Conditional execution (JavaScript expression)on- Event filter (pr_opened, pr_updated, etc.)tags- Array of tags for filteringgroup- Comment group nameforEach- Run check for each item in a collection (see examples)
See examples/mcp-provider-example.yaml for comprehensive production-ready workflows.
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"]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'
}))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'
}));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 [];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"
}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 [];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 [];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 [];- Command Validation: stdio commands are validated to prevent injection attacks
- Sandboxed JavaScript:
transform_jsruns in a secure sandbox without access to system resources - Safe Environment: Only whitelisted environment variables are exposed (CI_, GITHUB_, etc.)
- URL Validation: Only http: and https: protocols are allowed for SSE/HTTP transports
- Timeout Protection: All MCP calls have configurable timeouts (default 60s)
Enable debug mode to see MCP interactions:
visor --check my-mcp-check --debugDebug 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
- MCP Tools for AI Providers - Using MCP to enhance AI analysis
- Command Provider - Execute shell commands
- HTTP Integration - HTTP client and webhook providers
- Dependencies - Check dependency management
- Liquid Templates - Template syntax reference