Skip to content

Commit 5836277

Browse files
committed
README: Add recommendations to use a read-only database user
... to prevent agents from modifying the database content.
1 parent 94e5db2 commit 5836277

File tree

6 files changed

+31
-15
lines changed

6 files changed

+31
-15
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
standardizing on common naming conventions
66
- CLI: Added subcommand `cratedb-mcp serve` using Click, with
77
dedicated options `--transport` and `--port`
8+
- README: Added recommendations to use a read-only database user
9+
to prevent agents from modifying the database content
810

911
## v0.0.0 - 2025-05-16
1012
- Project: Established project layout

README.md

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,16 +122,6 @@ Relevant information is pulled from <https://cratedb.com/docs>, curated per
122122
<br>
123123
Tool names are: `get_cratedb_documentation_index`, `fetch_cratedb_docs`
124124

125-
### Security considerations
126-
127-
**By default, the application will access the database in read-only mode.**
128-
129-
We do not recommend letting LLM-based agents insert or modify data by itself.
130-
As such, only `SELECT` statements are permitted and forwarded to the database.
131-
All other operations will raise a `ValueError` exception, unless the
132-
`CRATEDB_MCP_PERMIT_ALL_STATEMENTS` environment variable is set to a
133-
truthy value. This is **not** recommended.
134-
135125
### Install
136126

137127
The configuration snippets for AI assistants are using the `uvx` launcher
@@ -174,6 +164,23 @@ in seconds.
174164
The `CRATEDB_MCP_DOCS_CACHE_TTL` environment variable (default: 3600) defines
175165
the cache lifetime for documentation resources in seconds.
176166

167+
### Security considerations
168+
169+
If you want to prevent agents from modifying data, i.e., permit `SELECT` statements
170+
only, it is recommended to [create a read-only database user by using "GRANT DQL"].
171+
```sql
172+
CREATE USER "read-only" WITH (password = 'YOUR_PASSWORD');
173+
GRANT DQL TO "read-only";
174+
```
175+
Then, include relevant access credentials in the cluster URL.
176+
```shell
177+
export CRATEDB_CLUSTER_URL="https://read-only:[email protected]:4200"
178+
```
179+
The MCP Server also prohibits non-SELECT statements on the application level.
180+
All other operations will raise a `PermissionError` exception, unless the
181+
`CRATEDB_MCP_PERMIT_ALL_STATEMENTS` environment variable is set to a
182+
truthy value.
183+
177184
### Operate
178185

179186
Start MCP server with `stdio` transport (default).
@@ -227,6 +234,7 @@ Version pinning is strongly recommended, especially if you use it as a library.
227234
[CrateDB]: https://cratedb.com/database
228235
[cratedb-about]: https://pypi.org/project/cratedb-about/
229236
[cratedb-outline.yaml]: https://github.com/crate/about/blob/v0.0.4/src/cratedb_about/outline/cratedb-outline.yaml
237+
[create a read-only database user by using "GRANT DQL"]: https://community.cratedb.com/t/create-read-only-database-user-by-using-grant-dql/2031
230238
[development documentation]: https://github.com/crate/cratedb-mcp/blob/main/DEVELOP.md
231239
[example questions]: https://github.com/crate/about/blob/v0.0.4/src/cratedb_about/query/model.py#L17-L44
232240
[examples folder]: https://github.com/crate/cratedb-mcp/tree/main/examples

cratedb_mcp/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def query_cratedb(query: str) -> list[dict]:
2727
)
2828
def query_sql(query: str):
2929
if not sql_is_permitted(query):
30-
raise ValueError("Only queries that have a SELECT statement are allowed.")
30+
raise PermissionError("Only queries that have a SELECT statement are allowed.")
3131
return query_cratedb(query)
3232

3333

examples/mcptools.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ set -euo pipefail
1414
# brew tap f/mcptools
1515
# brew install mcp uv
1616

17+
if test -z $(command -v mcptools); then
18+
echo mcptools not installed, skipping.
19+
echo "Skipped."
20+
exit 0
21+
fi
22+
1723
# Some systems do not provide the `mcpt` alias.
1824
alias mcpt=mcptools
1925

tests/test_examples.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# ruff: noqa: S603, S607
22
import subprocess
3-
from shutil import which
43

54
import pytest
65

76

8-
@pytest.mark.skipif(not which("mcptools"), reason="requires mcptools")
97
def test_mcptools():
108
proc = subprocess.run(["examples/mcptools.sh"], capture_output=True, timeout=15, check=True)
119
assert proc.returncode == 0
10+
if b"Skipped." in proc.stdout:
11+
raise pytest.skip("mcptools not installed")
1212
assert b"Ready." in proc.stdout

tests/test_mcp.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ def test_query_sql_permitted():
3838

3939

4040
def test_query_sql_forbidden_easy():
41-
with pytest.raises(ValueError) as ex:
41+
with pytest.raises(PermissionError) as ex:
4242
assert "RelationUnknown" in str(
4343
query_sql("INSERT INTO foobar (id) VALUES (42) RETURNING id")
4444
)
4545
assert ex.match("Only queries that have a SELECT statement are allowed")
4646

4747

4848
def test_query_sql_forbidden_sneak_value():
49-
with pytest.raises(ValueError) as ex:
49+
with pytest.raises(PermissionError) as ex:
5050
query_sql("INSERT INTO foobar (operation) VALUES ('select')")
5151
assert ex.match("Only queries that have a SELECT statement are allowed")
5252

0 commit comments

Comments
 (0)