mcp-read-only-sql 0.1.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/AGENTS.md +5 -3
  2. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/CHANGELOG.md +24 -0
  3. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/PKG-INFO +41 -17
  4. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/README.md +39 -16
  5. mcp_read_only_sql-0.2.2/RELEASING.md +76 -0
  6. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/connections.yaml.sample +20 -26
  7. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/pyproject.toml +5 -1
  8. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/__init__.py +0 -1
  9. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/config/connection.py +5 -20
  10. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/config/dbeaver_import.py +54 -31
  11. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/config/loader.py +7 -5
  12. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/connections.yaml.sample +20 -26
  13. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/connectors/base.py +54 -58
  14. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/connectors/base_cli.py +3 -1
  15. mcp_read_only_sql-0.2.2/src/mcp_read_only_sql/connectors/clickhouse/cli.py +290 -0
  16. mcp_read_only_sql-0.2.2/src/mcp_read_only_sql/connectors/clickhouse/python.py +332 -0
  17. mcp_read_only_sql-0.2.2/src/mcp_read_only_sql/connectors/postgresql/cli.py +250 -0
  18. mcp_read_only_sql-0.2.2/src/mcp_read_only_sql/connectors/postgresql/python.py +234 -0
  19. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/runtime_paths.py +17 -4
  20. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/server.py +68 -30
  21. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/tools/test_ssh_tunnel.py +2 -0
  22. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/tools/validate_config.py +0 -11
  23. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/utils/connection_utils.py +4 -3
  24. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/utils/json_serializer.py +3 -5
  25. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/utils/sql_guard.py +12 -4
  26. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/utils/ssh_tunnel.py +39 -18
  27. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/utils/ssh_tunnel_cli.py +31 -19
  28. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/utils/timeout_wrapper.py +35 -17
  29. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/utils/tsv_formatter.py +14 -3
  30. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/README.md +4 -5
  31. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/conftest.py +25 -5
  32. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/conftest_new.py +2 -2
  33. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/docker_test_config.py +22 -8
  34. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/pytest_plugins.py +1 -1
  35. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/sql_statement_lists.py +1 -1
  36. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_cli_ssh_tunnels.py +117 -78
  37. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_cli_system_ssh.py +79 -57
  38. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_clickhouse_cli_fallback.py +7 -2
  39. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_config_connection.py +46 -16
  40. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_connection_utils.py +17 -31
  41. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_connector_implementations.py +55 -39
  42. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_docker_connectivity.py +35 -25
  43. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_error_handling.py +121 -88
  44. mcp_read_only_sql-0.2.2/tests/test_host_mapping_flow.py +76 -0
  45. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_limits.py +45 -100
  46. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_mcp_protocol.py +4 -11
  47. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_mcp_server.py +2 -3
  48. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_postgresql_cli_fallback.py +3 -2
  49. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_process_cleanup.py +24 -21
  50. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_result_serialization.py +99 -81
  51. mcp_read_only_sql-0.2.2/tests/test_run_query_file_output.py +159 -0
  52. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_runtime_paths.py +18 -0
  53. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_security_layers.py +38 -23
  54. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_security_readonly.py +71 -35
  55. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_security_readonly_integration.py +8 -3
  56. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_serialization_fallback.py +8 -6
  57. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_server_selection.py +183 -143
  58. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_ssh_timeout.py +54 -45
  59. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_ssh_tunnels.py +159 -88
  60. mcp_read_only_sql-0.1.1/RELEASING.md +0 -109
  61. mcp_read_only_sql-0.1.1/src/mcp_read_only_sql/connectors/clickhouse/cli.py +0 -218
  62. mcp_read_only_sql-0.1.1/src/mcp_read_only_sql/connectors/clickhouse/python.py +0 -246
  63. mcp_read_only_sql-0.1.1/src/mcp_read_only_sql/connectors/postgresql/cli.py +0 -197
  64. mcp_read_only_sql-0.1.1/src/mcp_read_only_sql/connectors/postgresql/python.py +0 -163
  65. mcp_read_only_sql-0.1.1/tests/test_host_mapping_flow.py +0 -70
  66. mcp_read_only_sql-0.1.1/tests/test_run_query_file_output.py +0 -174
  67. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/.github/workflows/publish.yml +0 -0
  68. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/.github/workflows/test.yml +0 -0
  69. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/.gitignore +0 -0
  70. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/.mcp.json +0 -0
  71. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/LICENSE +0 -0
  72. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/READ_ONLY_ENFORCEMENT_MATRIX.md +0 -0
  73. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/conftest.py +0 -0
  74. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/docker/clickhouse/init/01_init.sql +0 -0
  75. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/docker/postgres/init/01_schema.sql +0 -0
  76. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/docker/postgres/init/02_data.sql +0 -0
  77. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/docker/ssh/Dockerfile +0 -0
  78. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/docker-compose.yml +0 -0
  79. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/justfile +0 -0
  80. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/pytest.ini +0 -0
  81. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/run_tests.sh +0 -0
  82. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/config/__init__.py +0 -0
  83. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/config/parser.py +0 -0
  84. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/connectors/__init__.py +0 -0
  85. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/connectors/clickhouse/__init__.py +0 -0
  86. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/connectors/postgresql/__init__.py +0 -0
  87. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/tools/__init__.py +0 -0
  88. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/tools/test_connection.py +0 -0
  89. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/utils/__init__.py +0 -0
  90. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/KNOWN_ISSUES.md +0 -0
  91. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/__init__.py +0 -0
  92. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/connections-test.yaml +0 -0
  93. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_cli_versions.py +0 -0
  94. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_config_parser.py +0 -0
  95. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_dbeaver_import.py +0 -0
  96. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_docker_test_config.py +0 -0
  97. {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_server.py +0 -0
@@ -1,7 +1,7 @@
1
1
  # Repository Guidelines
2
2
 
3
3
  ## Project Structure & Module Organization
4
- `src/mcp_read_only_sql/server.py` exposes the MCP entry point, while `src/mcp_read_only_sql/connectors/` hosts the PostgreSQL and ClickHouse adapters. Cross-cutting helpers (`ssh_tunnel.py`, `sql_guard.py`, TSV formatting) live in `src/mcp_read_only_sql/utils/`. Configuration tooling, including DBeaver import and manifest validation, sits in `src/mcp_read_only_sql/config/` and `src/mcp_read_only_sql/tools/`. Tests are grouped in `tests/`, and Docker fixtures for integration runs reside in `docker/`. The package ships a sample `connections.yaml`, and runtime config lives under `~/.config/lukleh/mcp-read-only-sql/`.
4
+ `src/mcp_read_only_sql/server.py` exposes the MCP entry point, while `src/mcp_read_only_sql/connectors/` hosts the PostgreSQL and ClickHouse adapters for both CLI and Python implementations. Cross-cutting helpers (`ssh_tunnel.py`, `ssh_tunnel_cli.py`, `sql_guard.py`, TSV formatting, timeout handling) live in `src/mcp_read_only_sql/utils/`. Configuration tooling, including DBeaver import and manifest validation, sits in `src/mcp_read_only_sql/config/` and `src/mcp_read_only_sql/tools/`. Tests are grouped in `tests/`, and Docker fixtures for integration runs reside in `docker/`. The package ships a sample `connections.yaml`, and runtime config lives under `~/.config/lukleh/mcp-read-only-sql/`.
5
5
 
6
6
  ## Build, Test, and Development Commands
7
7
  - `uv sync --extra dev` — install runtime and development dependencies.
@@ -11,9 +11,11 @@
11
11
  - `just validate` — lint `connections.yaml` against the schema and safety checks.
12
12
  - `just test` — spin up Dockerized fixtures and execute the full pytest suite via `./run_tests.sh`.
13
13
  - `uv run python -m pytest tests/test_sql_guard.py` — run an individual module when iterating quickly.
14
+ - `uv run ruff check .` and `uv run black .` — run linting and formatting for the Python tree.
15
+ - `uv run ty check` — type-check the full `src/` tree; there are no remaining package excludes.
14
16
 
15
17
  ## Coding Style & Naming Conventions
16
- Target Python 3.11+, four-space indentation, and Unix newlines. Format with `uv run black .` and lint via `uv run ruff check .`; CI mirrors these checks. Modules and callables use snake_case, classes PascalCase (e.g., `TestReadOnlyGuards`), and immutable settings uppercase (`DEFAULT_QUERY_TIMEOUT`). Apply type hints on public surfaces and keep docstrings brief, emphasising read-only guarantees.
18
+ Target Python 3.11+, four-space indentation, and Unix newlines. Format with `uv run black .`, lint via `uv run ruff check .`, and keep `uv run ty check` passing for `src/`. Modules and callables use snake_case, classes PascalCase (e.g., `TestReadOnlyGuards`), and immutable settings uppercase (`DEFAULT_QUERY_TIMEOUT`). Apply type hints on public surfaces and keep docstrings brief, emphasizing read-only guarantees and connector behavior.
17
19
 
18
20
  ## Testing Guidelines
19
21
  Pytest discovers `test_*.py` modules, `Test*` classes, and `test_*` functions per `pytest.ini`. Use the built-in markers (`security`, `cli`, `python`, `ssh`, `slow`) to scope runs, e.g. `pytest -m "security and not slow"`. A 30s default timeout applies, so tear down tunnels and subprocesses explicitly. `just test` emits JUnit XML at `test-results/pytest.xml` for CI uploads.
@@ -22,4 +24,4 @@ Pytest discovers `test_*.py` modules, `Test*` classes, and `test_*` functions pe
22
24
  Recent commits favour imperative, concise subjects (“Refactor PostgreSQL read-only guard into shared utility”). Keep changes focused and explain security or connector impacts in the body when needed. Pull requests should describe motivation, list affected modules or scripts, link issues, and attach logs or screenshots for operational updates.
23
25
 
24
26
  ## Security & Configuration Tips
25
- Do not relax safeguards in `src/mcp_read_only_sql/utils/sql_guard.py` or connector factories without matching tests. Treat `connections.yaml` as sensitive; the server stores database and SSH passwords directly in that file, so keep it private and user-readable only. Use the SSH tunnel helpers instead of bespoke subprocesses to preserve timeout and read-only enforcement.
27
+ Do not relax safeguards in `src/mcp_read_only_sql/utils/sql_guard.py` or connector factories without matching tests. Treat `connections.yaml` as sensitive; the server stores database and SSH passwords directly in that file, so keep it private and user-readable only. Use the shared SSH tunnel and timeout helpers instead of bespoke subprocesses so read-only enforcement, cleanup, and managed result-file behavior stay consistent.
@@ -7,6 +7,24 @@ and this project aims to follow [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2026-04-03
11
+
12
+ ### Added
13
+
14
+ - Added `ty` as a supported development check for the full packaged `src/` tree.
15
+
16
+ ### Changed
17
+
18
+ - Added repo-specific `AGENTS.md` guidance covering connector layout, shared timeout and SSH helpers, and the typed development workflow.
19
+ - Reworked `RELEASING.md` into an evergreen release checklist with explicit validation, tagging, and publish steps.
20
+
21
+ ### Fixed
22
+
23
+ - Flushed the final buffered TSV line when PostgreSQL and ClickHouse CLI queries stream results to an output file.
24
+ - Hardened DBeaver credential import so missing or non-dictionary decrypted sections are ignored cleanly instead of being treated as valid connection data.
25
+
26
+ ## [0.2.1] - 2026-04-02
27
+
10
28
  ### Added
11
29
 
12
30
  - Root `CHANGELOG.md` using the Keep a Changelog format and seeded package history.
@@ -15,6 +33,12 @@ and this project aims to follow [Semantic Versioning](https://semver.org/).
15
33
 
16
34
  - `project.urls.Changelog` now points to the in-repo changelog instead of the generic GitHub releases page.
17
35
  - The release flow now treats changelog maintenance as a required step and reuses changelog sections for GitHub release notes.
36
+ - Breaking: `run_query_read_only` now always writes successful query results under the managed state directory and returns the TSV file path instead of inline query output.
37
+ - Breaking: removed the `file_path` tool parameter and `max_result_bytes` configuration/result-size limit behavior.
38
+
39
+ ### Fixed
40
+
41
+ - Restored Python connector executor compatibility so non-file query execution no longer passes unexpected positional arguments to synchronous workers or test stubs after the managed result-file refactor.
18
42
 
19
43
  ## [0.1.0] - 2026-03-29
20
44
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-read-only-sql
3
- Version: 0.1.1
3
+ Version: 0.2.2
4
4
  Summary: MCP server for read-only SQL queries supporting PostgreSQL and ClickHouse
5
5
  Project-URL: Homepage, https://github.com/lukleh/mcp-read-only-sql
6
6
  Project-URL: Repository, https://github.com/lukleh/mcp-read-only-sql
@@ -29,6 +29,7 @@ Requires-Dist: black>=23.0.0; extra == 'dev'
29
29
  Requires-Dist: pytest-timeout>=2.2.0; extra == 'dev'
30
30
  Requires-Dist: pytest>=7.0.0; extra == 'dev'
31
31
  Requires-Dist: ruff>=0.1.0; extra == 'dev'
32
+ Requires-Dist: ty>=0.0.28; extra == 'dev'
32
33
  Description-Content-Type: text/markdown
33
34
 
34
35
  # MCP Read-Only SQL Server
@@ -41,6 +42,7 @@ A secure MCP (Model Context Protocol) server that provides **read-only** SQL acc
41
42
  > - Config: `~/.config/lukleh/mcp-read-only-sql/connections.yaml`
42
43
  > - Credentials: stored in `connections.yaml`
43
44
  > - State: `~/.local/state/lukleh/mcp-read-only-sql/`
45
+ > - Query results: `~/.local/state/lukleh/mcp-read-only-sql/results/`
44
46
  > - Cache: `~/.cache/lukleh/mcp-read-only-sql/`
45
47
 
46
48
  ## Security
@@ -48,8 +50,8 @@ A secure MCP (Model Context Protocol) server that provides **read-only** SQL acc
48
50
  The server implements a **three-layer security model**:
49
51
 
50
52
  1. **Database-level read-only** - Sessions forced to read-only mode
51
- 2. **Timeout protection** - Connection timeout (5s), query timeout (10s) - configurable per connection
52
- 3. **Result size limits** - Default 5KB, prevents memory exhaustion
53
+ 2. **Timeout protection** - Connection and query timeouts are configurable per connection
54
+ 3. **Managed result files** - Successful query results are written to `state_dir/results` with `0600` permissions
53
55
 
54
56
  All write operations (INSERT, UPDATE, DELETE, etc.) are blocked at the database level.
55
57
 
@@ -60,7 +62,7 @@ All write operations (INSERT, UPDATE, DELETE, etc.) are blocked at the database
60
62
  - **ClickHouse (Python)** – The driver sets `readonly=1` plus connection/query timeouts, forcing the server to reject any write or DDL attempt.
61
63
  - **ClickHouse (CLI)** – `clickhouse-client` is invoked with `--readonly=1`, `--max_execution_time`, and connection timeouts, turning the session into a read-only context.
62
64
 
63
- The shared connector base also applies hard timeouts and result-size ceilings, giving the MCP server deterministic behaviour even if the database misbehaves.
65
+ The shared connector base also applies hard timeouts, giving the MCP server deterministic behaviour even if the database misbehaves.
64
66
 
65
67
  See [READ_ONLY_ENFORCEMENT_MATRIX.md](READ_ONLY_ENFORCEMENT_MATRIX.md) for a statement-by-statement view of every write-capable command and the tests that enforce it.
66
68
 
@@ -70,7 +72,7 @@ See [READ_ONLY_ENFORCEMENT_MATRIX.md](READ_ONLY_ENFORCEMENT_MATRIX.md) for a sta
70
72
  - **Multi-database support** - PostgreSQL and ClickHouse
71
73
  - **Dual implementations** - Choose between Python (pure Python, no dependencies) or CLI (uses `psql`/`clickhouse-client`)
72
74
  - **SSH tunnel support** - Both implementations support key authentication; Python uses Paramiko for passwords and CLI uses `sshpass` for password-based tunnels
73
- - **Security built-in** - Timeouts, size limits, session controls
75
+ - **Security built-in** - Timeouts, managed result files, session controls
74
76
  - **DBeaver import** - Import existing connections easily
75
77
 
76
78
  ## Prerequisites
@@ -98,7 +100,25 @@ sshpass -V
98
100
 
99
101
  ## Quick Start
100
102
 
101
- ### 1. Install or Run from This Checkout
103
+ ### 1. Install or Run the Server
104
+
105
+ For the published package, prefer `@latest` with `uvx`:
106
+
107
+ ```bash
108
+ uvx mcp-read-only-sql@latest --write-sample-config
109
+ ```
110
+
111
+ Or install it once and reuse the command directly:
112
+
113
+ ```bash
114
+ uv tool install mcp-read-only-sql
115
+ mcp-read-only-sql --write-sample-config
116
+ ```
117
+
118
+ When using `uvx` with the published package, prefer
119
+ `mcp-read-only-sql@latest` in user-facing docs and MCP client configs. This
120
+ avoids reusing a stale cached tool environment after a new release is
121
+ published.
102
122
 
103
123
  For one-off runs from this checkout, use `uvx --from .`:
104
124
 
@@ -113,12 +133,14 @@ uv tool install .
113
133
  mcp-read-only-sql --write-sample-config
114
134
  ```
115
135
 
116
- After the package is published to PyPI, you can replace `.` with `mcp-read-only-sql`.
136
+ For checkout-based commands below, you can replace `uvx --from . mcp-read-only-sql`
137
+ with `uvx mcp-read-only-sql@latest` once you want to use the published package instead.
117
138
 
118
139
  That creates:
119
140
 
120
141
  - `~/.config/lukleh/mcp-read-only-sql/connections.yaml`
121
142
  - `~/.local/state/lukleh/mcp-read-only-sql/`
143
+ - `~/.local/state/lukleh/mcp-read-only-sql/results/`
122
144
  - `~/.cache/lukleh/mcp-read-only-sql/`
123
145
 
124
146
  ### 2. Choose an Implementation Per Connection
@@ -203,19 +225,19 @@ just print-paths
203
225
  For Claude Code:
204
226
 
205
227
  ```bash
206
- claude mcp add mcp-read-only-sql -- uvx --from . mcp-read-only-sql
228
+ claude mcp add mcp-read-only-sql -- uvx mcp-read-only-sql@latest
207
229
  ```
208
230
 
209
231
  For Codex:
210
232
 
211
233
  ```bash
212
- codex mcp add mcp-read-only-sql -- uvx --from . mcp-read-only-sql
234
+ codex mcp add mcp-read-only-sql -- uvx mcp-read-only-sql@latest
213
235
  ```
214
236
 
215
237
  For manual testing with a different config root:
216
238
 
217
239
  ```bash
218
- uvx --from . mcp-read-only-sql --config-dir /path/to/config-dir --print-paths
240
+ uvx mcp-read-only-sql@latest --config-dir /path/to/config-dir --print-paths
219
241
  ```
220
242
 
221
243
  ## MCP Tools
@@ -228,8 +250,7 @@ Execute read-only SQL queries on configured databases.
228
250
  "connection_name": "my_postgres",
229
251
  "query": "SELECT * FROM users LIMIT 10",
230
252
  "database": "analytics",
231
- "server": "db2.example.com",
232
- "file_path": "~/Downloads/query.tsv"
253
+ "server": "db2.example.com"
233
254
  }
234
255
  ```
235
256
 
@@ -238,12 +259,15 @@ Execute read-only SQL queries on configured databases.
238
259
  - `query` (required): SQL text that must remain read-only
239
260
  - `database` (optional): Database to use (must be listed in the connection's allowlist).
240
261
  - `server` (optional): Hostname to target a specific server. If not provided, uses the first server in the connection's list.
241
- - `file_path` (optional): When provided, results are written to this path (parents created if needed) and the tool returns the absolute path string instead of TSV content. The file must not already exist; if it does, the tool returns an error instead of overwriting. The result-size limit is skipped when saving to a file so the full output is streamed to disk.
242
262
 
243
- **Returns:** Tab-separated text (TSV) with a header row followed by data rows.
244
- The structured MCP payload mirrors the same TSV string. If results exceed
245
- `max_result_bytes`, a trailing notice indicates truncation. When `file_path`
246
- is supplied, the returned value is the absolute path of the written file, the tool refuses to overwrite existing files, and result-size truncation is not applied (full result is written).
263
+ **Returns:** Absolute path to a TSV file created under the server's managed
264
+ state directory, typically `~/.local/state/lukleh/mcp-read-only-sql/results/`.
265
+ Successful query results are persisted with `0600` permissions and are no
266
+ longer returned inline on success.
267
+
268
+ Result files accumulate under `state_dir/results/` until you remove them.
269
+ If you do not want to retain old query output, periodically clean
270
+ `~/.local/state/lukleh/mcp-read-only-sql/results/`.
247
271
 
248
272
  ### `list_connections`
249
273
  List all available database connections.
@@ -8,6 +8,7 @@ A secure MCP (Model Context Protocol) server that provides **read-only** SQL acc
8
8
  > - Config: `~/.config/lukleh/mcp-read-only-sql/connections.yaml`
9
9
  > - Credentials: stored in `connections.yaml`
10
10
  > - State: `~/.local/state/lukleh/mcp-read-only-sql/`
11
+ > - Query results: `~/.local/state/lukleh/mcp-read-only-sql/results/`
11
12
  > - Cache: `~/.cache/lukleh/mcp-read-only-sql/`
12
13
 
13
14
  ## Security
@@ -15,8 +16,8 @@ A secure MCP (Model Context Protocol) server that provides **read-only** SQL acc
15
16
  The server implements a **three-layer security model**:
16
17
 
17
18
  1. **Database-level read-only** - Sessions forced to read-only mode
18
- 2. **Timeout protection** - Connection timeout (5s), query timeout (10s) - configurable per connection
19
- 3. **Result size limits** - Default 5KB, prevents memory exhaustion
19
+ 2. **Timeout protection** - Connection and query timeouts are configurable per connection
20
+ 3. **Managed result files** - Successful query results are written to `state_dir/results` with `0600` permissions
20
21
 
21
22
  All write operations (INSERT, UPDATE, DELETE, etc.) are blocked at the database level.
22
23
 
@@ -27,7 +28,7 @@ All write operations (INSERT, UPDATE, DELETE, etc.) are blocked at the database
27
28
  - **ClickHouse (Python)** – The driver sets `readonly=1` plus connection/query timeouts, forcing the server to reject any write or DDL attempt.
28
29
  - **ClickHouse (CLI)** – `clickhouse-client` is invoked with `--readonly=1`, `--max_execution_time`, and connection timeouts, turning the session into a read-only context.
29
30
 
30
- The shared connector base also applies hard timeouts and result-size ceilings, giving the MCP server deterministic behaviour even if the database misbehaves.
31
+ The shared connector base also applies hard timeouts, giving the MCP server deterministic behaviour even if the database misbehaves.
31
32
 
32
33
  See [READ_ONLY_ENFORCEMENT_MATRIX.md](READ_ONLY_ENFORCEMENT_MATRIX.md) for a statement-by-statement view of every write-capable command and the tests that enforce it.
33
34
 
@@ -37,7 +38,7 @@ See [READ_ONLY_ENFORCEMENT_MATRIX.md](READ_ONLY_ENFORCEMENT_MATRIX.md) for a sta
37
38
  - **Multi-database support** - PostgreSQL and ClickHouse
38
39
  - **Dual implementations** - Choose between Python (pure Python, no dependencies) or CLI (uses `psql`/`clickhouse-client`)
39
40
  - **SSH tunnel support** - Both implementations support key authentication; Python uses Paramiko for passwords and CLI uses `sshpass` for password-based tunnels
40
- - **Security built-in** - Timeouts, size limits, session controls
41
+ - **Security built-in** - Timeouts, managed result files, session controls
41
42
  - **DBeaver import** - Import existing connections easily
42
43
 
43
44
  ## Prerequisites
@@ -65,7 +66,25 @@ sshpass -V
65
66
 
66
67
  ## Quick Start
67
68
 
68
- ### 1. Install or Run from This Checkout
69
+ ### 1. Install or Run the Server
70
+
71
+ For the published package, prefer `@latest` with `uvx`:
72
+
73
+ ```bash
74
+ uvx mcp-read-only-sql@latest --write-sample-config
75
+ ```
76
+
77
+ Or install it once and reuse the command directly:
78
+
79
+ ```bash
80
+ uv tool install mcp-read-only-sql
81
+ mcp-read-only-sql --write-sample-config
82
+ ```
83
+
84
+ When using `uvx` with the published package, prefer
85
+ `mcp-read-only-sql@latest` in user-facing docs and MCP client configs. This
86
+ avoids reusing a stale cached tool environment after a new release is
87
+ published.
69
88
 
70
89
  For one-off runs from this checkout, use `uvx --from .`:
71
90
 
@@ -80,12 +99,14 @@ uv tool install .
80
99
  mcp-read-only-sql --write-sample-config
81
100
  ```
82
101
 
83
- After the package is published to PyPI, you can replace `.` with `mcp-read-only-sql`.
102
+ For checkout-based commands below, you can replace `uvx --from . mcp-read-only-sql`
103
+ with `uvx mcp-read-only-sql@latest` once you want to use the published package instead.
84
104
 
85
105
  That creates:
86
106
 
87
107
  - `~/.config/lukleh/mcp-read-only-sql/connections.yaml`
88
108
  - `~/.local/state/lukleh/mcp-read-only-sql/`
109
+ - `~/.local/state/lukleh/mcp-read-only-sql/results/`
89
110
  - `~/.cache/lukleh/mcp-read-only-sql/`
90
111
 
91
112
  ### 2. Choose an Implementation Per Connection
@@ -170,19 +191,19 @@ just print-paths
170
191
  For Claude Code:
171
192
 
172
193
  ```bash
173
- claude mcp add mcp-read-only-sql -- uvx --from . mcp-read-only-sql
194
+ claude mcp add mcp-read-only-sql -- uvx mcp-read-only-sql@latest
174
195
  ```
175
196
 
176
197
  For Codex:
177
198
 
178
199
  ```bash
179
- codex mcp add mcp-read-only-sql -- uvx --from . mcp-read-only-sql
200
+ codex mcp add mcp-read-only-sql -- uvx mcp-read-only-sql@latest
180
201
  ```
181
202
 
182
203
  For manual testing with a different config root:
183
204
 
184
205
  ```bash
185
- uvx --from . mcp-read-only-sql --config-dir /path/to/config-dir --print-paths
206
+ uvx mcp-read-only-sql@latest --config-dir /path/to/config-dir --print-paths
186
207
  ```
187
208
 
188
209
  ## MCP Tools
@@ -195,8 +216,7 @@ Execute read-only SQL queries on configured databases.
195
216
  "connection_name": "my_postgres",
196
217
  "query": "SELECT * FROM users LIMIT 10",
197
218
  "database": "analytics",
198
- "server": "db2.example.com",
199
- "file_path": "~/Downloads/query.tsv"
219
+ "server": "db2.example.com"
200
220
  }
201
221
  ```
202
222
 
@@ -205,12 +225,15 @@ Execute read-only SQL queries on configured databases.
205
225
  - `query` (required): SQL text that must remain read-only
206
226
  - `database` (optional): Database to use (must be listed in the connection's allowlist).
207
227
  - `server` (optional): Hostname to target a specific server. If not provided, uses the first server in the connection's list.
208
- - `file_path` (optional): When provided, results are written to this path (parents created if needed) and the tool returns the absolute path string instead of TSV content. The file must not already exist; if it does, the tool returns an error instead of overwriting. The result-size limit is skipped when saving to a file so the full output is streamed to disk.
209
228
 
210
- **Returns:** Tab-separated text (TSV) with a header row followed by data rows.
211
- The structured MCP payload mirrors the same TSV string. If results exceed
212
- `max_result_bytes`, a trailing notice indicates truncation. When `file_path`
213
- is supplied, the returned value is the absolute path of the written file, the tool refuses to overwrite existing files, and result-size truncation is not applied (full result is written).
229
+ **Returns:** Absolute path to a TSV file created under the server's managed
230
+ state directory, typically `~/.local/state/lukleh/mcp-read-only-sql/results/`.
231
+ Successful query results are persisted with `0600` permissions and are no
232
+ longer returned inline on success.
233
+
234
+ Result files accumulate under `state_dir/results/` until you remove them.
235
+ If you do not want to retain old query output, periodically clean
236
+ `~/.local/state/lukleh/mcp-read-only-sql/results/`.
214
237
 
215
238
  ### `list_connections`
216
239
  List all available database connections.
@@ -0,0 +1,76 @@
1
+ # Releasing `mcp-read-only-sql`
2
+
3
+ This repository publishes to PyPI from Git tags through GitHub Actions.
4
+
5
+ Release automation lives in:
6
+ - `.github/workflows/publish.yml`
7
+ - `.github/workflows/test.yml`
8
+ - the GitHub environment named `pypi`
9
+ - the PyPI trusted publisher for `lukleh/mcp-read-only-sql`
10
+
11
+ ## What To Change For A Release
12
+
13
+ Update these files in the release commit:
14
+
15
+ 1. `CHANGELOG.md`
16
+ Move the user-visible items from `## [Unreleased]` into a new section:
17
+ `## [X.Y.Z] - YYYY-MM-DD`
18
+ 2. `pyproject.toml`
19
+ Update `[project].version` to `X.Y.Z`
20
+
21
+ Do not expect a tracked `uv.lock` change in this repository. `uv.lock` is
22
+ gitignored here, so it is not part of the release diff.
23
+
24
+ `RELEASING.md` should stay evergreen. It should explain the process, not carry a
25
+ release-specific version number.
26
+
27
+ ## How To Update The Version
28
+
29
+ 1. Edit `pyproject.toml`
30
+ 2. Refresh the local environment if needed:
31
+
32
+ ```bash
33
+ uv sync --extra dev
34
+ ```
35
+
36
+ 3. Confirm the installed package metadata and CLI version output match:
37
+
38
+ ```bash
39
+ uv run --extra dev pytest tests/test_server.py tests/test_cli_versions.py -q -k 'package_version_matches_distribution_metadata or support_version'
40
+ ```
41
+
42
+ ## Pre-Release Validation
43
+
44
+ Run the normal local checks before tagging:
45
+
46
+ ```bash
47
+ uv run --extra dev ruff check .
48
+ uv run --extra dev ty check
49
+ ./run_tests.sh
50
+ ```
51
+
52
+ If you are iterating on release metadata only, the focused version tests above
53
+ are the minimum sanity check.
54
+
55
+ ## How To Publish
56
+
57
+ 1. Make the release commit on `main`
58
+ 2. Create and push the matching tag:
59
+
60
+ ```bash
61
+ git tag vX.Y.Z
62
+ git push origin main
63
+ git push origin vX.Y.Z
64
+ ```
65
+
66
+ 3. GitHub Actions starts `.github/workflows/publish.yml`
67
+ 4. The workflow runs the test matrix, builds the wheel and sdist, and smoke-tests the built artifacts
68
+ 5. The final publish job pauses on the GitHub `pypi` environment
69
+ 6. Approve the deployment in GitHub Actions
70
+ 7. GitHub publishes the package to PyPI
71
+
72
+ ## Notes
73
+
74
+ - Keep both `implementation: cli` and `implementation: python` clearly supported in release notes and docs
75
+ - `uvx` installs the Python package only; it does not install `psql`, `clickhouse-client`, or `sshpass`
76
+ - If the public CLI or result-file behavior changes, keep the package smoke tests and README aligned with `mcp-read-only-sql`
@@ -1,6 +1,6 @@
1
1
  # MCP Read-Only SQL Server - Connection Configuration Sample
2
2
  # This file demonstrates all connection variations and options.
3
- # `uvx --from . mcp-read-only-sql --write-sample-config` writes it to
3
+ # `uvx mcp-read-only-sql@latest --write-sample-config` writes it to
4
4
  # ~/.config/lukleh/mcp-read-only-sql/connections.yaml
5
5
 
6
6
  # ============================================================================
@@ -115,7 +115,7 @@
115
115
  # SECURITY-LIMITED CONNECTIONS
116
116
  # ============================================================================
117
117
 
118
- # Strict security limits for sensitive data
118
+ # Strict security defaults for sensitive data
119
119
  - connection_name: restricted_postgres
120
120
  description: Highly restricted connection for sensitive data
121
121
  type: postgresql
@@ -125,10 +125,9 @@
125
125
  db: sensitive_data # Connect to specific database
126
126
  username: auditor
127
127
  password: change_me
128
- # Strict security limits (stricter than defaults)
129
- query_timeout: 2 # Max 2 seconds per query (default: 10)
130
- connection_timeout: 2 # Max 2 seconds to connect (default: 5)
131
- max_result_bytes: 1024 # Max 1KB results (default: 5KB)
128
+ # Strict timeouts for sensitive data
129
+ query_timeout: 2 # Max 2 seconds per query
130
+ connection_timeout: 2 # Max 2 seconds to connect
132
131
 
133
132
  # Moderate limits for general use
134
133
  - connection_name: limited_analytics
@@ -140,9 +139,8 @@
140
139
  db: analytics
141
140
  username: analyst
142
141
  password: change_me
143
- query_timeout: 60 # 1 minute for complex queries (default: 10)
144
- connection_timeout: 10 # 10 seconds to connect (default: 5)
145
- max_result_bytes: 20971520 # 20MB for larger datasets (default: 5KB)
142
+ query_timeout: 60 # 1 minute for complex queries
143
+ connection_timeout: 10 # 10 seconds to connect
146
144
 
147
145
  # Very strict limits for public/untrusted queries
148
146
  - connection_name: public_readonly
@@ -153,9 +151,8 @@
153
151
  db: public_data
154
152
  username: public_reader
155
153
  password: change_me
156
- query_timeout: 1 # 1 second max (default: 10)
157
- connection_timeout: 1 # Quick connect only (default: 5)
158
- max_result_bytes: 512 # 512 bytes max (default: 5KB)
154
+ query_timeout: 1 # 1 second max
155
+ connection_timeout: 1 # Quick connect only
159
156
 
160
157
  # ============================================================================
161
158
  # CLI IMPLEMENTATION CONNECTIONS
@@ -200,10 +197,9 @@
200
197
  db: production # For multiple databases, use allowed_databases + default_database
201
198
  username: power_user
202
199
  password: change_me
203
- # Security limits (reasonable for production)
204
- query_timeout: 30 # 30 seconds (default: 10)
205
- connection_timeout: 10 # 10 seconds to connect (default: 5)
206
- max_result_bytes: 10485760 # 10MB (default: 5KB)
200
+ # Security timeouts (reasonable for production)
201
+ query_timeout: 30 # 30 seconds
202
+ connection_timeout: 10 # 10 seconds to connect
207
203
  # SSH tunnel
208
204
  ssh_tunnel:
209
205
  host: bastion.example.com
@@ -242,15 +238,13 @@
242
238
  # ============================================================================
243
239
  # 1. Default implementation is 'cli' if not specified
244
240
  # 2. Default ports: PostgreSQL=5432, ClickHouse=9000 (native protocol)
245
- # 3. Default security limits:
246
- # - query_timeout: 10 seconds
247
- # - connection_timeout: 5 seconds
248
- # - max_result_bytes: 5120 bytes (5KB)
249
- # 4. Use 'db' for a single allowed database, or 'allowed_databases' + 'default_database' for multiple
250
- # 5. Multiple servers: Currently always uses first server (no load balancing/failover)
251
- # 6. Passwords live directly in this YAML file
252
- # 7. All queries are forced to be read-only at multiple levels
253
- # 8. CLI implementations require the respective tools installed:
241
+ # 3. Query and connection timeouts are configurable per connection
242
+ # 4. Successful query results are written to ~/.local/state/lukleh/mcp-read-only-sql/results/
243
+ # 5. Use 'db' for a single allowed database, or 'allowed_databases' + 'default_database' for multiple
244
+ # 6. Multiple servers: Currently always uses first server (no load balancing/failover)
245
+ # 7. Passwords live directly in this YAML file
246
+ # 8. All queries are forced to be read-only at multiple levels
247
+ # 9. CLI implementations require the respective tools installed:
254
248
  # - PostgreSQL: psql
255
249
  # - ClickHouse: clickhouse-client
256
- # 9. Never commit actual passwords from this file; keep permissions restricted
250
+ # 10. Never commit actual passwords from this file; keep permissions restricted
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "mcp-read-only-sql"
3
- version = "0.1.1"
3
+ version = "0.2.2"
4
4
  description = "MCP server for read-only SQL queries supporting PostgreSQL and ClickHouse"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
@@ -39,6 +39,7 @@ dev = [
39
39
  "pytest-timeout>=2.2.0",
40
40
  "black>=23.0.0",
41
41
  "ruff>=0.1.0",
42
+ "ty>=0.0.28",
42
43
  ]
43
44
 
44
45
  [build-system]
@@ -50,3 +51,6 @@ mcp-read-only-sql = "mcp_read_only_sql.server:main"
50
51
 
51
52
  [tool.hatch.build.targets.wheel]
52
53
  packages = ["src/mcp_read_only_sql"]
54
+
55
+ [tool.ty.src]
56
+ include = ["src"]
@@ -2,7 +2,6 @@
2
2
 
3
3
  from importlib.metadata import PackageNotFoundError, version
4
4
 
5
-
6
5
  try:
7
6
  __version__ = version("mcp-read-only-sql")
8
7
  except PackageNotFoundError:
@@ -9,7 +9,6 @@ DEFAULT_IMPLEMENTATION = "cli"
9
9
  DEFAULT_SSH_PORT = 22
10
10
  DEFAULT_QUERY_TIMEOUT = 120
11
11
  DEFAULT_CONNECTION_TIMEOUT = 10
12
- DEFAULT_MAX_RESULT_BYTES = 10_000_000
13
12
 
14
13
 
15
14
  def _normalize_positive_timeout(value: Any, field_name: str) -> float:
@@ -21,15 +20,6 @@ def _normalize_positive_timeout(value: Any, field_name: str) -> float:
21
20
  return value
22
21
 
23
22
 
24
- def _normalize_positive_int(value: Any, field_name: str) -> int:
25
- """Validate positive integer settings used at runtime."""
26
- if isinstance(value, bool) or not isinstance(value, int):
27
- raise ValueError(f"Field '{field_name}' must be a positive integer")
28
- if value <= 0:
29
- raise ValueError(f"Field '{field_name}' must be a positive integer")
30
- return value
31
-
32
-
33
23
  def _normalize_database_list(value: Any, field_name: str) -> List[str]:
34
24
  """Normalize a database list field to a deduplicated list of names."""
35
25
  if value is None:
@@ -119,7 +109,7 @@ class SSHTunnelConfig:
119
109
  raise ValueError("SSH tunnel timeout must be a positive integer")
120
110
 
121
111
  @classmethod
122
- def from_dict(cls, data: Dict[str, Any]) -> "SSHTunnelConfig":
112
+ def from_dict(cls, data: Dict[str, Any]) -> Optional["SSHTunnelConfig"]:
123
113
  """Create SSHTunnelConfig from dict with validation."""
124
114
  if not data.get("enabled", True):
125
115
  return None
@@ -221,6 +211,10 @@ class Connection:
221
211
  raise ValueError(
222
212
  "Field 'password_env' is no longer supported; put the database password in 'password'"
223
213
  )
214
+ if "max_result_bytes" in config:
215
+ raise ValueError(
216
+ "Field 'max_result_bytes' is no longer supported; successful query results are now written to managed files"
217
+ )
224
218
 
225
219
  password = config.get("password", "")
226
220
 
@@ -302,10 +296,6 @@ class Connection:
302
296
  config.get("connection_timeout", DEFAULT_CONNECTION_TIMEOUT),
303
297
  "connection_timeout",
304
298
  )
305
- self._max_result_bytes = _normalize_positive_int(
306
- config.get("max_result_bytes", DEFAULT_MAX_RESULT_BYTES),
307
- "max_result_bytes",
308
- )
309
299
  self._description = config.get("description", "")
310
300
 
311
301
  @property
@@ -378,11 +368,6 @@ class Connection:
378
368
  """Connection timeout in seconds"""
379
369
  return self._connection_timeout
380
370
 
381
- @property
382
- def max_result_bytes(self) -> Optional[int]:
383
- """Maximum result size in bytes (None for unlimited)"""
384
- return self._max_result_bytes
385
-
386
371
  @property
387
372
  def description(self) -> str:
388
373
  """Connection description (optional)"""