Custom MCP Tools¶
Create custom CPG query tools for Claude Code and other MCP clients by defining SQL queries in a YAML file. No code changes required.
Overview¶
The dynamic tool loader reads .codegraph/tools.yaml from your project root and registers each tool definition as an MCP tool. This lets you create project-specific analysis queries that Claude Code (or any MCP client) can invoke by name.
Key features: - Define custom SQL queries as named MCP tools - Parameters with types and defaults - Read-only (SELECT queries only) — write operations are rejected - No code changes, no server restart (tools loaded at MCP server startup)
Quick Start¶
1. Create the tools file¶
mkdir -p .codegraph
Create .codegraph/tools.yaml:
tools:
- name: find_large_functions
description: Find functions with more than N lines of code
sql: |
SELECT name, filename, (line_number_end - line_number) as lines
FROM nodes_method
WHERE (line_number_end - line_number) > {min_lines}
ORDER BY lines DESC
LIMIT {limit}
parameters:
- name: min_lines
type: int
default: 100
- name: limit
type: int
default: 20
2. Configure MCP server¶
Ensure your config.yaml has:
mcp:
enabled: true
dynamic_tools_path: .codegraph/tools.yaml
3. Start MCP server¶
python -m src.mcp
The tool find_large_functions is now available to any MCP client.
4. Use from Claude Code¶
Add to your .claude/mcp.json:
{
"mcpServers": {
"codegraph": {
"command": "python",
"args": ["-m", "src.mcp", "--db", "data/projects/myproject.duckdb"]
}
}
}
Then ask Claude: “Find functions longer than 200 lines” — it will call find_large_functions with min_lines=200.
YAML Format¶
tools:
- name: tool_name # Required: unique tool name (snake_case)
description: Human-readable # Required: shown to MCP clients
sql: | # Required: SQL template (SELECT only)
SELECT ...
WHERE column > {param}
LIMIT {limit}
parameters: # Optional: tool parameters
- name: param # Parameter name (matches {param} in SQL)
type: int # Type: int, str, float
default: 10 # Default value if not provided
Parameter Substitution¶
Parameters use {name} syntax in SQL templates. They are substituted via Python str.format():
sql: |
SELECT name, filename
FROM nodes_method
WHERE CyclomaticComplexity > {threshold}
AND filename LIKE '%{path_pattern}%'
LIMIT {limit}
parameters:
- name: threshold
type: int
default: 10
- name: path_pattern
type: str
default: ""
- name: limit
type: int
default: 20
Example Tools¶
Find Uncalled Functions¶
- name: find_uncalled_functions
description: Find functions that are never called from other code
sql: |
SELECT m.name, m.filename, m.line_number
FROM nodes_method m
LEFT JOIN edges_call e ON m.id = e.dst
WHERE e.src IS NULL
ORDER BY m.filename, m.line_number
LIMIT {limit}
parameters:
- name: limit
type: int
default: 50
Find High-Complexity Methods¶
- name: find_complex_methods
description: Find methods with high cyclomatic complexity
sql: |
SELECT name, filename, CyclomaticComplexity, FanIn, FanOut
FROM nodes_method
WHERE CyclomaticComplexity > {threshold}
ORDER BY CyclomaticComplexity DESC
LIMIT {limit}
parameters:
- name: threshold
type: int
default: 15
- name: limit
type: int
default: 30
Find Security Sinks¶
- name: find_dangerous_calls
description: Find calls to potentially dangerous functions
sql: |
SELECT c.callee_name, c.filename, c.line_number, m.name as caller
FROM nodes_call c
JOIN nodes_method m ON c.method_id = m.id
WHERE c.callee_name IN ('strcpy', 'strcat', 'sprintf', 'gets')
ORDER BY c.callee_name, c.filename
LIMIT {limit}
parameters:
- name: limit
type: int
default: 100
Find TODO/FIXME Comments¶
- name: find_todos
description: Find methods with TODO or FIXME markers
sql: |
SELECT name, filename, line_number
FROM nodes_method
WHERE HasTodoFixme = true
ORDER BY filename, line_number
LIMIT {limit}
parameters:
- name: limit
type: int
default: 50
Security¶
Read-Only Enforcement¶
Only SELECT queries are allowed. The loader validates SQL at two points:
- At load time — tool definitions with non-SELECT SQL are rejected
- At execution time — generated SQL (after parameter substitution) is re-validated
Rejected SQL keywords: INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, GRANT, REVOKE.
Row Limits¶
Results are capped by mcp.max_query_rows (default: 1000) to prevent memory exhaustion.
Configuration Reference¶
# config.yaml
mcp:
enabled: true # Master on/off for MCP server
transport: stdio # Transport: stdio | http | websocket
max_query_rows: 1000 # Maximum rows per query result
dynamic_tools_path: .codegraph/tools.yaml # Path to custom tools YAML
Troubleshooting¶
Tool not appearing?
- Check that .codegraph/tools.yaml exists relative to the project root
- Check MCP server logs for validation warnings
- Verify mcp.enabled: true and mcp.dynamic_tools_path in config.yaml
SQL rejected?
- Ensure the query starts with SELECT
- Check for accidentally included DDL/DML keywords
- Verify parameter names match between {param} placeholders and parameters definitions
Empty results?
- Test the SQL directly: python -m src.cli gocpg query "YOUR SQL HERE"
- Check table and column names against Schema Reference
Related Documentation¶
- Schema Reference — CPG database tables and columns
- SQL Cookbook — SQL query examples
- ACP Integration — MCP configuration for IDEs
- CLI Guide —
gocpg querycommand for testing SQL
Module: src/mcp/dynamic_loader.py
Last updated: February 2026