Skip to main content
Glama
avantifellows

Avanti Fellows PostgreSQL MCP Server

Official

query

Execute read-only SELECT queries to explore, debug, or validate data in the Avanti Fellows PostgreSQL database.

Instructions

Execute a read-only SQL query against the database.

Only SELECT queries are allowed. Use this to explore data,
debug issues, or validate assumptions about the data.

Args:
    sql: A SELECT query to execute

Returns:
    JSON array of results, or error message

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sqlYes

Implementation Reference

  • The primary handler function for the 'query' MCP tool. It validates the SQL as read-only, executes it using asyncpg, serializes results to JSON, and handles errors.
    @mcp.tool()
    async def query(sql: str) -> str:
        """Execute a read-only SQL query against the database.
    
        Only SELECT queries are allowed. Use this to explore data,
        debug issues, or validate assumptions about the data.
    
        Args:
            sql: A SELECT query to execute
    
        Returns:
            JSON array of results, or error message
        """
        if not is_read_only(sql):
            return json.dumps({"error": "Only SELECT queries are allowed"})
    
        try:
            async with get_connection() as conn:
                rows = await conn.fetch(sql)
                # Convert to list of dicts, handling special types
                results = []
                for row in rows:
                    results.append({k: _serialize_value(v) for k, v in dict(row).items()})
                return json.dumps(results, indent=2, default=str)
        except Exception as e:
            return json.dumps({"error": str(e)})
  • Helper function called by the query handler to ensure only safe, read-only SELECT queries are executed.
    def is_read_only(sql: str) -> bool:
        """Check if SQL is read-only (SELECT only)."""
        normalized = sql.strip().upper()
        # Must start with SELECT or WITH (for CTEs)
        if not (normalized.startswith("SELECT") or normalized.startswith("WITH")):
            return False
        # Block dangerous keywords even in subqueries
        dangerous = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "TRUNCATE", "CREATE", "GRANT", "REVOKE"]
        return not any(kw in normalized for kw in dangerous)
  • Async context manager for obtaining PostgreSQL connections, used by the query handler.
    @asynccontextmanager
    async def get_connection():
        """Get a database connection."""
        conn = await asyncpg.connect(**DB_CONFIG)
        try:
            yield conn
        finally:
            await conn.close()
  • Utility function used in query handler to serialize PostgreSQL values (e.g., dates, bytes) to JSON-compatible formats.
    def _serialize_value(value):
        """Serialize special PostgreSQL types to JSON-compatible values."""
        if value is None:
            return None
        if isinstance(value, (dict, list)):
            return value
        if hasattr(value, "isoformat"):  # datetime, date, time
            return value.isoformat()
        if isinstance(value, bytes):
            return value.decode("utf-8", errors="replace")
        return value
  • The @mcp.tool() decorator registers the query function as an MCP tool.
    @mcp.tool()

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/avantifellows/mcp-postgres'

If you have feedback or need assistance with the MCP directory API, please join our Discord server