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
- Parameterized queries — values passed via DuckDB ? placeholders (not string interpolation)
- Read-only (SELECT queries only) — write operations and set 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.
Full CLI options:
python -m src.mcp --db data/projects/myproject.duckdb # stdio (default)
python -m src.mcp --transport sse --port 27495 # SSE transport
python -m src.mcp --transport http --port 27495 # Streamable HTTP
python -m src.mcp --transport sse --no-auth # SSE without auth
python -m src.mcp --verbose # Debug logging
| Argument | Description |
|---|---|
--db PATH |
Path to DuckDB CPG database |
--transport {stdio,sse,http} |
Transport protocol (default: from config or stdio) |
--host HOST |
Bind address for SSE/HTTP (default: 0.0.0.0) |
--port PORT |
Listen port for SSE/HTTP (default: 27495) |
--no-auth |
Disable authentication for SSE/HTTP transports |
--verbose, -v |
Enable debug logging |
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, string, float
default: 10 # Default value if not provided
Parameter Types¶
| Type | Validation | Example |
|---|---|---|
int |
Converted to integer, raises ValueError on failure |
100 |
float |
Converted to float, raises ValueError on failure |
3.14 |
string |
SQL metacharacter check (rejects ', ", ;, --, /*, */) |
"src/" |
Any unrecognized type falls back to string coercion with truncation to 1000 characters (no metacharacter validation).
Parameter Substitution¶
Parameters use {name} syntax in SQL templates. At execution time, each {name} placeholder is replaced with a ? positional marker, and the sanitized values are passed as a parameter tuple to DuckDB’s parameterized query API. This prevents SQL injection by design.
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: string
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
Blocked SQL keywords: INSERT, UPDATE, DELETE, DROP, ALTER, CREATE, TRUNCATE, GRANT, REVOKE, UNION, EXCEPT, INTERSECT.
Parameterized Queries¶
User-supplied parameter values are never interpolated into SQL strings. The loader replaces {name} placeholders with ? markers and passes values as a tuple to DuckDB’s parameterized execution API (execute_query(sql, parameters=...)).
SQL Metacharacter Blocking¶
String parameters (type string) are checked against a blocklist of SQL metacharacters: ', ", ;, --, /*, */. If any are found, the query is rejected with a ValueError.
Size and Count Limits¶
| Limit | Config key | Default |
|---|---|---|
| SQL query size | security.dynamic_sql_max_length_kb |
10 KB |
| Parameter count | security.dynamic_sql_max_parameters |
10 |
These limits are enforced at execution time. Exceeding them raises a ValueError.
Row Limits¶
Results are capped by mcp.max_query_rows (default: 1000) to prevent memory exhaustion. If the SQL template does not contain a LIMIT clause, the server appends one automatically.
Authentication¶
SSE and HTTP transports are protected by MCPAuthMiddleware. Clients must provide one of:
Authorization: Bearer <jwt>header — validated viaverify_token()X-API-Keyheader — validated viaApiKeyRepository
Missing or invalid credentials return HTTP 401. The stdio transport is local and does not require authentication.
Use --no-auth to disable authentication for development:
python -m src.mcp --transport sse --no-auth
Configuration Reference¶
# config.yaml
mcp:
enabled: true # Master on/off for MCP server
transport: stdio # Transport: stdio | sse | http
host: 0.0.0.0 # Bind address for SSE/HTTP
http_port: 27495 # Listen port for SSE/HTTP
max_query_rows: 1000 # Maximum rows per query result
dynamic_tools_path: .codegraph/tools.yaml # Path to custom tools YAML
# Security limits (in security section)
security:
dynamic_sql_max_length_kb: 10 # Max SQL size in KB
dynamic_sql_max_parameters: 10 # Max number of parameters per tool
Troubleshooting¶
Tool not appearing?
- Check that .codegraph/tools.yaml exists relative to the project root
- Check MCP server logs for validation warnings (--verbose for debug output)
- 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 (including UNION, EXCEPT, INTERSECT)
- Verify parameter names match between {param} placeholders and parameters definitions
- Check that string parameters do not contain SQL metacharacters (', ", ;, --)
Empty results?
- Test the SQL directly: python -m src.cli gocpg query "YOUR SQL HERE"
- Check table and column names against Schema Reference
Authentication errors (SSE/HTTP)?
- Verify Bearer JWT or X-API-Key is provided
- Use --no-auth for local development
Built-in MCP Tools¶
CodeGraph ships with 149 built-in MCP tools across code analysis, security, patterns, enterprise features, OpenViking context operations, and platform integrations. These tools are always available without any configuration.
For the complete list of built-in tools, see ACP Integration — Available Tools.
Related Documentation¶
- Schema Reference — CPG database tables and columns
- SQL Cookbook — SQL query examples
- ACP Integration — MCP configuration for IDEs + full tool list
- CLI Guide —
gocpg querycommand for testing SQL
Module: src/mcp/dynamic_loader.py
Last updated: March 2026