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.
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/AGENTS.md +5 -3
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/CHANGELOG.md +24 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/PKG-INFO +41 -17
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/README.md +39 -16
- mcp_read_only_sql-0.2.2/RELEASING.md +76 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/connections.yaml.sample +20 -26
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/pyproject.toml +5 -1
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/__init__.py +0 -1
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/config/connection.py +5 -20
- {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
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/config/loader.py +7 -5
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/connections.yaml.sample +20 -26
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/connectors/base.py +54 -58
- {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
- mcp_read_only_sql-0.2.2/src/mcp_read_only_sql/connectors/clickhouse/cli.py +290 -0
- mcp_read_only_sql-0.2.2/src/mcp_read_only_sql/connectors/clickhouse/python.py +332 -0
- mcp_read_only_sql-0.2.2/src/mcp_read_only_sql/connectors/postgresql/cli.py +250 -0
- mcp_read_only_sql-0.2.2/src/mcp_read_only_sql/connectors/postgresql/python.py +234 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/runtime_paths.py +17 -4
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/server.py +68 -30
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {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
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/README.md +4 -5
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/conftest.py +25 -5
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/conftest_new.py +2 -2
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/docker_test_config.py +22 -8
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/pytest_plugins.py +1 -1
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/sql_statement_lists.py +1 -1
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_cli_ssh_tunnels.py +117 -78
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_cli_system_ssh.py +79 -57
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_clickhouse_cli_fallback.py +7 -2
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_config_connection.py +46 -16
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_connection_utils.py +17 -31
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_connector_implementations.py +55 -39
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_docker_connectivity.py +35 -25
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_error_handling.py +121 -88
- mcp_read_only_sql-0.2.2/tests/test_host_mapping_flow.py +76 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_limits.py +45 -100
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_mcp_protocol.py +4 -11
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_mcp_server.py +2 -3
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_postgresql_cli_fallback.py +3 -2
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_process_cleanup.py +24 -21
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_result_serialization.py +99 -81
- mcp_read_only_sql-0.2.2/tests/test_run_query_file_output.py +159 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_runtime_paths.py +18 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_security_layers.py +38 -23
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_security_readonly.py +71 -35
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_security_readonly_integration.py +8 -3
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_serialization_fallback.py +8 -6
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_server_selection.py +183 -143
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_ssh_timeout.py +54 -45
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_ssh_tunnels.py +159 -88
- mcp_read_only_sql-0.1.1/RELEASING.md +0 -109
- mcp_read_only_sql-0.1.1/src/mcp_read_only_sql/connectors/clickhouse/cli.py +0 -218
- mcp_read_only_sql-0.1.1/src/mcp_read_only_sql/connectors/clickhouse/python.py +0 -246
- mcp_read_only_sql-0.1.1/src/mcp_read_only_sql/connectors/postgresql/cli.py +0 -197
- mcp_read_only_sql-0.1.1/src/mcp_read_only_sql/connectors/postgresql/python.py +0 -163
- mcp_read_only_sql-0.1.1/tests/test_host_mapping_flow.py +0 -70
- mcp_read_only_sql-0.1.1/tests/test_run_query_file_output.py +0 -174
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/.github/workflows/publish.yml +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/.github/workflows/test.yml +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/.gitignore +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/.mcp.json +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/LICENSE +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/READ_ONLY_ENFORCEMENT_MATRIX.md +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/conftest.py +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/docker/clickhouse/init/01_init.sql +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/docker/postgres/init/01_schema.sql +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/docker/postgres/init/02_data.sql +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/docker/ssh/Dockerfile +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/docker-compose.yml +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/justfile +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/pytest.ini +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/run_tests.sh +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/config/__init__.py +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/config/parser.py +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/connectors/__init__.py +0 -0
- {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
- {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
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/tools/__init__.py +0 -0
- {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
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/utils/__init__.py +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/KNOWN_ISSUES.md +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/__init__.py +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/connections-test.yaml +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_cli_versions.py +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_config_parser.py +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_dbeaver_import.py +0 -0
- {mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/tests/test_docker_test_config.py +0 -0
- {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
|
|
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
|
|
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.
|
|
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
|
|
52
|
-
3. **
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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:**
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
19
|
-
3. **
|
|
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
|
|
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,
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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:**
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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
|
|
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
|
|
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
|
|
129
|
-
query_timeout: 2 # Max 2 seconds per query
|
|
130
|
-
connection_timeout: 2 # Max 2 seconds to connect
|
|
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
|
|
144
|
-
connection_timeout: 10 # 10 seconds to connect
|
|
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
|
|
157
|
-
connection_timeout: 1 # Quick connect only
|
|
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
|
|
204
|
-
query_timeout: 30 # 30 seconds
|
|
205
|
-
connection_timeout: 10 # 10 seconds to connect
|
|
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.
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
#
|
|
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
|
-
#
|
|
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.
|
|
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"]
|
{mcp_read_only_sql-0.1.1 → mcp_read_only_sql-0.2.2}/src/mcp_read_only_sql/config/connection.py
RENAMED
|
@@ -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)"""
|