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
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,17 @@ The Postgres MCP Pro Docker image will automatically remap the hostname `localho

Replace `postgresql://...` with your [Postgres database connection URI](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING-URIS).

You can also provide the URI through a mounted file by setting `DATABASE_URI_FILE`.
If both `DATABASE_URI` and `DATABASE_URI_FILE` are set, `DATABASE_URI` takes precedence.

For example, with Docker:

```bash
docker run -i --rm \
-v /path/to/database-uri:/run/secrets/database-uri:ro \
-e DATABASE_URI_FILE=/run/secrets/database-uri \
crystaldba/postgres-mcp --access-mode=unrestricted
```

##### Access Mode

Expand Down
20 changes: 17 additions & 3 deletions src/postgres_mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,19 @@ class AccessMode(str, Enum):
shutdown_in_progress = False


def get_database_url(database_url_arg: str | None) -> str | None:
"""Resolve the database URL from env, file-mounted secret, or CLI."""
if "DATABASE_URI" in os.environ:
return os.environ["DATABASE_URI"]

database_uri_file_path = os.environ.get("DATABASE_URI_FILE")
if database_uri_file_path:
with open(database_uri_file_path, encoding="utf-8") as database_uri_file:
return database_uri_file.read().strip()

return database_url_arg


async def get_sql_driver() -> Union[SqlDriver, SafeSqlDriver]:
"""Get the appropriate SQL driver based on the current access mode."""
base_driver = SqlDriver(conn=db_connection)
Expand Down Expand Up @@ -625,12 +638,13 @@ 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 from environment variable, file-mounted secret, or command line
database_url = get_database_url(args.database_url)

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 URL provided. Please specify via 'DATABASE_URI' environment variable, "
"'DATABASE_URI_FILE' file path, or command-line argument.",
)

# Initialize database connection pool
Expand Down
56 changes: 56 additions & 0 deletions tests/unit/test_transport.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import sys
from unittest.mock import AsyncMock
from unittest.mock import patch
Expand Down Expand Up @@ -127,3 +128,58 @@ async def test_default_transport_is_stdio():
mock_http.assert_not_called()
finally:
sys.argv = original_argv


@pytest.mark.asyncio
async def test_database_uri_file_is_used_when_database_uri_is_not_set(tmp_path):
"""Test that DATABASE_URI_FILE is read by the application."""
from postgres_mcp.server import main

database_uri_file = tmp_path / "database-uri"
database_uri_file.write_text("postgresql://file_user:password@localhost/file_db\n", encoding="utf-8")

original_argv = sys.argv
try:
sys.argv = ["postgres_mcp"]

with (
patch.dict(os.environ, {"DATABASE_URI_FILE": str(database_uri_file)}, clear=True),
patch("postgres_mcp.server.db_connection.pool_connect", AsyncMock()) as mock_pool_connect,
patch("postgres_mcp.server.mcp.run_stdio_async", AsyncMock()),
):
await main()

mock_pool_connect.assert_called_once_with("postgresql://file_user:password@localhost/file_db")
finally:
sys.argv = original_argv


@pytest.mark.asyncio
async def test_database_uri_takes_precedence_over_database_uri_file(tmp_path):
"""Test that DATABASE_URI keeps its existing precedence."""
from postgres_mcp.server import main

database_uri_file = tmp_path / "database-uri"
database_uri_file.write_text("postgresql://file_user:password@localhost/file_db\n", encoding="utf-8")

original_argv = sys.argv
try:
sys.argv = ["postgres_mcp"]

with (
patch.dict(
os.environ,
{
"DATABASE_URI": "postgresql://env_user:password@localhost/env_db",
"DATABASE_URI_FILE": str(database_uri_file),
},
clear=True,
),
patch("postgres_mcp.server.db_connection.pool_connect", AsyncMock()) as mock_pool_connect,
patch("postgres_mcp.server.mcp.run_stdio_async", AsyncMock()),
):
await main()

mock_pool_connect.assert_called_once_with("postgresql://env_user:password@localhost/env_db")
finally:
sys.argv = original_argv