---
title: "Custom Tools"
---
Custom tools allow you to define reusable, parameterized SQL operations that are automatically registered as MCP tools. They provide type-safe interfaces for common database queries without writing repetitive code.
Custom tools are ideal for:
- **Frequently used queries**: Define once, use everywhere without rewriting SQL
- **Standardized data access**: Ensure consistent query patterns across your team
- **Controlled database access**: Expose specific operations without granting broad SQL access
- **AI-friendly interfaces**: Give AI models well-defined tools with clear parameters instead of open-ended SQL
- **Complex queries**: Encapsulate JOINs, aggregations, or multi-step operations into simple tool calls
- **Parameter validation**: Enforce type checking and allowed values before queries execute
## Configuration
Custom tools are defined in your `dbhub.toml` configuration file. See the [TOML Configuration](/config/toml) documentation for complete configuration reference.
**Required fields:**
- `name` - Unique tool identifier
- `description` - What the tool does (helps AI models)
- `source` - Database source ID to use
- `statement` - SQL query with parameter placeholders
- `parameters` - Parameter definitions (optional)
See [TOML Configuration - Tool Options](/config/toml#tool-options) for detailed field descriptions, parameter types, and validation rules.
## Examples
### Basic Query
Simple SELECT query with a single parameter:
```toml
[[tools]]
name = "get_user_by_id"
description = "Retrieve user details by their unique ID"
source = "production"
statement = "SELECT id, name, email, created_at FROM users WHERE id = $1"
[[tools.parameters]]
name = "user_id"
type = "integer"
description = "The unique user ID"
```
### Search with Limit
Search query with optional limit parameter:
```toml
[[tools]]
name = "search_employees"
description = "Search employees by name with configurable result limit"
source = "prod_pg"
statement = "SELECT emp_no, first_name, last_name FROM employee WHERE first_name ILIKE '%' || $1 || '%' LIMIT $2"
[[tools.parameters]]
name = "search_term"
type = "string"
description = "Name to search for (case-insensitive partial match)"
[[tools.parameters]]
name = "limit"
type = "integer"
description = "Maximum number of results"
default = 10
```
### Optional Filter with Enum
Query with optional status filter using enum validation:
```toml
[[tools]]
name = "list_orders"
description = "List orders with optional status filter"
source = "prod_pg"
statement = "SELECT * FROM orders WHERE customer_id = $1 AND ($2::text IS NULL OR status = $2)"
[[tools.parameters]]
name = "customer_id"
type = "integer"
description = "Customer ID"
[[tools.parameters]]
name = "status"
type = "string"
description = "Optional status filter"
required = false
allowed_values = ["pending", "processing", "shipped", "delivered"]
```
### Multiple Database Types
The same tool pattern works across different databases, just adjust the parameter placeholder syntax:
<Tabs>
<Tab title="PostgreSQL">
```toml
[[tools]]
name = "get_product"
description = "Get product by ID"
source = "postgres_db"
statement = "SELECT * FROM products WHERE id = $1"
[[tools.parameters]]
name = "product_id"
type = "integer"
description = "Product ID"
```
</Tab>
<Tab title="MySQL/MariaDB/SQLite">
```toml
[[tools]]
name = "get_product"
description = "Get product by ID"
source = "mysql_db"
statement = "SELECT * FROM products WHERE id = ?"
[[tools.parameters]]
name = "product_id"
type = "integer"
description = "Product ID"
```
</Tab>
<Tab title="SQL Server">
```toml
[[tools]]
name = "get_product"
description = "Get product by ID"
source = "sqlserver_db"
statement = "SELECT * FROM products WHERE id = @p1"
[[tools.parameters]]
name = "product_id"
type = "integer"
description = "Product ID"
```
</Tab>
</Tabs>
See [Parameter Placeholders](/config/toml#parameters) for syntax reference.
## Common Patterns
### Flexible Filtering
Use `COALESCE` or NULL checks to make parameters optional:
```toml
[[tools]]
name = "search_users"
description = "Search users with optional department filter"
source = "production"
# PostgreSQL: Use NULL check for optional parameters
statement = "SELECT * FROM users WHERE ($1::text IS NULL OR department = $1) LIMIT $2"
[[tools.parameters]]
name = "department"
type = "string"
description = "Optional department filter"
required = false
[[tools.parameters]]
name = "limit"
type = "integer"
description = "Maximum results"
default = 50
```
### Date Range Queries
```toml
[[tools]]
name = "get_orders_by_date_range"
description = "Retrieve orders within a date range"
source = "production"
statement = "SELECT * FROM orders WHERE created_at BETWEEN $1 AND $2 ORDER BY created_at DESC"
[[tools.parameters]]
name = "start_date"
type = "string"
description = "Start date (ISO 8601 format: YYYY-MM-DD)"
[[tools.parameters]]
name = "end_date"
type = "string"
description = "End date (ISO 8601 format: YYYY-MM-DD)"
```
### Aggregation Queries
```toml
[[tools]]
name = "get_sales_summary"
description = "Get sales summary by product category"
source = "production"
statement = """
SELECT
category,
COUNT(*) as total_orders,
SUM(amount) as total_revenue,
AVG(amount) as avg_order_value
FROM orders
WHERE status = $1
GROUP BY category
ORDER BY total_revenue DESC
"""
[[tools.parameters]]
name = "status"
type = "string"
description = "Order status"
allowed_values = ["completed", "pending", "cancelled"]
```
## Security & Validation
### SQL Injection Protection
Custom tools use parameterized queries, which provide automatic protection against SQL injection attacks. Parameter values are never interpolated directly into SQL strings.
<Warning>
Always use parameter placeholders. Never concatenate user input into SQL statements.
</Warning>
### Readonly Mode
The `readonly` setting on `execute_sql` only affects that tool. Custom tools are controlled by their SQL statement - DBHub analyzes the statement to determine if it's read-only.
```toml
# Configure execute_sql as read-only
[[tools]]
name = "execute_sql"
source = "prod_pg"
readonly = true
# Custom tools are unaffected by readonly setting
[[tools]]
name = "get_user_by_id"
source = "prod_pg"
description = "Get user details"
statement = "SELECT * FROM users WHERE id = $1"
[[tools.parameters]]
name = "user_id"
type = "integer"
description = "User ID"
```
### Max Rows Enforcement
For custom tools with parameterized LIMIT clauses, the connector's `max_rows` setting still applies:
```toml
[[tools]]
name = "execute_sql"
source = "prod_pg"
max_rows = 1000 # Hard limit on SELECT results
[[tools]]
name = "search_users"
source = "prod_pg"
description = "Search users with limit"
statement = "SELECT * FROM users WHERE active = $1 LIMIT $2"
# Even if $2 is 5000, max_rows=1000 will cap the results
```
### Startup Validation
Tools are validated when the server starts:
- All required fields must be present
- The specified source must exist
- Tool names must be unique and cannot conflict with built-in tools (`execute_sql`, `search_objects`)
- Parameter count must match SQL placeholders
- Parameter types must be valid
If validation fails, the server will not start and will display detailed error messages.
## Tool Response Format
Custom tools return the same response format as `execute_sql`:
**Success:**
```json
{
"success": true,
"rows": [
{
"id": 12345,
"name": "Alice Johnson",
"email": "alice@example.com",
"created_at": "2024-01-15T10:30:00Z"
}
],
"count": 1,
"source_id": "prod_pg"
}
```
**Error:**
```json
{
"success": false,
"error": "Parameter validation failed: user_id: Required",
"code": "EXECUTION_ERROR"
}
```
## Best Practices
- **Use descriptive names**: Tool names should clearly indicate their purpose (e.g., `get_active_users_by_department` rather than `query1`)
- **Write detailed descriptions**: Help AI models understand when to use the tool by providing clear, complete descriptions
- **Document parameter constraints**: Include units, ranges, and format expectations in parameter descriptions
- **Leverage enums**: Use `allowed_values` for parameters with a fixed set of valid options
- **Provide defaults**: Make tools easier to use by providing sensible defaults for optional parameters
- **Keep tools focused**: Each tool should perform a single, well-defined operation
- **Test parameter combinations**: Ensure optional parameters work correctly in all combinations
- **Use multi-line strings**: For complex SQL, use TOML's `"""` multi-line syntax for readability
## See Also
- [TOML Configuration](/config/toml) - Complete configuration reference
- [execute_sql](/tools/execute-sql) - Direct SQL execution tool
- [search_objects](/tools/search-objects) - Database schema exploration tool