This guide walks you through creating custom YAML SQL tools from basic queries to advanced enterprise operations. You’ll learn to build tools that integrate seamlessly with AI agents while maintaining IBM i security and performance standards.
Prerequisites: Basic SQL knowledge and familiarity with IBM i system services (QSYS2 schema). For YAML syntax, any text editor will work, but one with YAML syntax highlighting is recommended.
# Database connection configurationsources: ibmi-system: host: ${DB2i_HOST} user: ${DB2i_USER} password: ${DB2i_PASS} port: 8076 ignore-unauthorized: true # Development only# Define your SQL toolstools: list_active_jobs: source: ibmi-system description: "List currently active jobs on the IBM i system" statement: | SELECT job_name, user_name, job_status, cpu_used, elapsed_time FROM qsys2.active_job_info WHERE job_status = 'ACTIVE' ORDER BY cpu_used DESC FETCH FIRST 20 ROWS ONLY parameters: []# Organize tools into logical groupstoolsets: basic_monitoring: title: "Basic System Monitoring" description: "Simple tools for monitoring system activity" tools: - list_active_jobs
TOOLS_YAML_PATH=my-first-tools.yaml npm run start:http
Use the MCP Inspector to test your tool:
Copy
npm run mcp-inspector
Development Tip: Always start with simple, parameter-free tools to verify your connection and SQL syntax. You can add complexity incrementally as you gain confidence with the YAML structure.
Parameters make tools dynamic and reusable. Let’s enhance our job listing tool with filtering capabilities:
Basic Parameters
Optional Parameters
Enum Parameters
Copy
tools: list_jobs_by_user: source: ibmi-system description: "List active jobs for a specific user" parameters: - name: user_name type: string required: true description: "IBM i user profile name (uppercase)" pattern: "^[A-Z][A-Z0-9_]*$" maxLength: 10 statement: | SELECT job_name, job_status, cpu_used, elapsed_time FROM qsys2.active_job_info WHERE user_name = :user_name AND job_status = 'ACTIVE' ORDER BY cpu_used DESC FETCH FIRST 50 ROWS ONLY
The MCP server automatically validates parameters before executing SQL:
Copy
parameters: - name: library_name type: string required: true pattern: "^[A-Z][A-Z0-9_]*$" # IBM i naming rules maxLength: 10 description: "IBM i library name (must be uppercase alphanumeric)" - name: row_limit type: integer minimum: 1 maximum: 1000 default: 50 description: "Number of rows to return (1-1000)"
Security Note: Parameter validation is your first line of defense against invalid input. Always include appropriate constraints, especially for string parameters that could be used in injection attacks.
tools: system_overview: source: ibmi-system description: "Comprehensive system status and configuration" statement: | SELECT system_name, current_timestamp, elapsed_time, total_jobs, active_threads, cpu_utilization, main_storage_size / 1024 / 1024 AS memory_gb, current_unprotected_used / 1024 / 1024 AS used_memory_gb FROM TABLE(QSYS2.SYSTEM_STATUS()) X subsystem_status: source: ibmi-system description: "Status of all subsystems" statement: | SELECT subsystem_name, subsystem_library, status, monitor_job, secondary_language, COALESCE(current_active_jobs, 0) as active_jobs, COALESCE(maximum_active_jobs, 0) as max_jobs FROM qsys2.subsystem_info ORDER BY subsystem_name
Build robust tools with comprehensive error handling:
Copy
tools: validated_object_query: source: ibmi-system description: "Query objects with comprehensive validation" parameters: - name: library type: string required: true pattern: "^[A-Z][A-Z0-9_]*$" maxLength: 10 description: "Library name (must exist and be accessible)" - name: object_filter type: string required: false pattern: "^[A-Z*][A-Z0-9_*]*$" maxLength: 10 description: "Object name filter (supports * wildcards)" statement: | WITH library_check AS ( SELECT library_name FROM qsys2.library_info WHERE library_name = :library ), object_list AS ( SELECT o.*, l.library_name FROM library_check l, table(qsys2.object_statistics(l.library_name, COALESCE(:object_filter, '*ALL'))) o ) SELECT CASE WHEN NOT EXISTS(SELECT 1 FROM library_check) THEN 'ERROR: Library ' || :library || ' not found or not accessible' ELSE object_name END AS result, object_type, object_size, created_timestamp FROM object_list UNION ALL SELECT 'ERROR: Library ' || :library || ' not found or not accessible', NULL, NULL, NULL FROM library_check WHERE NOT EXISTS(SELECT 1 FROM library_check) ORDER BY result
# Validate YAML syntax and structurenpm run validate -- --config my-tools.yaml# List available toolsetsnpm run start:http -- --list-toolsets --tools my-tools.yaml# Test specific tool executionnpm run test -- --grep "yaml-sql"
For complete testing strategies including MCP Inspector usage, see the Quick Start Guide. For production testing, refer to the Production Deployment guide.
Organize tools by business domain or functional area:
Copy
my-tools/├── performance.yaml # System performance monitoring├── security.yaml # Security and audit tools├── business.yaml # Business logic and reporting└── development.yaml # Development and testing tools
Tool Design Philosophy: Effective SQL tools balance three concerns: usability (clear parameters and descriptions for AI agents), security (proper validation and authority checking), and performance (efficient queries that respect system resources). Start simple and add complexity incrementally as you understand your specific use cases.