Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# PostgreSQL MCP Server Environment Configuration
# Copy this file to .env and update with your actual values

# Database Connection
# Required: PostgreSQL connection URI
DATABASE_URI=postgresql://username:password@localhost:5432/dbname

# Access Mode
# Options: unrestricted (full read/write), restricted (read-only with protections)
# Default: unrestricted
ACCESS_MODE=restricted

# Transport Mode
# Options: stdio (standard input/output), sse (server-sent events)
# Default: stdio
TRANSPORT=stdio

# SSE Configuration (only used when TRANSPORT=sse)
# SSE server host
# Default: localhost
SSE_HOST=localhost

# SSE server port
# Default: 8000
SSE_PORT=8000

# OpenAI API Key (optional)
# Required only for LLM-based index optimization features
# OPENAI_API_KEY=your-api-key-here

# Additional PostgreSQL Connection Options (optional)
# You can also use individual connection parameters instead of DATABASE_URI:
# DB_HOST=localhost
# DB_PORT=5432
# DB_NAME=dbname
# DB_USER=username
# DB_PASSWORD=password
# DB_SSLMODE=prefer
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,49 @@ uv pip install postgres-mcp

If you need to install `uv`, see the [uv installation instructions](https://docs.astral.sh/uv/getting-started/installation/).

### Environment Configuration

Postgres MCP Pro supports configuration through environment variables, which can be provided via:
1. A `.env` file (recommended for development)
2. Direct environment variables
3. Command-line arguments (take precedence over environment variables)

#### Using .env Files

1. Copy the `.env.example` file to `.env`:
```bash
cp .env.example .env
```

2. Edit the `.env` file with your configuration:
```bash
# Database connection
DATABASE_URI=postgresql://username:password@localhost:5432/dbname

# Or use individual connection parameters
DB_HOST=localhost
DB_PORT=5432
DB_NAME=dbname
DB_USER=username
DB_PASSWORD=password
DB_SSLMODE=prefer

# Server configuration
ACCESS_MODE=restricted # or unrestricted
TRANSPORT=stdio # or sse
SSE_HOST=localhost # for SSE transport
SSE_PORT=8000 # for SSE transport

# Optional: OpenAI API key for LLM-based index optimization
OPENAI_API_KEY=your-api-key-here
```

The precedence order for configuration is:
1. Command-line arguments (highest priority)
2. Environment variables
3. Values from `.env` file
4. Default values (lowest priority)


### Configure Your AI Assistant

Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies = [
"attrs>=25.3.0",
"psycopg-pool>=3.2.6",
"instructor>=1.7.9",
"python-dotenv>=1.0.0",
]
license = "mit"
license-files = ["LICENSE"]
Expand Down
59 changes: 51 additions & 8 deletions src/postgres_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
import signal
import sys
from enum import Enum
from pathlib import Path
from typing import Any
from typing import List
from typing import Literal
from typing import Union

import mcp.types as types
from dotenv import load_dotenv
from mcp.server.fastmcp import FastMCP
from pydantic import Field
from pydantic import validate_call
Expand Down Expand Up @@ -510,33 +512,49 @@ async def get_top_queries(


async def main():
# Parse command line arguments
# Load environment variables from .env file
# Try multiple locations for the .env file
env_paths = [
Path.cwd() / ".env", # Current working directory
Path(__file__).parent.parent.parent / ".env", # Project root (relative to src/postgres_mcp/server.py)
]

for env_path in env_paths:
if env_path.exists():
load_dotenv(env_path)
logger.debug(f"Loaded environment variables from {env_path}")
break
else:
# No .env file found, which is fine - we can use direct env vars or CLI args
logger.debug("No .env file found, using environment variables or CLI arguments")

# Parse command line arguments with environment variable defaults
parser = argparse.ArgumentParser(description="PostgreSQL MCP Server")
parser.add_argument("database_url", help="Database connection URL", nargs="?")
parser.add_argument(
"--access-mode",
type=str,
choices=[mode.value for mode in AccessMode],
default=AccessMode.UNRESTRICTED.value,
default=os.getenv("ACCESS_MODE", AccessMode.UNRESTRICTED.value),
help="Set SQL access mode: unrestricted (unrestricted) or restricted (read-only with protections)",
)
parser.add_argument(
"--transport",
type=str,
choices=["stdio", "sse"],
default="stdio",
default=os.getenv("TRANSPORT", "stdio"),
help="Select MCP transport: stdio (default) or sse",
)
parser.add_argument(
"--sse-host",
type=str,
default="localhost",
default=os.getenv("SSE_HOST", "localhost"),
help="Host to bind SSE server to (default: localhost)",
)
parser.add_argument(
"--sse-port",
type=int,
default=8000,
default=int(os.getenv("SSE_PORT", "8000")),
help="Port for SSE server (default: 8000)",
)

Expand All @@ -554,12 +572,37 @@ async def main():

logger.info(f"Starting PostgreSQL MCP Server in {current_access_mode.upper()} mode")

# Get database URL from environment variable or command line
database_url = os.environ.get("DATABASE_URI", args.database_url)
# Get database URL - precedence: CLI arg > DATABASE_URI > constructed from individual params
database_url = args.database_url

if not database_url:
# Try DATABASE_URI environment variable
database_url = os.environ.get("DATABASE_URI")

if not database_url:
# Try to construct from individual connection parameters
db_host = os.environ.get("DB_HOST")
db_port = os.environ.get("DB_PORT", "5432")
db_name = os.environ.get("DB_NAME")
db_user = os.environ.get("DB_USER")
db_password = os.environ.get("DB_PASSWORD")
db_sslmode = os.environ.get("DB_SSLMODE", "prefer")

if all([db_host, db_name, db_user]):
# Construct the connection URL
if db_password:
database_url = f"postgresql://{db_user}:{db_password}@{db_host}:{db_port}/{db_name}?sslmode={db_sslmode}"
else:
database_url = f"postgresql://{db_user}@{db_host}:{db_port}/{db_name}?sslmode={db_sslmode}"
logger.debug("Constructed database URL from individual connection parameters")

if not database_url:
raise ValueError(
"Error: No database URL provided. Please specify via 'DATABASE_URI' environment variable or command-line argument.",
"Error: No database connection provided. Please specify via:\n"
" 1. Command-line argument: postgres-mcp 'postgresql://...'\n"
" 2. DATABASE_URI environment variable\n"
" 3. Individual connection parameters (DB_HOST, DB_PORT, DB_NAME, DB_USER, DB_PASSWORD)\n"
" 4. .env file with any of the above variables",
)

# Initialize database connection pool
Expand Down