How to Optimize Slow Queries Using pg_stat_statements with the MCP PostgreSQL Server
The MCP PostgreSQL Operations server exposes a built-in get_pg_stat_statements_top_queries tool that automatically detects the PostgreSQL version and extension availability, returning a formatted markdown table of the most time-consuming queries without requiring manual SQL.
The call518/mcp-postgresql-ops repository provides a Model Context Protocol (MCP) server that simplifies database performance tuning by wrapping pg_stat_statements in high-level tools. By leveraging this integration, developers can identify bottlenecks through automated version-aware queries and focus on optimization rather than writing boilerplate SQL.
Architecture of the pg_stat_statements Integration
The MCP server abstracts the complexity of querying pg_stat_statements through a four-stage pipeline that handles extension detection, version compatibility, data retrieval, and formatting.
Extension Detection and Validation
Before executing any query, the server verifies that pg_stat_statements is installed using check_extension_exists(). This function, located in src/mcp_postgresql_ops/functions.py (lines 81-87), prevents errors on databases where the extension hasn't been enabled.
Version-Aware Query Generation
PostgreSQL changed its timing column names between versions 12 and 13. The get_pg_stat_statements_query() function in src/mcp_postgresql_ops/version_compat.py (lines 503-545) dynamically constructs the correct SELECT statement: using total_time and mean_time for PostgreSQL 12, and total_exec_time and mean_exec_time for PostgreSQL 13+. This ensures accurate statistics regardless of your database version.
Data Retrieval and Formatting
The get_pg_stat_statements_data() function in src/mcp_postgresql_ops/functions.py (lines 91-100) executes the generated query and returns a list of dictionaries containing query statistics. These results are then processed by format_table_data() (lines 1-68 in the same file) to convert raw byte counts and durations into human-readable formats.
MCP Tool Exposure
The @mcp.tool() decorator in src/mcp_postgresql_ops/mcp_main.py (lines 48-90) exposes get_pg_stat_statements_top_queries, which orchestrates the entire workflow and delivers a markdown table ready for chat interfaces or direct API consumption.
How to Query pg_stat_statements via the MCP Server
You can retrieve slow query statistics through multiple interfaces depending on your deployment.
Python Client Integration
Import the tool directly from the MCP main module to fetch statistics programmatically:
from mcp_postgresql_ops.mcp_main import get_pg_stat_statements_top_queries
# Retrieve the 20 slowest queries from the default database
result_md = await get_pg_stat_statements_top_queries()
print(result_md)
HTTP and Chat Interfaces
When deployed with FastAPI, invoke the tool via JSON payload:
{
"tool": "get_pg_stat_statements_top_queries",
"arguments": { "limit": 15, "database_name": "myapp" }
}
The response returns a markdown table ordered by total_exec_time DESC, showing columns including queryid, query, calls, rows, and timing metrics.
Direct Low-Level Access
For custom processing, bypass the formatting layer and access raw data:
from mcp_postgresql_ops.functions import get_pg_stat_statements_data
# Returns a list of dictionaries for programmatic analysis
data = await get_pg_stat_statements_data(limit=10, database="mydb")
for row in data:
print(row["query"], row["total_exec_time"])
Optimization Workflow for Slow Queries
Once you have extracted data using pg_stat_statements, follow this structured approach to improve performance:
- Identify high-impact statements – Focus on queries with high
total_exec_timeor frequentcallsthat appear in the MCP tool output. - Locate source code – Use the
querycolumn text to find the corresponding SQL in your application codebase. - Analyze execution plans – Run
EXPLAIN (ANALYZE, BUFFERS)on the identified queries to detect sequential scans or excessive sorts. - Implement fixes – Add missing indexes on WHERE clause columns, rewrite inefficient joins, or adjust PostgreSQL configuration parameters like
work_membased on plan evidence. - Validate improvements – Re-run
get_pg_stat_statements_top_queriesafter changes to confirm reduced execution times for the targeted statements.
Automating Performance Reports
Combine the MCP tool with custom logic to generate actionable reports:
from mcp_postgresql_ops.mcp_main import get_pg_stat_statements_top_queries
async def generate_optimization_report(db: str = None):
"""
Produce a markdown report highlighting queries with >100ms average execution time.
"""
md_table = await get_pg_stat_statements_top_queries(limit=10, database_name=db)
lines = md_table.splitlines()
header, separator, *rows = lines
report = ["# PostgreSQL Slow-Query Report", "", header, separator]
for row in rows:
cols = [c.strip() for c in row.split("|")]
avg_ms = float(cols[5]) # mean_exec_time column
if avg_ms > 100:
report.append(row + " **⚠️ High Avg Time**")
else:
report.append(row)
report.append("\n**Suggested Action:** If filtering consistently uses a specific column (e.g., `user_id`), consider `CREATE INDEX ON <table> (user_id);`")
return "\n".join(report)
Enabling the Extension
Before using these features, ensure pg_stat_statements is installed. The repository includes scripts/enable-pgsql-extensions.sh to automate extension setup in Docker-based PostgreSQL instances. Alternatively, run:
CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
Summary
- The MCP PostgreSQL server provides version-agnostic access to
pg_stat_statementsthrough theget_pg_stat_statements_top_queriestool. - Automatic compatibility handling in
version_compat.pyeliminates manual SQL differences between PostgreSQL 12 and 13+. - Extension safety checks in
functions.pyprevent errors on unconfigured databases. - Data is returned as formatted markdown tables via
mcp_main.py, suitable for both human review and automated processing. - Focus optimization efforts on queries with the highest
total_exec_timefor maximum performance ROI.
Frequently Asked Questions
What is the difference between total_time and total_exec_time in pg_stat_statements?
PostgreSQL 12 uses total_time and mean_time to track query execution duration, while PostgreSQL 13 and later versions renamed these columns to total_exec_time and mean_exec_time to clarify that they measure execution time only (excluding planning time). The MCP server automatically detects your version and queries the correct columns.
How do I enable pg_stat_statements if the MCP tool reports the extension is missing?
Execute CREATE EXTENSION IF NOT EXISTS pg_stat_statements; as a superuser in your target database, or use the provided scripts/enable-pgsql-extensions.sh script for Docker deployments. You must also ensure shared_preload_libraries = 'pg_stat_statements' is set in postgresql.conf and restart the server.
Can I filter pg_stat_statements results by specific databases or time windows?
The get_pg_stat_statements_top_queries tool accepts a database_name parameter to restrict results to a specific database. For custom filtering (e.g., specific time windows or query patterns), use the low-level get_pg_stat_statements_data() function and apply additional Python filtering logic to the returned list of dictionaries.
Why does the MCP server check for extension existence before querying?
The check_extension_exists() function prevents runtime errors and provides clear feedback when pg_stat_statements is not installed. This check occurs in src/mcp_postgresql_ops/functions.py before any data retrieval, ensuring the tool fails gracefully with an informative message rather than a database error.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →