MCP server for PostgreSQL database queries via Cloud SQL Proxy
Solid foundation with consistent naming, comprehensive descriptions, and proper schema definitions. All 6 tools follow verb_noun pattern (postgres_list_*, postgres_describe_*, postgres_query_*, postgres_count_*, postgres_list_*). Descriptions average ~250 chars, exceeding baseline (194 chars). All parameters have type definitions and descriptions. Tool annotations present (readOnlyHint, destructiveHint, idempotentHint). However, output schemas are not formally documented, responses use ad-hoc structuredContent fields without declared return types. Error handling is basic (generic error messages, no recovery guidance). No pagination support despite query_data potentially returning large result sets. SQL injection risk in count_rows and query_data via string interpolation of table/column names.
Count the total number of rows in a table, with optional WHERE filter. Args: - table_name (string): Name of the table to count. - where_clause (string, optional): WHERE condition without the WHERE keyword (e.g., "status = 'active'" or "age > 18"). - database (string, optional): Database name. If omitted, uses DB_NAME env var. Returns: The total count of matching rows. Example: - Count all rows: table_name="users" - Count with filter: table_name="users", where_clause="active = true" - Count with date: table_name="orders", where_clause="created_at > '2024-01-01'" Error Handling: - Returns an error if the table doesn't exist or WHERE clause is invalid
Get the structure of a table including columns, constraints, and indexes. Args: - table_name (string): Name of the table to describe. - database (string, optional): Database name. If omitted, uses DB_NAME env var. Returns: - Column details: name, data type, nullable, default value. - Constraints: primary keys, unique, check, and foreign key constraints. - Indexes: all indexes defined on the table. Example: - Use when: "What columns does the users table have?" - Use when: "Show me the structure of the orders table" - Use when: "What indexes are on the users table?" Error Handling: - Returns an error if the table doesn't exist
List all available databases in the PostgreSQL instance. Returns: A list of database names available on the server. Example: - Use when: "What databases are available?" - Use when: "Show me all the databases" Error Handling: - Returns an error message if the connection fails
Output schemas not formally documented. Tools return structuredContent with ad-hoc field names (e.g., 'table', 'count', 'where_clause') without declared return types. LLMs cannot reliably extract fields for downstream tool chaining.
SQL injection vulnerability in count_rows and query_data. Table names and WHERE clauses are interpolated directly into SQL strings without parameterization. Malicious input like 'users; DROP TABLE users;' would execute.
No pagination support. postgres_query_data accepts a limit parameter (default 100, max 1000) but does not return a cursor, offset, or total_count. Large result sets cannot be iterated; agents cannot discover if more rows exist.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 58 | 2025-06-18+ | v2 |
Discover foreign key relationships between tables in a database. Use this tool to understand how tables are related before writing JOIN queries. Returns all foreign key constraints showing which columns in which tables reference other tables. Args: - table_name (string, optional): Filter relationships for a specific table. - database (string, optional): Database name. If omitted, uses DB_NAME env var. Returns: A list of foreign key relationships with source/target tables and columns. Example: - "What tables are related to the users table?" → table_name="users" - "Show me all relationships in the database" → no table_name filter - "How do orders connect to customers?" → table_name="orders"
List all tables in a database's public schema. Args: - database (string, optional): Database name. If omitted, uses DB_NAME env var. Returns: A list of table names in the public schema. Example: - Use when: "What tables are in my database?" - Use when: "Show me the tables in the users database" Error Handling: - Returns an error if the database doesn't exist or connection fails
Execute a read-only SELECT query against a PostgreSQL database. IMPORTANT: Only SELECT queries are allowed. Any INSERT, UPDATE, DELETE, DROP, ALTER, or CREATE statements will be rejected. Args: - sql (string): The SELECT query to execute. Use $1, $2, etc. for parameters. - params (array, optional): Parameter values for $1, $2, etc. - database (string, optional): Database name. If omitted, uses DB_NAME env var. - limit (number, optional): Max rows to return (default: 100, max: 1000). Returns: Query results as rows with column headers. Example: - "SELECT * FROM users WHERE active = $1" with params [true] - "SELECT name, email FROM customers LIMIT 10" Error Handling: - Rejects non-SELECT queries - Returns error for syntax errors or invalid tables
Error messages lack recovery guidance. Errors return generic text like 'Error counting rows: <message>' without suggesting next steps. LLMs cannot self-correct or plan recovery.
No input validation or constraint enforcement. Parameters like 'limit' (max 1000) and 'where_clause' are not validated before use. Invalid input produces cryptic database errors instead of actionable feedback.