Model Context Protocol (MCP) is the part of the 2025–2026 agent stack that actually stuck. Instead of writing a bespoke tool wrapper for every assistant, you run a small server that speaks MCP and any MCP client — Claude Desktop, Claude Code, Cursor, VS Code Copilot agent mode, your own LangGraph agent — can call it. Neo4j ships official MCP servers, which means your agent can inspect a graph schema, run Cypher, and keep durable memory in a graph rather than in a text file.
This tutorial gets a real Neo4j MCP setup running end to end, then spends the second half on the part most write-ups skip: how to expose a graph to a non-deterministic client without handing it your production database.
What you will build
mcp-neo4j-cypher— schema introspection plus read and write Cypher, wired into Claude Desktop and Cursor.mcp-neo4j-memory— a persistent entity/relationship memory graph the agent can read and update across sessions.mcp-neo4j-data-modeling— conversational graph data modeling that emits an importable model.- A read-only role, a transaction timeout, and a review checklist so none of the above can ruin your afternoon.
Prerequisites
- A running Neo4j 5.x or 2025.x instance. The Docker setup from our earlier tutorial is perfect; Aura works too (use the
neo4j+s://URI). - Python 3.10+ with
uvinstalled (pipx install uv).uvxruns the servers without a permanent install. - An MCP client. We use Claude Desktop and Cursor; the config shape is nearly identical everywhere.
A quick smoke test that the server package resolves:
uvx mcp-neo4j-cypher@latest --help
If that prints usage text, uvx can fetch and launch the server, which is all the client needs to do later.
Step 1 — a read-only user for the agent
Do this first. An agent with your neo4j superuser credentials is a production incident waiting for a plausible-sounding prompt. Neo4j's fine-grained privileges make a safe role a five-line job (Enterprise / Aura; on Community, use a separate throwaway database instead):
// as an admin user, in the system database
CREATE ROLE agent_reader IF NOT EXISTS;
GRANT ACCESS ON DATABASE neo4j TO agent_reader;
GRANT MATCH {*} ON GRAPH neo4j NODES * TO agent_reader;
GRANT MATCH {*} ON GRAPH neo4j RELATIONSHIPS * TO agent_reader;
GRANT SHOW INDEX ON DATABASE neo4j TO agent_reader;
CREATE USER agent IF NOT EXISTS
SET PLAINTEXT PASSWORD 'rotate-me'
SET PASSWORD CHANGE NOT REQUIRED;
GRANT ROLE agent_reader TO agent;
If some property is genuinely sensitive — salaries, PII, API keys — deny it explicitly rather than trusting the prompt:
DENY READ {ssn, salary} ON GRAPH neo4j NODES Person TO agent_reader;
Our multi-tenant RBAC walkthrough goes deeper on privilege design if you need per-tenant separation as well.
Also cap runaway queries at the server, not in the prompt:
# neo4j.conf
db.transaction.timeout=30s
dbms.memory.transaction.total.max=2g
Step 2 — connect the Cypher server to Claude Desktop
Edit Claude Desktop's config file (~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows):
{
"mcpServers": {
"neo4j-cypher": {
"command": "uvx",
"args": ["mcp-neo4j-cypher@latest"],
"env": {
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USERNAME": "agent",
"NEO4J_PASSWORD": "rotate-me",
"NEO4J_DATABASE": "neo4j",
"NEO4J_READ_ONLY": "true"
}
}
}
}
Restart the client. You should now see three tools offered by the server:
get_neo4j_schema— labels, relationship types, and properties, gathered from the database rather than guessed.read_neo4j_cypher— runs a read query and returns rows.write_neo4j_cypher— runs a write query (absent or refused when the server runs read-only).
Ask something the model cannot answer without the schema, e.g. "What labels exist and how are Customer and Order connected? Then show me the ten customers with the most orders." Watch the tool calls: a schema fetch, then a generated MATCH ... RETURN ... ORDER BY ... LIMIT. That schema call is what makes agent-written Cypher usable at all — the same principle as the retrieval-side guardrails in our text-to-Cypher post.
Cursor and Claude Code
Cursor uses .cursor/mcp.json in the project (or the global equivalent) with the identical mcpServers block, so you can commit a project-scoped config that points every developer at a shared dev graph. Claude Code takes it from the CLI:
claude mcp add neo4j-cypher \
--env NEO4J_URI=bolt://localhost:7687 \
--env NEO4J_USERNAME=agent \
--env NEO4J_PASSWORD=rotate-me \
--env NEO4J_READ_ONLY=true \
-- uvx mcp-neo4j-cypher@latest
And verify with claude mcp list. If a server shows as failed, run the exact uvx ... command in a terminal with the same env vars — you will usually find a URI typo, a TLS scheme mismatch (bolt:// vs neo4j+s://), or uvx missing from the client's PATH. GUI clients often do not inherit your shell PATH; the fix is an absolute path such as /Users/you/.local/bin/uvx in command.
Step 3 — give the agent a memory graph
mcp-neo4j-memory is the reason graph people should care about MCP more than most. Instead of a flat scratchpad, the agent stores entities, observations, and relationships — and can traverse them later. Add a second server, this time with a writable user and its own database or label namespace:
{
"mcpServers": {
"neo4j-memory": {
"command": "uvx",
"args": ["mcp-neo4j-memory@latest"],
"env": {
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USERNAME": "agent_memory",
"NEO4J_PASSWORD": "rotate-me-too",
"NEO4J_DATABASE": "memory"
}
}
}
}
The tools follow the knowledge-graph memory pattern: create entities, add observations to them, link them with typed relationships, search, and read the whole graph back. In practice a session looks like "remember that the Orders service owns the Order and LineItem nodes and that Priya owns it" → three entities and two relationships written. Weeks later, "who should review a change to the Order model?" resolves by traversal instead of by luck.
Two habits keep the memory graph healthy:
- Keep it in its own database (
CREATE DATABASE memory) so agent writes can never collide with application data, and so you can drop and rebuild it freely. - Constrain the entity name.
CREATE CONSTRAINT memory_entity_name IF NOT EXISTS FOR (e:Memory) REQUIRE e.name IS UNIQUE;stops duplicate entities from accumulating every time the model phrases a name differently.
Inspect what it wrote with plain Cypher — it is a normal graph, and that is the whole appeal:
MATCH (e)-[r]->(t)
RETURN e.name AS from, type(r) AS rel, t.name AS to
ORDER BY from LIMIT 50;
Step 4 — model before you migrate
mcp-neo4j-data-modeling exposes graph modeling as tools: propose nodes, relationships and properties, validate the model, and export it as JSON that Neo4j's import tooling and the Aura import UI understand. It is genuinely good for the first pass over a relational schema — paste your DDL, ask for a property-graph model, then argue with it.
The arguing matters. Agents reliably make the same modeling mistakes: turning every join table into a node when a relationship with properties would do, modelling time as a property when your queries need a time-tree or a linked list of events, and inventing a relationship type per verb tense. Treat the output as a draft, then check it against the actual query workload — which is exactly the review we run at the start of a migration engagement, alongside the loading strategy in Importing Relational Data Without the (Dead) ETL Tool.
Step 5 — the production guardrail checklist
Before any of this points at something that matters:
- Separate credentials per server. Read-only for Cypher, write-only-to-
memoryfor memory. Never one shared account. - Read-only by default. Turn on writes for a single, deliberate task and turn them off again. An agent that can
DETACH DELETEis one confused loop away from an incident. - Point at a replica or a copy. For analysis work, a secondary or a restored snapshot removes the whole class of risk — see our backup and PITR drill for a fast way to produce one.
- Timeouts and result caps.
db.transaction.timeout, plus a habit of asking forLIMITed results, keeps one broadMATCHfrom pinning the heap. - Log the queries. Enable the query log and review what the agent actually ran for the first few weeks. It is the cheapest audit you will ever run — and it is how you discover the queries worth optimising properly with PROFILE.
- Pin versions in shared configs.
@latestis fine on your laptop; use an explicit version in anything a team depends on. - Treat prompt injection as real. If the graph contains user-supplied text and the agent can write, hostile content in a property can become instructions. Read-only access is the mitigation that actually holds.
Where this pays off
The immediate win is developer speed: schema-aware Cypher drafting, ad-hoc data questions answered without a BI ticket, and modeling sessions that produce an importable artefact. The larger win is architectural — once agent memory lives in Neo4j, memory and domain data are queryable together, which is the foundation the GraphRAG systems in our first GraphRAG pipeline tutorial are built on.
If you are deciding how far to let agents reach into a production graph — the roles, the database topology, the audit trail — get in touch. Our senior Neo4j consultants design exactly these boundaries, and a short architecture review is usually enough to settle it.