Skip to content

Commit 024b04f

Browse files
committed
MCP: Add software tests
1 parent 1a0d361 commit 024b04f

File tree

5 files changed

+209
-1
lines changed

5 files changed

+209
-1
lines changed

.github/workflows/framework-mcp.yml

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
name: mcp
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- '.github/workflows/framework-mcp.yml'
7+
- 'framework/mcp/**'
8+
- '/requirements.txt'
9+
push:
10+
branches: [ main ]
11+
paths:
12+
- '.github/workflows/framework-mcp.yml'
13+
- 'framework/mcp/**'
14+
- '/requirements.txt'
15+
16+
# Allow job to be triggered manually.
17+
workflow_dispatch:
18+
19+
# Run job each night after CrateDB nightly has been published.
20+
schedule:
21+
- cron: '0 3 * * *'
22+
23+
# Cancel in-progress jobs when pushing to the same branch.
24+
concurrency:
25+
cancel-in-progress: true
26+
group: ${{ github.workflow }}-${{ github.ref }}
27+
28+
jobs:
29+
test:
30+
name: "
31+
Python: ${{ matrix.python-version }}
32+
CrateDB: ${{ matrix.cratedb-version }}
33+
on ${{ matrix.os }}"
34+
runs-on: ${{ matrix.os }}
35+
strategy:
36+
fail-fast: false
37+
matrix:
38+
os: [ 'ubuntu-latest' ]
39+
python-version: [ '3.10', '3.13' ]
40+
cratedb-version: [ 'nightly' ]
41+
42+
services:
43+
cratedb:
44+
image: crate/crate:${{ matrix.cratedb-version }}
45+
ports:
46+
- 4200:4200
47+
- 5432:5432
48+
env:
49+
CRATE_HEAP_SIZE: 4g
50+
51+
env:
52+
UV_SYSTEM_PYTHON: true
53+
54+
steps:
55+
56+
- name: Acquire sources
57+
uses: actions/checkout@v4
58+
59+
- name: Set up Python
60+
uses: actions/setup-python@v5
61+
with:
62+
python-version: ${{ matrix.python-version }}
63+
architecture: x64
64+
cache: 'pip'
65+
cache-dependency-path: |
66+
framework/mcp/requirements*.txt
67+
68+
- name: Install uv
69+
uses: astral-sh/setup-uv@v5
70+
71+
- name: Install JBang
72+
uses: jbangdev/setup-jbang@main
73+
74+
- name: Configure JBang
75+
run: |
76+
jbang trust add https://github.com/quarkiverse
77+
78+
- name: Install utilities
79+
run: |
80+
uv pip install -r requirements.txt
81+
82+
- name: Validate framework/mcp
83+
run: |
84+
ngr test --accept-no-venv framework/mcp

framework/mcp/README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ Acquire sources, set up sandbox, and install packages.
7474
```bash
7575
git clone https://github.com/crate/cratedb-examples
7676
cd cratedb-examples/framework/mcp
77-
uv pip install -r requirements.txt
77+
uv pip install -r requirements.txt -r requirements-test.txt
7878
```
7979

8080
## Synopsis
@@ -89,6 +89,13 @@ uv run example_dbhub.py
8989
uv run example_jdbc.py
9090
```
9191

92+
## Tests
93+
94+
Run integration tests.
95+
```bash
96+
pytest
97+
```
98+
9299
## Development
93100

94101
`ctk tail` is a handy command to follow the progress of CrateDB's `sys.jobs_log`,

framework/mcp/pyproject.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[tool.pytest.ini_options]
2+
minversion = "2.0"
3+
addopts = """
4+
-rfEXs -p pytester --strict-markers --verbosity=3
5+
--capture=no
6+
"""
7+
log_level = "DEBUG"
8+
log_cli_level = "DEBUG"
9+
testpaths = ["*.py"]
10+
xfail_strict = true
11+
markers = [
12+
]

framework/mcp/requirements-test.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
cratedb-toolkit
2+
pytest<9

framework/mcp/test.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import dataclasses
2+
import re
3+
import shlex
4+
import subprocess
5+
import sys
6+
7+
from cratedb_toolkit.util import DatabaseAdapter
8+
9+
10+
@dataclasses.dataclass
11+
class Process:
12+
"""
13+
Manage outputs of a process.
14+
"""
15+
proc: subprocess.Popen
16+
stdout: bytes
17+
stderr: bytes
18+
19+
@property
20+
def returncode(self) -> int:
21+
return self.proc.returncode
22+
23+
24+
def run(command: str, timeout: int = 30) -> Process:
25+
"""
26+
Invoke a command in a subprocess.
27+
"""
28+
proc = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
29+
stdout, stderr = proc.communicate(timeout=timeout)
30+
return Process(proc, stdout, stderr)
31+
32+
33+
def test_builtin():
34+
"""
35+
Validate the basic built-in MCP server for PostgreSQL works well.
36+
37+
It is written in TypeScript.
38+
https://www.npmjs.com/package/@modelcontextprotocol/server-postgres
39+
"""
40+
p = run(f"{sys.executable} example_builtin.py")
41+
assert p.returncode == 0
42+
43+
# Validate output specific to the MCP server.
44+
assert b"Could not roll back transaction: error: line 1:1: mismatched input 'ROLLBACK' expecting" in p.stderr
45+
46+
# Validate output specific to CrateDB.
47+
assert b"Calling tool: read_query" in p.stdout
48+
assert b'"mountain": "Mont Blanc"' in p.stdout
49+
50+
51+
def test_jdbc():
52+
"""
53+
Validate the Quarkus MCP server for JDBC works well.
54+
55+
It is written in Java.
56+
https://github.com/quarkiverse/quarkus-mcp-servers/tree/main/jdbc#readme
57+
"""
58+
p = run(f"{sys.executable} example_jdbc.py")
59+
assert p.returncode == 0
60+
61+
# Validate output specific to the MCP server.
62+
assert re.match(br".*\[io.quarkus\] \(main\) mcp-server-jdbc 999-SNAPSHOT on JVM.*", p.stderr, re.DOTALL)
63+
64+
# Validate output specific to CrateDB.
65+
assert b"Calling tool: database_info" in p.stdout
66+
assert b'{"database_product_name":"PostgreSQL","driver_name":"PostgreSQL JDBC Driver"' in p.stdout
67+
assert b"Calling tool: describe_table" in p.stdout
68+
assert b"Failed to describe table: The column name current_database was not found in this ResultSet." in p.stdout
69+
assert b"Calling tool: read_query" in p.stdout
70+
assert b'"mountain":"Mont Blanc"' in p.stdout
71+
72+
# Validate database content.
73+
db = DatabaseAdapter("crate://crate@localhost:4200/")
74+
records = db.refresh_table("doc.testdrive")
75+
records = db.run_sql("SELECT * FROM doc.testdrive", records=True)
76+
assert len(records) >= 1
77+
assert records[0] == {"id": 42, "data": "foobar"}
78+
79+
80+
def test_dbhub():
81+
"""
82+
Validate the DBHub MCP server works well.
83+
84+
DBHub is a universal database gateway implementing the Model Context
85+
Protocol (MCP) server interface. This gateway allows MCP-compatible
86+
clients to connect to and explore different databases.
87+
88+
It is written in TypeScript.
89+
https://github.com/bytebase/dbhub
90+
"""
91+
p = run(f"{sys.executable} example_dbhub.py")
92+
assert p.returncode == 0
93+
94+
# Validate output specific to the MCP server.
95+
assert b"Successfully connected to PostgreSQL database" in p.stderr
96+
assert b"Universal Database MCP Server" in p.stderr
97+
98+
# Validate output specific to CrateDB.
99+
assert b"Calling tool: run_query" in p.stdout
100+
assert b'"mountain": "Mont Blanc"' in p.stdout
101+
102+
assert b"Calling tool: list_connectors" in p.stdout
103+
assert b' "dsn": "postgres://' in p.stdout

0 commit comments

Comments
 (0)