Skip to content

Commit 1a0d361

Browse files
committed
MCP: Add minimal examples
1 parent 1c1d9cf commit 1a0d361

File tree

6 files changed

+402
-0
lines changed

6 files changed

+402
-0
lines changed

framework/mcp/README.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Verify MCP with CrateDB
2+
3+
## About
4+
5+
This folder includes Python programs that use the [Model Context Protocol
6+
Python SDK], implementing MCP clients that hold conversations with MCP servers
7+
wrapping database access.
8+
9+
In this case, the focus is on [CrateDB], by using relevant PostgreSQL adapter
10+
implementations provided by the MCP ecosystem, because CrateDB is compatible
11+
with PostgreSQL.
12+
13+
[MCP], the Model Context Protocol, is an open protocol that enables seamless
14+
integration between LLM applications and external data sources and tools.
15+
16+
MCP clients call servers by either invoking them as a subprocess and
17+
communicate with stdio, or by using SSE, which implements TCP socket
18+
communication.
19+
20+
## What's Inside
21+
22+
The Python programs have been derived from the [Writing MCP Clients] example
23+
program.
24+
25+
- `example_builtin.py`: Exercise the basic built-in Model Context Protocol server
26+
[@modelcontextprotocol/server-postgres] that provides read-only access to
27+
PostgreSQL databases per `query` tool. This server enables LLMs to inspect
28+
database schemas and execute read-only queries. It is written in TypeScript,
29+
to be invoked with [npx].
30+
31+
- `example_dbhub.py`: Exercise communication using [DBHub], a universal database
32+
gateway implementing the Model Context Protocol (MCP) server interface. This
33+
gateway allows MCP-compatible clients to connect to and explore different databases.
34+
35+
- `example_jdbc.py`: Exercise JDBC communication using the [Model Context Protocol
36+
Server for JDBC] from the [quarkus-mcp-servers] package, providing a range
37+
of tools. It is written in Java, to be invoked with [JBang].
38+
39+
## Resources
40+
41+
- Read a [brief introduction to MCP] by ByteByteGo.
42+
- Read the canonical [Introduction to MCP].
43+
- Read about the [MCP Python SDK].
44+
- Read about configuring those servers with other MCP clients.
45+
- [Claude Desktop configuration]
46+
- [oterm configuration]
47+
- [Connecting to an already running MCP server] seems to become possible
48+
with [MCP SSE]. As of 2025, not all servers implement that just yet.
49+
- [Model Context Protocol (MCP) @ CrateDB]
50+
51+
## Setup
52+
53+
Start a CrateDB instance on your machine.
54+
```shell
55+
docker run -it --rm \
56+
--publish=4200:4200 --publish=5432:5432 \
57+
--env=CRATE_HEAP_SIZE=2g \
58+
crate:latest -Cdiscovery.type=single-node
59+
```
60+
61+
Install the [uv] package manager and launcher.
62+
```shell
63+
{brew,pip} install uv
64+
```
65+
66+
Install the [JBang] package manager and launcher.
67+
```shell
68+
{asdf,choco,dnf,brew,scoop,sdk} install jbang
69+
```
70+
71+
## Install
72+
73+
Acquire sources, set up sandbox, and install packages.
74+
```bash
75+
git clone https://github.com/crate/cratedb-examples
76+
cd cratedb-examples/framework/mcp
77+
uv pip install -r requirements.txt
78+
```
79+
80+
## Synopsis
81+
82+
```shell
83+
uv run example_builtin.py
84+
```
85+
```shell
86+
uv run example_dbhub.py
87+
```
88+
```shell
89+
uv run example_jdbc.py
90+
```
91+
92+
## Development
93+
94+
`ctk tail` is a handy command to follow the progress of CrateDB's `sys.jobs_log`,
95+
which is applicable for all sorts of driver, adapter, and connector explorations.
96+
After providing authentication information, just use uv's `uvx` launcher to invoke
97+
CrateDB Toolkit's tail command without installation.
98+
```shell
99+
export CRATEDB_SQLALCHEMY_URL=crate://crate@localhost:4200/
100+
```
101+
```shell
102+
uvx --from=cratedb-toolkit ctk tail -n 3 --follow --format=log-pretty sys.jobs_log
103+
```
104+
105+
## Outlook
106+
107+
A future iteration may provide a premium MCP database adapter for CrateDB,
108+
unlocking more details and features.
109+
110+
111+
[brief introduction to MCP]: https://blog.bytebytego.com/i/159075598/what-is-mcp
112+
[Claude Desktop configuration]: https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#using-an-mcp-client
113+
[connecting to an already running MCP server]: https://github.com/modelcontextprotocol/python-sdk/issues/145
114+
[CrateDB]: https://cratedb.com/database
115+
[DBHub]: https://github.com/bytebase/dbhub
116+
[Introduction to MCP]: https://modelcontextprotocol.io/introduction
117+
[JBang]: https://www.jbang.dev/
118+
[MCP]: https://modelcontextprotocol.io/
119+
[MCP Python SDK]: https://github.com/modelcontextprotocol/python-sdk
120+
[MCP SSE]: https://github.com/sidharthrajaram/mcp-sse
121+
[Model Context Protocol (MCP) @ CrateDB]: https://github.com/crate/crate-clients-tools/discussions/234
122+
[Model Context Protocol Python SDK]: https://pypi.org/project/mcp/
123+
[Model Context Protocol Server for JDBC]: https://github.com/quarkiverse/quarkus-mcp-servers/tree/main/jdbc#readme
124+
[@modelcontextprotocol/server-postgres]: https://www.npmjs.com/package/@modelcontextprotocol/server-postgres
125+
[npx]: https://docs.npmjs.com/cli/v11/commands/npx
126+
[oterm configuration]: https://ggozad.github.io/oterm/tools/mcp/
127+
[quarkus-mcp-servers]: https://github.com/quarkiverse/quarkus-mcp-servers
128+
[uv]: https://docs.astral.sh/uv/
129+
[Writing MCP Clients]: https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#writing-mcp-clients

framework/mcp/backlog.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# MCP CrateDB backlog
2+
3+
## Iteration +1
4+
- Builtin: Submit a CrateDB adapter, getting rid of the
5+
`Could not roll back transaction: error`
6+
- JDBC: Tool `list_tables` currently blocks.
7+
- DBHub: Reading resource `tables` does not work,
8+
because `WHERE table_schema = 'public'`
9+
10+
## Iteration +2
11+
- General: Evaluate all connectors per `stdio` and `sse`, where possible
12+
- General: Evaluate available prompts, there are no test cases yet
13+
- General: Improve test cases to not just look at stdout/stderr streams,
14+
do regular method-based testing instead
15+
16+
## Iteration +3
17+
- Provide a premium connector, like available for other databases.
18+
Examples:
19+
- Tinybird MCP server
20+
https://github.com/tinybirdco/mcp-tinybird
21+
- MongoDB Lens
22+
https://github.com/modelcontextprotocol/servers/pull/892
23+
https://github.com/furey/mongodb-lens
24+
- Provide database specifics per "resources", like [SQLite Explorer] and
25+
DBHub are doing it.
26+
27+
## Done
28+
- DBHub: https://github.com/bytebase/dbhub/issues/5
29+
Resolved: https://github.com/bytebase/dbhub/pull/6
30+
31+
32+
[SQLite Explorer]: https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#sqlite-explorer

framework/mcp/example_builtin.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Built-in Model Context Protocol Server for PostgreSQL
2+
# https://www.npmjs.com/package/@modelcontextprotocol/server-postgres
3+
#
4+
# Derived from:
5+
# https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#writing-mcp-clients
6+
from mcp import ClientSession, StdioServerParameters
7+
from mcp.client.stdio import stdio_client
8+
from pprint import pprint
9+
10+
11+
# Create server parameters for stdio connection.
12+
server_params = StdioServerParameters(
13+
command="npx",
14+
args=[
15+
"-y",
16+
"@modelcontextprotocol/server-postgres",
17+
"postgresql://crate@localhost:5432/doc",
18+
],
19+
env=None,
20+
)
21+
22+
23+
async def run():
24+
async with stdio_client(server_params) as (read, write):
25+
async with ClientSession(
26+
read, write
27+
) as session:
28+
# Initialize the connection
29+
await session.initialize()
30+
31+
# List available prompts
32+
# TODO: Not available on this server.
33+
#print("Prompts:")
34+
#pprint(await session.list_prompts())
35+
#print()
36+
37+
# List available resources
38+
print("Resources:")
39+
pprint(await session.list_resources())
40+
print()
41+
42+
# List available tools
43+
print("Tools:")
44+
pprint(await session.list_tools())
45+
print()
46+
47+
print("Calling tool: read_query")
48+
result = await session.call_tool("query", arguments={"sql": "SELECT * FROM sys.summits ORDER BY height DESC LIMIT 3"})
49+
pprint(result)
50+
print()
51+
52+
53+
54+
if __name__ == "__main__":
55+
import asyncio
56+
57+
asyncio.run(run())

framework/mcp/example_dbhub.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# DBHub Model Context Protocol Server for PostgreSQL
2+
# https://github.com/bytebase/dbhub
3+
#
4+
# Derived from:
5+
# https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#writing-mcp-clients
6+
import shlex
7+
8+
from mcp import ClientSession, StdioServerParameters
9+
from mcp.client.stdio import stdio_client
10+
from pprint import pprint
11+
12+
13+
# Create server parameters for stdio connection.
14+
server_params_npx = StdioServerParameters(
15+
command="npx",
16+
args=[
17+
"-y",
18+
"@bytebase/dbhub",
19+
"--transport=stdio",
20+
#"--transport=sse",
21+
#"--port=8080",
22+
"--dsn=postgres://crate@localhost:5432/doc",
23+
],
24+
env=None,
25+
)
26+
27+
docker_command = """
28+
docker run --rm --init \
29+
--name dbhub \
30+
--publish 8080:8080 \
31+
bytebase/dbhub:latest \
32+
--transport sse \
33+
--port 8080 \
34+
--dsn="postgres://crate@host.docker.internal:5432/doc?sslmode=disable"
35+
"""
36+
server_params_docker = StdioServerParameters(
37+
command="npx",
38+
args=shlex.split(docker_command),
39+
env=None,
40+
)
41+
42+
43+
async def run():
44+
async with stdio_client(server_params_npx) as (read, write):
45+
async with ClientSession(
46+
read, write
47+
) as session:
48+
# Initialize the connection
49+
await session.initialize()
50+
51+
# List available prompts
52+
print("Prompts:")
53+
pprint(await session.list_prompts())
54+
print()
55+
56+
# List available resources
57+
print("Resources:")
58+
pprint(await session.list_resources())
59+
print()
60+
61+
# List available tools
62+
print("Tools:")
63+
pprint(await session.list_tools())
64+
print()
65+
66+
print("Calling tool: run_query")
67+
result = await session.call_tool("run_query", arguments={"query": "SELECT * FROM sys.summits ORDER BY height DESC LIMIT 3"})
68+
pprint(result)
69+
print()
70+
71+
print("Calling tool: list_connectors")
72+
result = await session.call_tool("list_connectors", arguments={})
73+
pprint(result)
74+
print()
75+
76+
# Read a resource
77+
# FIXME: Does not work, because the PostgreSQL adapters hard-codes `WHERE table_schema = 'public'`.
78+
# https://github.com/bytebase/dbhub/blob/09424c8513c8c7bef7f66377b46a2b93a69a57d2/src/connectors/postgres/index.ts#L89-L107
79+
"""
80+
print("Reading resource: tables")
81+
content, mime_type = await session.read_resource("db://tables")
82+
print("MIME type:", mime_type)
83+
pprint(content)
84+
"""
85+
86+
87+
if __name__ == "__main__":
88+
import asyncio
89+
90+
asyncio.run(run())

0 commit comments

Comments
 (0)