Skip to main content
Glama

FastMCP PostgreSQL server

A small learning project that exposes an existing PostgreSQL database through an MCP server. It is intentionally narrow: inspect the schema, turn a natural-language request into SQL, and execute read-only queries.

The generate_query tool sends the prompt and database schema to a configured LLM through an OpenAI-compatible API, then validates the returned SQL before passing it back to the MCP client.

How the project is organized

src/mcp_server/
  server.py         FastMCP application and tool definitions
  database.py       PostgreSQL connection, schema discovery, read-only queries
  sql_generator.py  LLM-backed prompt-to-SQL generation and validation
tests/              Unit tests for SQL generation and database helpers

The server exposes five tools:

Tool

Purpose

list_tables

List tables visible to the configured database role

describe_table

Return columns and PostgreSQL data types

generate_query

Ask the configured LLM for a validated read-only query

run_readonly_query

Execute one SELECT or WITH query

execute_generated_query

Generate and execute a query immediately (dangerous)

database.py sets PostgreSQL's default_transaction_read_only for every connection. Still use a database role with only the permissions this server needs; application-level checks are defense in depth, not authorization.

Related MCP server: MCP PostgreSQL Server

Setup

Requires Python 3.11+ and network access to PostgreSQL.

py -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -e ".[dev]"
Copy-Item .env.example .env

Edit .env with the connection string for your existing database:

DATABASE_URL=postgresql://user:password@localhost:5432/database_name
DB_CONNECT_TIMEOUT=5

The server and the VS Code launch profiles load .env automatically. Do not put the connection string in a PowerShell profile or hard-code it in .vscode/launch.json. Never commit .env; it is ignored by Git, while .env.example is safe to commit.

Use the exact connection values from pgAdmin's connection properties: Host name/address, Port, Maintenance database (usually the database name), Username, and password. For example, a local database commonly uses localhost, port 5432, and postgres, but do not assume those values.

Before using the Inspector, test the same connection directly:

python -c "from mcp_server.database import Database; print(Database().list_tables())"

If that command times out, compare .env with pgAdmin and check that the host, port, database, username, and SSL settings match. DB_CONNECT_TIMEOUT limits how long a connection attempt waits; it does not make an unavailable database available.

Run and test

Run the unit tests:

py -m pytest

Confirm the FastMCP application imports and registers its tools:

python -c "from mcp_server.server import mcp; print([tool.name for tool in mcp._tool_manager.list_tools()])"

Start the server over stdio:

mcp-sql-server

VS Code launch profiles

The project includes three profiles in .vscode/launch.json. Open the Run and Debug view (Ctrl+Shift+D), select a profile, and press F5:

  • Launch MCP server starts mcp_server.server with .env loaded. The server uses stdio and waits for an MCP client, so an idle terminal is expected.

  • Launch MCP Inspector runs the MCP CLI's dev command for this server and opens the browser-based Inspector. It uses .env through scripts/launch_inspector.py; no PowerShell environment variable is needed.

  • Run tests launches pytest -q with the same project environment. Set a breakpoint in a test or application file to debug it.

Select the Python interpreter from .venv when VS Code prompts for one. The Python extension and its debugger (debugpy) must be installed. The launch profiles load .env; environment variables configured by VS Code or the terminal can still take precedence according to VS Code's environment rules.

The process waits for an MCP client. It is not an HTTP server and will appear idle in the terminal; that is expected. Configure an MCP client or the MCP Inspector to launch mcp-sql-server from this project environment. Then try:

  1. list_tables

  2. describe_table with a table name such as users or reporting.orders

  3. generate_query with a request such as “show the first 10 orders”

  4. Review the generated SQL, then call run_readonly_query

execute_generated_query combines steps 3 and 4. It is intentionally marked DANGEROUS in the tool description and response because it executes model-generated SQL without giving you a separate review step. Prefer generate_query, inspect the SQL, and then call run_readonly_query. Read-only mode prevents data modification, but it does not prevent expensive queries, excessive result sets, sensitive data exposure, or incorrect results.

Using an LLM for SQL generation

generate_query calls an OpenAI-compatible chat-completions endpoint. The server sends the user's prompt plus the PostgreSQL schema, then validates the response before returning it. The default configuration targets local Ollama:

Ollama

Ollama is a separate application that runs open-source LLMs locally and exposes them through a local API. It is not installed inside this Python project or virtual environment.

Download and install Ollama from the official page:

https://ollama.com/download

Verify the installation, download a model, and start the local service:

ollama --version
ollama pull qwen2.5-coder:3b
ollama serve

Ollama normally listens on http://localhost:11434. The MCP server connects to that endpoint using the following settings.

Set these values in .env:

LLM_BASE_URL=http://localhost:11434/v1
LLM_MODEL=qwen2.5-coder:3b
LLM_API_KEY=
LLM_TIMEOUT=60

If generate_query cannot reach Ollama, verify the service and model before restarting the MCP Inspector:

ollama list
Invoke-RestMethod http://localhost:11434/api/tags

LLM_MODEL must exactly match a name shown by ollama list, for example qwen2.5-coder:3b. If Ollama is running on another host or port, update LLM_BASE_URL accordingly. Ollama's OpenAI-compatible URL includes /v1.

For vLLM, LM Studio, or a hosted OpenAI-compatible service, change LLM_BASE_URL, LLM_MODEL, and LLM_API_KEY as appropriate. Restart the MCP server after changing .env.

Keep generation separate from execution. Model output is untrusted text: the generator accepts only one SELECT or WITH statement, and the database layer independently enforces read-only transactions. For production, add SQL parsing, query timeouts, result-size limits, query logging, and schema/table allow-lists.

Open-source model options

These are good starting points for text-to-SQL experimentation. Run them locally with Ollama, vLLM, or another OpenAI-compatible server, then call that endpoint from Python.

  • Qwen2.5-Coder 3B/7B/14B — strong code and SQL generation for its size; a practical local starting point.

  • DeepSeek-Coder V2 Lite — capable coding model with useful SQL reasoning; check its memory requirements before choosing a larger variant.

  • SQLCoder — specifically tuned for text-to-SQL; useful when SQL generation is the primary task rather than general conversation.

  • Qwen3-Coder — newer coding-focused option; consider it when you have more GPU memory or a hosted inference endpoint.

  • Llama 3.1/3.2 Instruct — broad ecosystem and easy local deployment; may need stronger schema/prompt constraints for reliable SQL.

Hardware recommendations for less than 8 GB of VRAM

For a GPU with less than 8 GB of VRAM, start with a 3B–7B model in a 4-bit quantized format. The model, context window, and runtime overhead all consume VRAM, so avoid assuming that a model's parameter count is its total memory requirement.

  • Best starting point: Qwen2.5-Coder 3B or 7B at 4-bit quantization.

  • SQL-focused option: SQLCoder in its smallest available quantized variant, if the model fits comfortably with your schema context.

  • Good fallback: a 3B instruct/coder model with a concise schema prompt.

  • Avoid initially: 14B+ models, large context windows, and unquantized weights.

With Ollama, try a 3B model first and keep the schema context focused on tables relevant to the request. If a 7B model is slow, runs out of memory, or causes the system to swap, move down to 3B or use a hosted endpoint. CPU inference works for experimentation but will usually have noticeably higher latency.

Model quality depends heavily on schema context and evaluation. Start with a small model and a fixed set of representative prompts, compare generated SQL against expected queries, and only then consider fine-tuning or a larger model. Check each model's license and hardware requirements before shipping it.

Useful next steps

  1. Add a get_schema tool that returns only the tables relevant to a request.

  2. Add SQL parsing/validation and a configurable maximum row count.

  3. Add integration tests against a disposable PostgreSQL instance.

  4. Add query timing and audit logging without logging credentials or sensitive result data.

Available Tools

5 tools
describe_tableB

Return column metadata for a table.

ParametersJSON Schema
NameRequiredDescriptionDefault
table_nameYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

B3.1/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry the transparency burden. 'Return column metadata' implies a safe, read-only operation, but no explicit statement about side effects, permissions, or behavior on missing tables is provided.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One sentence, no filler, front-loaded with the action verb. It is as concise as possible while communicating the core function.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present and only one parameter, the tool is inherently simple. The description is nearly sufficient, but lacks usage context relative to sibling tools and any note about table name qualification, leaving a small gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The single required parameter table_name has no schema description (0% coverage). The description only says 'a table' and does not add detail about expected format, qualification, or allowed values, so the agent gets little beyond the parameter name.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description identifies a specific action ('Return') and object ('column metadata for a table'), making the tool's purpose immediately clear. It does not explicitly call out sibling tools, so it stops short of the top score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use describe_table rather than siblings like list_tables or generate_query. There is no mention of use cases, prerequisites, or exclusion conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

execute_generated_queryA

DANGEROUS: generate SQL from a prompt and execute it immediately.

This executes model-generated SQL without a separate review step. Use generate_query and run_readonly_query instead when possible.

ParametersJSON Schema
NameRequiredDescriptionDefault
promptYes

Output Schema

ParametersJSON Schema
NameRequiredDescription

No output parameters

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It warns 'DANGEROUS' and explains that model-generated SQL is executed immediately without review, which conveys the primary risk profile. It could be more explicit about potential write/destructive effects, but the warning is substantial.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no filler. It front-loads the most critical information ('DANGEROUS') and each sentence adds essential context about behavior and alternatives.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter tool with an output schema, the description covers the core behavior, risk, and alternatives. It is slightly lacking in explicit details about prompt semantics and side effects, but overall the agent has enough to call it correctly and safely.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must explain the 'prompt' parameter. It says 'generate SQL from a prompt,' which clarifies that the prompt is the natural-language input for SQL generation, but it does not specify prompt expectations, format, or constraints. This is minimal compensation for a complete lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a precise action: generate SQL from a prompt and execute it immediately. It clearly distinguishes itself from siblings by noting that it executes without a separate review step, unlike generate_query and run_readonly_query.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly steers agents toward safer alternatives: 'Use generate_query and run_readonly_query instead when possible.' This gives a clear preference order, though it does not define exact conditions for when this tool must be used.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

generate_queryA

Turn a natural-language request into one read-only SQL statement.

ParametersJSON Schema
NameRequiredDescriptionDefault
promptYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.7/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden of behavioral disclosure. It discloses that the tool produces one read-only SQL statement and does not claim to execute it, which are useful behavioral traits. However, it does not elaborate on potential constraints, validation behavior, or how it relates to execution beyond that implication.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence with no wasted words. Every phrase adds value: 'natural-language request' defines the input, 'read-only' signals safety, and 'SQL statement' defines the output.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a single-parameter generation tool with an output schema, the description is nearly complete: it defines the input, the output, and the read-only nature. It would be more complete with an explicit note about not executing the generated SQL and when to choose this over the sibling execution tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has zero description coverage, so the description must clarify the parameter. It does this by identifying the input as a 'natural-language request,' which adds meaning to the bare 'prompt' property. It also defines what the output will be, helping the agent understand the transformation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('turn ... into') and resource/outcome: a natural-language request becomes one read-only SQL statement. It clearly distinguishes generation from the sibling execution tools by describing the output as a statement rather than a run operation, though it does not explicitly name an alternative.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the tool is used when a natural-language request needs to be converted into SQL, and the read-only qualifier signals a safe generation context. It does not explicitly say when to use this tool instead of run_readonly_query, execute_generated_query, or the table-introspection siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_tablesA

List the tables available in the configured read-only database.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description must carry the behavioral disclosure burden. It does note the database is 'read-only', implying this is a safe read operation, but it does not disclose potential edge behaviors like whether views or system tables are included, whether sorting is applied, or any error conditions. The read-only hint provides some context but not comprehensive behavioral detail.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, tightly worded sentence with no redundant phrases. Every word contributes meaning: 'List' gives the action, 'tables' gives the object, and 'configured read-only database' gives the scope.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter listing tool with a provided output schema, the description is nearly complete. It tells the agent what the tool does, and the output schema can document the return structure. It lacks only a brief note about when to use it in the broader tool workflow, but that is already captured by the low usage_guidelines score.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has zero parameters, so parameter documentation is not needed. With 0 parameters, the baseline is 4, and the description correctly omits parameter explanations because none exist.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb ('List') and a specific resource ('tables available in the configured read-only database'). It clearly distinguishes itself from sibling query and description tools because it enumerates all tables rather than running a query or describing one table.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives no explicit guidance on when to use this tool versus alternatives such as describe_table or run_readonly_query. The context signals list sibling tools, but the description does not reference them or define conditions for choosing this tool over them.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

run_readonly_queryA

Execute a single read-only SELECT or WITH query and return its rows.

ParametersJSON Schema
NameRequiredDescriptionDefault
queryYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses key behavior: the operation is read-only and returns query rows. It doesn't cover permissions, dialects, limits, or error behavior, but the core safety-relevant trait is explicit.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One sentence with no filler; the operative verb and key constraints ('read-only', 'single', 'SELECT or WITH') come first and the result is stated.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter tool with an output schema, the description captures the input constraint and the output shape. It doesn't need to restate the schema's return format. The main gap is no explicit sibling-selection guidance, but that is already accounted for under usage guidelines.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema's query parameter has no description, so the description must compensate. It adds meaning by restricting the string to a single read-only SELECT or WITH query, but it gives no syntax hints, examples, or statement-termination expectations.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States an explicit action ('Execute') and a constrained resource ('single read-only SELECT or WITH query'), plus the result ('return its rows'). It doesn't name sibling tools, but the read-only SELECT/WITH restriction helps an agent distinguish it from execute_generated_query.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for read-only query execution, but never states when to prefer this over execute_generated_query, generate_query, or not to use it. There are no explicit alternatives or exclusion conditions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 5 tool updatesv0.1.0
    • First observeddescribe_table
    • First observedexecute_generated_query
    • First observedgenerate_query
    • First observedlist_tables
    • First observedrun_readonly_query

TDQS

A3.9/5.0
Disambiguation4/5

Most tools are clearly distinct: schema inspection (list_tables, describe_table), SQL generation (generate_query), and query execution (run_readonly_query). execute_generated_query overlaps by combining generation and execution, but its DANGEROUS warning and guidance to prefer the separate steps mitigate confusion.

Naming Consistency5/5

All tool names follow a consistent snake_case verb_noun pattern, with optional qualifiers like readonly and generated. The naming clearly indicates the action and target (list_tables, describe_table, generate_query, run_readonly_query, execute_generated_query).

Tool Count5/5

Five tools is well-scoped for a read-only database interface: schema exploration, query generation, exact query execution, and a dangerous combined shortcut. Each tool contributes a distinct capability without unnecessary bloat.

Completeness5/5

The tool surface covers the complete read-only workflow: see what tables exist, inspect schema, translate natural language to SQL, and execute read-only queries. The optional generate-and-execute tool also fills the convenience path without introducing an obvious dead end.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables interaction with PostgreSQL databases through MCP, allowing users to explore database structures, inspect table schemas, and execute read-only SQL queries.
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables secure read-only access to PostgreSQL databases, allowing users to list tables, query schemas, execute SELECT statements, and inspect table structures through natural language interactions.
    607
    4
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    Provides read-only access to PostgreSQL databases, enabling querying, schema exploration, and table metadata retrieval via MCP tools like query, list-tables, describe-table, list-schemas, and list-environments.
    -

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/eastonjeff/mcp-server'

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