You can expose custom shell-based tools to AI using one of two methods:
Method 1: Using ai_custom_tools (Recommended)
tools:
grep-pattern:
name: grep-pattern
exec: 'grep -rn "{{ args.pattern }}" *.ts'
inputSchema:
type: object
properties:
pattern: {type: string}
required: [pattern]
steps:
security-scan:
type: ai
prompt: Use grep-pattern to find security issues.
ai_custom_tools: [grep-pattern] # Simple and explicitMethod 2: Using tools: within ai_mcp_servers
tools:
grep-pattern:
name: grep-pattern
exec: 'grep -rn "{{ args.pattern }}" *.ts'
inputSchema:
type: object
properties:
pattern: {type: string}
required: [pattern]
steps:
security-scan:
type: ai
prompt: Use grep-pattern to find security issues.
ai_mcp_servers:
custom-tools:
tools: [grep-pattern] # Creates ephemeral SSE MCP serverWhen you use either ai_custom_tools or tools: [...] within an MCP server config:
- Automatic Detection: Visor detects you're referencing custom tools defined in the global
tools:section - Server Startup: Creates an ephemeral SSE MCP server on an available port
- Tool Exposure: Your custom tools become available to the AI via MCP protocol
- Auto Cleanup: Server stops automatically when the AI check completes
tools:
users-api:
type: api
name: users-api
spec: ./openapi/users.yaml
headers:
Authorization: "Bearer ${USERS_API_BEARER_TOKEN}"
X-Tenant-Id: "${USERS_API_TENANT_ID}"
overlays:
- ./openapi/users-overlay.yaml
- actions:
- target: "$.paths['/users/{id}'].get.operationId"
update: getUserFromInlineOverlay
whitelist: [get*]
targetUrl: https://api.example.com
steps:
api-assistant:
type: ai
prompt: Use users API tools to answer requests.
ai_custom_tools: [users-api]users-api is a reusable tool bundle; each OpenAPI operation becomes an MCP tool exposed to AI.
spec and overlays support both inline objects and file/URL references.
headers also supports environment-variable interpolation (for example Authorization: "Bearer ${USERS_API_BEARER_TOKEN}").
Repository examples:
examples/api-tools-library.yamlexamples/api-tools-ai-example.yaml(embedded tests)examples/api-tools-mcp-example.yaml(embedded tests)examples/api-tools-inline-overlay-example.yaml(embedded tests)
tools:
check-secrets:
name: check-secrets
description: Scan for hardcoded secrets
exec: 'grep -rn -E "(api_key|secret|password)" .'
inputSchema:
type: object
properties: {}
steps:
security-review:
type: ai
prompt: Use check-secrets to find hardcoded credentials.
ai_custom_tools: [check-secrets]tools:
grep-pattern:
name: grep-pattern
exec: 'grep -rn "{{ args.pattern }}" *.ts'
inputSchema:
type: object
properties:
pattern: {type: string}
required: [pattern]
count-todos:
name: count-todos
exec: 'grep -r "TODO" src/ | wc -l'
inputSchema:
type: object
properties: {}
steps:
code-quality:
type: ai
prompt: |
Use grep-pattern to find console.log statements.
Use count-todos to count pending work items.
ai_custom_tools: [grep-pattern, count-todos]steps:
full-review:
type: ai
prompt: |
You have both custom tools and external MCP servers.
Use them for a comprehensive review.
# Custom tools via ai_custom_tools
ai_custom_tools: [grep-pattern, check-secrets]
# External MCP servers
ai_mcp_servers:
filesystem:
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "."]steps:
my-check:
type: ai
ai_custom_tools: [tool-name-1, tool-name-2, ...]Key points:
- Simple and explicit configuration
- Tools must be defined in global
tools:section - Automatically creates SSE server on ephemeral port
- Can be combined with
ai_mcp_serversfor external servers
ai_mcp_servers:
<server-name>:
tools: [tool-name-1, tool-name-2, ...]Key points:
- Server name can be anything (e.g.,
custom-tools,my-tools) - Tools must be defined in global
tools:section - Automatically creates SSE server on ephemeral port
- No command/URL needed - handled automatically
ai_mcp_servers:
<server-name>:
command: <command>
args: [<arg1>, <arg2>, ...]Key points:
- Requires command to run external MCP server
- Uses stdio transport by default
- Can also use SSE/HTTP with
url:andtransport:
Both configuration methods are fully supported:
steps:
# Method 1: ai_custom_tools (recommended for simplicity)
check-with-custom-tools:
type: ai
ai_custom_tools: [tool1, tool2]
# Method 2: tools: in ai_mcp_servers (useful for naming)
check-with-mcp-tools:
type: ai
ai_mcp_servers:
my-security-tools:
tools: [tool1, tool2]Choose ai_custom_tools when:
- You want simple, explicit configuration
- You're combining custom tools with external MCP servers
Choose tools: in ai_mcp_servers when:
- You want to name your tool server
- You prefer all MCP configuration in one place
version: "1.0"
# Define custom tools (global section)
tools:
grep-security:
name: grep-security
description: Search for security-related patterns
inputSchema:
type: object
properties:
pattern: {type: string}
required: [pattern]
exec: 'grep -rn "{{ args.pattern }}" src/'
parseJson: false
scan-dependencies:
name: scan-dependencies
description: Check for outdated dependencies
inputSchema:
type: object
properties: {}
exec: 'npm outdated --json 2>/dev/null || echo "{}"'
parseJson: true
count-lines:
name: count-lines
description: Count lines of code
inputSchema:
type: object
properties:
extension: {type: string}
required: [extension]
exec: 'find . -name "*.{{ args.extension }}" -exec wc -l {} + | tail -1'
parseJson: false
# Use custom tools in AI checks
steps:
security-audit:
type: ai
prompt: |
Perform a security audit:
1. Use grep-security to find eval(), exec(), or dangerous patterns
2. Use scan-dependencies to check for outdated packages
3. Report findings with severity levels
ai_custom_tools: [grep-security, scan-dependencies]
ai:
provider: anthropic
model: claude-3-5-sonnet-20241022
code-metrics:
type: ai
prompt: |
Analyze code metrics:
- Use count-lines to measure TypeScript code
- Provide insights on code size and complexity
ai_custom_tools: [count-lines]
ai:
provider: anthropic
model: claude-3-5-sonnet-20241022
output:
pr_comment:
enabled: true
format: markdown
group_by: check
collapse: falseProblem: Custom tool not found: my-tool
Solution: Make sure the tool is defined in the global tools: section with the exact same name.
Problem: Server fails to start
Solutions:
- Check if ports are available
- Verify tool definitions are valid
- Enable debug mode:
ai.debug: true
Problem: AI says it doesn't have access to tools
Solutions:
- Verify
ai_custom_tools:syntax (notai_custom_toolsingular) - If using
ai_mcp_servers, verifytools:field (nottoolsingular) - Check tool names match exactly (case-sensitive)
- Enable debug logging to see server startup
- Full documentation:
docs/ai-custom-tools.md - Examples:
examples/ai-custom-tools-simple.yaml - Advanced examples:
examples/ai-custom-tools-example.yaml