Skip to main content
Glama
niti007

mysql-neo4j-mcp

by niti007
README.md
# mysql-neo4j-mcp

MCP server exposing scoped read/write tools over a local MySQL database
(`contoso-mysql` container, db `contoso_claims`) and a local Neo4j database
(`enterprise_kb_neo4j` container).

See [`docs/why-mcp.md`](docs/why-mcp.md) for the rationale behind this
architecture vs. connecting directly or scoping access at the cloud IAM
layer (Azure).

## Tools

| Tool | DB | Access |
|---|---|---|
| `mysql_query(sql, params)` | MySQL | Read-only. Runs as `contoso_ro`, which only has `SELECT` granted. Rejects non-`SELECT` statements in code too. |
| `mysql_execute(sql, params)` | MySQL | Read-write. Runs as `contoso_rw`, which has `SELECT/INSERT/UPDATE/DELETE` — no DDL, no admin grants. |
| `neo4j_query(cypher, params)` | Neo4j | Read-only. Runs in a Neo4j `READ` transaction — the server itself rejects any write clause with an `AccessMode` error. |
| `neo4j_write(cypher, params)` | Neo4j | Read-write. Runs in a `WRITE` transaction. |

### Why Neo4j only has one DB user

Neo4j **Community edition** (what's running here) has no role-based access
control at all — `CREATE ROLE` / `GRANT ROLE` commands are Enterprise-only,
and every authenticated user has full read/write access. Creating a second
"reader" user would be theater: it would have identical permissions to the
writer user.

Instead, read/write separation for Neo4j is enforced by the **transaction
mode** (`execute_read` vs `execute_write`), which the Neo4j server honors
independent of RBAC/edition — verified directly: a write query issued
inside a read transaction is rejected server-side with
`Neo.ClientError.Statement.AccessMode`, even on this single Community
instance with no roles configured.

## Setup

1. Copy `.env.example` to `.env` and fill in credentials (see "Provisioning
   DB users" below for how they were created).
2. `python3.12 -m venv .venv && .venv/bin/pip install -r requirements.txt`
   (needs Python 3.10+; the `mcp` SDK doesn't support older versions).
3. Run standalone: `.venv/bin/python3 server.py`
4. Register with an MCP client (e.g. Claude Code):
   ```
   claude mcp add mysql-neo4j-mcp -- /Users/tarunsachdeva/dev/mysql-neo4j-mcp/.venv/bin/python3 /Users/tarunsachdeva/dev/mysql-neo4j-mcp/server.py
   ```

## Provisioning DB users

**MySQL** (connect as root to `contoso-mysql`):
```sql
CREATE USER 'contoso_ro'@'%' IDENTIFIED BY '<password>';
GRANT SELECT ON contoso_claims.* TO 'contoso_ro'@'%';

CREATE USER 'contoso_rw'@'%' IDENTIFIED BY '<password>';
GRANT SELECT, INSERT, UPDATE, DELETE ON contoso_claims.* TO 'contoso_rw'@'%';
FLUSH PRIVILEGES;
```

**Neo4j** (connect via `cypher-shell` as `neo4j` to `enterprise_kb_neo4j`):
```cypher
CREATE USER kb_app SET PASSWORD '<password>' CHANGE NOT REQUIRED;
```
(No role grants — see "Why Neo4j only has one DB user" above.)