From 9963d149ee8fcf11f478619609b2b97efebe6c6e Mon Sep 17 00:00:00 2001 From: artarts36 Date: Sun, 13 Sep 2026 02:53:08 +0300 Subject: [PATCH 1/2] feat: add dsn from secret file --- README.md | 11 +++++++ src/postgres_mcp/server.py | 20 +++++++++++-- tests/unit/test_transport.py | 56 ++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d5005ed6..8d904d96 100644 --- a/README.md +++ b/README.md @@ -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_PATH`. +If both `DATABASE_URI` and `DATABASE_URI_PATH` 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_PATH=/run/secrets/database-uri \ + crystaldba/postgres-mcp --access-mode=unrestricted +``` ##### Access Mode diff --git a/src/postgres_mcp/server.py b/src/postgres_mcp/server.py index f3ba8f8b..85d1553f 100644 --- a/src/postgres_mcp/server.py +++ b/src/postgres_mcp/server.py @@ -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_path = os.environ.get("DATABASE_URI_PATH") + if database_uri_path: + with open(database_uri_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) @@ -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_PATH' file path, or command-line argument.", ) # Initialize database connection pool diff --git a/tests/unit/test_transport.py b/tests/unit/test_transport.py index 4197aceb..b3a51ac6 100644 --- a/tests/unit/test_transport.py +++ b/tests/unit/test_transport.py @@ -1,3 +1,4 @@ +import os import sys from unittest.mock import AsyncMock from unittest.mock import patch @@ -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_path_is_used_when_database_uri_is_not_set(tmp_path): + """Test that DATABASE_URI_PATH 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_PATH": 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_path(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_PATH": 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 From 6b6eaddba8ac81c1a3811eaa893d0797d7ec20a9 Mon Sep 17 00:00:00 2001 From: artarts36 Date: Sun, 13 Sep 2026 15:21:32 +0300 Subject: [PATCH 2/2] chore: rename var to DATABASE_URI_FILE --- README.md | 6 +++--- src/postgres_mcp/server.py | 8 ++++---- tests/unit/test_transport.py | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8d904d96..8375ab71 100644 --- a/README.md +++ b/README.md @@ -217,15 +217,15 @@ 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_PATH`. -If both `DATABASE_URI` and `DATABASE_URI_PATH` are set, `DATABASE_URI` takes precedence. +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_PATH=/run/secrets/database-uri \ + -e DATABASE_URI_FILE=/run/secrets/database-uri \ crystaldba/postgres-mcp --access-mode=unrestricted ``` diff --git a/src/postgres_mcp/server.py b/src/postgres_mcp/server.py index 85d1553f..63441916 100644 --- a/src/postgres_mcp/server.py +++ b/src/postgres_mcp/server.py @@ -64,9 +64,9 @@ def get_database_url(database_url_arg: str | None) -> str | None: if "DATABASE_URI" in os.environ: return os.environ["DATABASE_URI"] - database_uri_path = os.environ.get("DATABASE_URI_PATH") - if database_uri_path: - with open(database_uri_path, encoding="utf-8") as database_uri_file: + 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 @@ -644,7 +644,7 @@ async def main(): if not database_url: raise ValueError( "Error: No database URL provided. Please specify via 'DATABASE_URI' environment variable, " - "'DATABASE_URI_PATH' file path, or command-line argument.", + "'DATABASE_URI_FILE' file path, or command-line argument.", ) # Initialize database connection pool diff --git a/tests/unit/test_transport.py b/tests/unit/test_transport.py index b3a51ac6..a14f17e9 100644 --- a/tests/unit/test_transport.py +++ b/tests/unit/test_transport.py @@ -131,8 +131,8 @@ async def test_default_transport_is_stdio(): @pytest.mark.asyncio -async def test_database_uri_path_is_used_when_database_uri_is_not_set(tmp_path): - """Test that DATABASE_URI_PATH is read by the application.""" +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" @@ -143,7 +143,7 @@ async def test_database_uri_path_is_used_when_database_uri_is_not_set(tmp_path): sys.argv = ["postgres_mcp"] with ( - patch.dict(os.environ, {"DATABASE_URI_PATH": str(database_uri_file)}, clear=True), + 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()), ): @@ -155,7 +155,7 @@ async def test_database_uri_path_is_used_when_database_uri_is_not_set(tmp_path): @pytest.mark.asyncio -async def test_database_uri_takes_precedence_over_database_uri_path(tmp_path): +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 @@ -171,7 +171,7 @@ async def test_database_uri_takes_precedence_over_database_uri_path(tmp_path): os.environ, { "DATABASE_URI": "postgresql://env_user:password@localhost/env_db", - "DATABASE_URI_PATH": str(database_uri_file), + "DATABASE_URI_FILE": str(database_uri_file), }, clear=True, ),