Skip to content

MCP: Add example about MCP Alchemy #880

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/framework-mcp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ jobs:
- name: Install uv
uses: astral-sh/setup-uv@v5

- name: Install Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'

- name: Install JBang
uses: jbangdev/setup-jbang@main

Expand Down
7 changes: 7 additions & 0 deletions framework/mcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ program.
Server for JDBC] from the [quarkus-mcp-servers] package, providing a range
of tools. It is written in Java, to be invoked with [JBang].

- `example_mcp_alchemy.py`: Exercise communication using the [MCP Alchemy] MCP
server package, providing a range of tools. It is written in Python, and uses
[SQLAlchemy] and the [CrateDB SQLAlchemy dialect].

## Resources

- Read a [brief introduction to MCP] by ByteByteGo.
Expand Down Expand Up @@ -124,10 +128,12 @@ unlocking more details and features.
[Claude Desktop configuration]: https://github.com/modelcontextprotocol/servers?tab=readme-ov-file#using-an-mcp-client
[connecting to an already running MCP server]: https://github.com/modelcontextprotocol/python-sdk/issues/145
[CrateDB]: https://cratedb.com/database
[CrateDB SQLAlchemy dialect]: https://cratedb.com/docs/sqlalchemy-cratedb/
[DBHub]: https://github.com/bytebase/dbhub
[Introduction to MCP]: https://modelcontextprotocol.io/introduction
[JBang]: https://www.jbang.dev/
[MCP]: https://modelcontextprotocol.io/
[MCP Alchemy]: https://github.com/runekaagaard/mcp-alchemy
[MCP Python SDK]: https://github.com/modelcontextprotocol/python-sdk
[MCP SSE]: https://github.com/sidharthrajaram/mcp-sse
[Model Context Protocol (MCP) @ CrateDB]: https://github.com/crate/crate-clients-tools/discussions/234
Expand All @@ -137,5 +143,6 @@ unlocking more details and features.
[npx]: https://docs.npmjs.com/cli/v11/commands/npx
[oterm configuration]: https://ggozad.github.io/oterm/tools/mcp/
[quarkus-mcp-servers]: https://github.com/quarkiverse/quarkus-mcp-servers
[SQLAlchemy]: https://sqlalchemy.org/
[uv]: https://docs.astral.sh/uv/
[Writing MCP Clients]: https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#writing-mcp-clients
10 changes: 4 additions & 6 deletions framework/mcp/example_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,12 @@ async def run():

# Validate database content.
db = DatabaseAdapter("crate://crate@localhost:4200/")
db.run_sql("CREATE TABLE IF NOT EXISTS public.testdrive (id INT, data TEXT)")
db.run_sql("INSERT INTO public.testdrive (id, data) VALUES (42, 'Hotzenplotz')")
db.refresh_table("public.testdrive")
db.run_sql("CREATE TABLE IF NOT EXISTS public.mcp_builtin (id INT, data TEXT)")
db.run_sql("INSERT INTO public.mcp_builtin (id, data) VALUES (42, 'Hotzenplotz')")
db.refresh_table("public.mcp_builtin")

# Read a few resources.
# FIXME: Only works on schema=public, because the PostgreSQL adapter hard-codes `WHERE table_schema = 'public'`.
# https://github.com/bytebase/dbhub/blob/09424c8513c8c7bef7f66377b46a2b93a69a57d2/src/connectors/postgres/index.ts#L89-L107
await client.read_resource("postgres://crate@localhost:5432/testdrive/schema")
await client.read_resource("postgres://crate@localhost:5432/mcp_builtin/schema")


if __name__ == "__main__":
Expand Down
8 changes: 4 additions & 4 deletions framework/mcp/example_dbhub.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ async def run():

# Validate database content.
db = DatabaseAdapter("crate://crate@localhost:4200/")
db.run_sql("CREATE TABLE IF NOT EXISTS testdrive.dbhub (id INT, data TEXT)")
db.run_sql("INSERT INTO testdrive.dbhub (id, data) VALUES (42, 'Hotzenplotz')")
db.refresh_table("public.testdrive")
db.run_sql("CREATE TABLE IF NOT EXISTS testdrive.mcp_dbhub (id INT, data TEXT)")
db.run_sql("INSERT INTO testdrive.mcp_dbhub (id, data) VALUES (42, 'Hotzenplotz')")
db.refresh_table("testdrive.mcp_dbhub")

# Read available resources.
await client.read_resource("db://schemas")
Expand All @@ -57,7 +57,7 @@ async def run():
"schema": "sys",
})
await client.get_prompt("explain_db", arguments={"schema": "testdrive"})
await client.get_prompt("explain_db", arguments={"schema": "testdrive", "table": "dbhub"})
await client.get_prompt("explain_db", arguments={"schema": "testdrive", "table": "mcp_dbhub"})


if __name__ == "__main__":
Expand Down
4 changes: 2 additions & 2 deletions framework/mcp/example_jdbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ async def run():
# await client.call_tool("list_tables", arguments={})
await client.call_tool("describe_table", arguments={"schema": "sys", "table": "summits"})
await client.call_tool("read_query", arguments={"query": "SELECT * FROM sys.summits ORDER BY height DESC LIMIT 3"})
await client.call_tool("create_table", arguments={"query": "CREATE TABLE IF NOT EXISTS testdrive (id INT, data TEXT)"})
await client.call_tool("write_query", arguments={"query": "INSERT INTO testdrive (id, data) VALUES (42, 'foobar')"})
await client.call_tool("create_table", arguments={"query": "CREATE TABLE IF NOT EXISTS testdrive.mcp_jdbc (id INT, data TEXT)"})
await client.call_tool("write_query", arguments={"query": "INSERT INTO testdrive.mcp_jdbc (id, data) VALUES (42, 'foobar')"})

# Get a few prompts.
await client.get_prompt("er_diagram")
Expand Down
51 changes: 51 additions & 0 deletions framework/mcp/example_mcp_alchemy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# MCP Alchemy Model Context Protocol Server for CrateDB
# https://github.com/runekaagaard/mcp-alchemy
#
# Derived from:
# https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#writing-mcp-clients
from cratedb_toolkit.util import DatabaseAdapter
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
import where

from mcp_utils import McpDatabaseConversation


async def run():
# Create server parameters for stdio connection.
server_params = StdioServerParameters(
command=where.first("mcp-alchemy"),
args=[],
env={"DB_URL": "crate://crate@localhost:4200/?schema=testdrive"},
)

async with stdio_client(server_params) as (read, write):
async with ClientSession(
read, write
) as session:
# Initialize the connection.
await session.initialize()

client = McpDatabaseConversation(session)
await client.inquire()

print("## MCP server conversations")
print()

# Provision database content.
db = DatabaseAdapter("crate://crate@localhost:4200/")
db.run_sql("CREATE TABLE IF NOT EXISTS mcp_alchemy (id INT, data TEXT)")
db.run_sql("INSERT INTO mcp_alchemy (id, data) VALUES (42, 'Hotzenplotz')")
db.refresh_table("mcp_alchemy")

# Call a few tools.
await client.call_tool("execute_query", arguments={"query": "SELECT * FROM sys.summits ORDER BY height DESC LIMIT 3"})
await client.call_tool("all_table_names", arguments={})
await client.call_tool("filter_table_names", arguments={"q": "mcp"})
await client.call_tool("schema_definitions", arguments={"table_names": ["mcp_alchemy"]})


if __name__ == "__main__":
import asyncio

asyncio.run(run())
5 changes: 4 additions & 1 deletion framework/mcp/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
cratedb-toolkit
mcp<1.5
mcp<1.6
mcp-alchemy @ git+https://github.com/runekaagaard/mcp-alchemy.git@b85aae6; python_version>='3.12'
sqlalchemy-cratedb>=0.42.0.dev1
where
Comment on lines -2 to +5
Copy link
Member Author

@amotl amotl Apr 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Python version constraint python_version>='3.12' with mcp-alchemy can be removed after merging this one.

42 changes: 38 additions & 4 deletions framework/mcp/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import subprocess
import sys

import pytest
from cratedb_toolkit.util import DatabaseAdapter


Expand Down Expand Up @@ -46,7 +47,7 @@ def test_builtin():
# Validate output specific to CrateDB.
assert b"Calling tool: query" in p.stdout
assert b"mountain: Mont Blanc" in p.stdout
assert b"Reading resource: postgres://crate@localhost:5432/testdrive/schema" in p.stdout
assert b"Reading resource: postgres://crate@localhost:5432/mcp_builtin/schema" in p.stdout
assert b"column_name: id" in p.stdout
assert b"data_type: integer" in p.stdout

Expand Down Expand Up @@ -77,8 +78,8 @@ def test_jdbc():

# Validate database content.
db = DatabaseAdapter("crate://crate@localhost:4200/")
db.refresh_table("doc.testdrive")
records = db.run_sql("SELECT * FROM doc.testdrive", records=True)
db.refresh_table("testdrive.mcp_jdbc")
records = db.run_sql("SELECT * FROM testdrive.mcp_jdbc", records=True)
assert len(records) >= 1
assert records[0] == {"id": 42, "data": "foobar"}

Expand Down Expand Up @@ -114,5 +115,38 @@ def test_dbhub():
assert b"- testdrive" in p.stdout

assert b"Getting prompt: explain_db" in p.stdout
assert b"Table: dbhub in schema 'testdrive'" in p.stdout
assert b"Table: mcp_dbhub in schema 'testdrive'" in p.stdout
assert b"Structure:\\n- id (integer)\\n- data (text)" in p.stdout


@pytest.mark.skipif(sys.version_info < (3, 12), reason="requires Python 3.12+")
def test_mcp_alchemy():
"""
Validate the MCP Alchemy server works well.

MCP Alchemy connects Claude Desktop directly to your databases.
MCP Alchemy is a MCP (model context protocol) server that gives the LLM access
to and knowledge about relational databases like SQLite, Postgresql, MySQL &
MariaDB, Oracle, MS-SQL, and CrateDB.

It is written in Python and uses SQLAlchemy.
https://github.com/runekaagaard/mcp-alchemy
"""
p = run(f"{sys.executable} example_mcp_alchemy.py")
assert p.returncode == 0

# Validate output specific to the MCP server.

# Validate output specific to CrateDB.
assert b"Calling tool: execute_query" in p.stdout
assert b"mountain: Mont Blanc" in p.stdout

assert b"Calling tool: all_table_names" in p.stdout
assert b"mcp_alchemy" in p.stdout

assert b"Calling tool: filter_table_names" in p.stdout
assert b"mcp_alchemy" in p.stdout

assert b"Calling tool: schema_definitions" in p.stdout
assert b"id: INTEGER, nullable" in p.stdout
assert b"data: VARCHAR, nullable" in p.stdout