🔍⁉️ Agensgraph MCP Server
🌟 Overview
A Model Context Protocol (MCP) server implementation that provides database interaction and allows graph exploration capabilities through Agensgraph. This server enables running Cypher graph queries, analyzing complex domain data, and automatically generating business insights that can be enhanced with Claude's analysis.
🧩 Components
🛠️ Tools
The server offers these core tools:
📊 Query Tools
-
read_agensgraph_cypher- Execute Cypher read queries to read data from the database. Runs in a read-only transaction (the database rejects writes even if the keyword check is bypassed), and results are paginated so an unbounded query can't flood the context.
- Input:
query(string): The Cypher query to executeparams(dictionary, optional): Parameters to pass to the Cypher querylimit(int, optional): max rows for this page (default 100; clamped to a server max, default 1000)offset(int, optional): rows to skip — pass the response'snext_offsetto fetch the next page. Skipping produces and discards the rows before the page, so the cost grows with the offset: measured over 50,000 nodes, offset 0 took 0.062 s, 10,000 took 1.19 s and 40,000 took 6.21 s.walk(bool, optional): read the whole result in one pass from a server-side cursor instead of a page of it, and report the exacttotal_rows. Paging through a whole result costs the sum of the offsets: measured over 200,000 rows, pages of 1,000 took 7.90 s and one walk took 0.70 s.
- Returns: a JSON object
{ "rows": [...], "row_count", "offset", "limit", "has_more", "next_offset" }. Whenhas_moreis true, call again withoffset = next_offsetfor the next page. (Page sizes are configurable viaAGENSGRAPH_PAGE_SIZE/AGENSGRAPH_MAX_PAGE_SIZE.)
A response is bounded by a token budget — 10,000 tokens by default, set with
AGENSGRAPH_RESPONSE_TOKEN_LIMITor--token-limit, and turned off with0. Whole rows are dropped for it, never part of one, androws_omittedsays how many: an unbounded read of a graph holding text returned 874,043 bytes in a single result.A list of 128 items or more is replaced with
<omitted: list of N items>rather than removed, so a suppressed embedding cannot be read as an absent property.A query that both ends in its own
SKIP/OFFSET/LIMITand usesFILTER,NEXTorCALL ... YIELDis refused by name: the grammar keeps those three for the top of a statement, so neither appending paging nor reading the query as a subquery works. Write the page into the query itself and leavelimit/offsetalone. -
write_agensgraph_cypher- Execute updating Cypher queries
- Input:
query(string): The Cypher update queryparams(dictionary, optional): Parameters to pass to the Cypher query
- Returns: A JSON object carrying the five write counters —
{ insertedvertices, insertededges, deletedvertices, deletededges, updatedproperties }— plus therowsthe statement returned. A counter the statement cannot be held to isnullrather than0. The counters are read inside the same transaction as the write, and nothing is raised once the commit has landed: a reply is a reply about work that was committed, and a failure means nothing was.
A parameter is written
%(name)s, not$name. Both tools takeparamsas a map and bind it the way the database driver binds one, so the placeholder in the statement is the driver's:UNWIND %(records)s AS recordwithparams: {"records": [...]}.$recordsis not a placeholder this server rewrites — it reaches the server as written and comes backERROR: syntax error at or near "$". The data-modeling server generates ingest queries in this form already, so a generated query and a hand-written one take parameters the same way. A list or a map bound this way arrives as JSONB, which is whatUNWINDandINexpect.
🕸️ Schema Tools
get_agensgraph_schema- What is in the graph: one entry per node label, with its exact node count, its properties, and the relationship types leaving it.
- No input required
- Returns:
{ "<Label>": { "type": "node", "count", "properties": { "<key>": { "type", "declared", "indexed", "unique" } }, "relationships": { "<TYPE>": { "direction", "labels": [...], "properties", "count" } } } }. A label holding nothing is left out. - Counts, relationships, indexes and constraints are exact. Property types come from a
bounded sample of each label (
AGENSGRAPH_SCHEMA_SAMPLE, default 1000) except where a property has a column of its own, which the catalog answers exactly. Nothing scans the graph: the triples come from the catalog the planner keeps, falling back to a scan of the edges only when nothing has gathered it since the last write. Measured on a graph of 138,619 vertices and 274,748 edges: 0.86 s reading it by hand, 0.22 s this way. - Both places uniqueness is kept are read — a property index and a uniqueness assertion, which the server keeps as an exclusion constraint and the property-index view hides.
- Nothing is installed. A full start leaves
pg_procandpg_classthe size they were, which the tests assert.
⚡ Performance Tools
-
explain_agensgraph_cypher- Show how AgensGraph would run a Cypher statement.
EXPLAINaccepts Cypher directly, and withoutanalyzethe statement is only planned, never executed. - Input:
query(string): The Cypher statement to plananalyze(bool, optional): also run it and report actual timings. It runs inside a read-only transaction either way, so a statement that would write is refused by the server with25006and leaves nothing behind.
- Returns: the plan as JSON
- Show how AgensGraph would run a Cypher statement.
-
recommend_property_indexes- Suggest property indexes and rewrites for a query, from its plan and the label catalogs. Reports DDL to consider; it never runs it.
- Input:
query(string),params(dictionary, optional — a list bound as a parameter plans as a jsonb containment test rather than as an index lookup, so advice about a query with parameters is advice about a different plan without them) - Returns:
{ "findings": [...], "verified": false, "note": ..., "existing_indexes": [...] }
A property tested only with
STARTS WITHis never also recommended an index: with the index built the same query still plansSeq Scan ... Filter: string_starts_with(...), so the two pieces of advice would contradict each other. Another property in the same filter still gets its index.Alongside missing indexes it flags two shapes that cannot reach an index at all:
STARTS WITH, which compiles tostring_starts_withand reads the whole label however selective the prefix is (AGV2-514), and the jsonb containment test anINlist becomes once bound as a parameter (AGV2-515). Both are reported with the rewrite that does use an index.Recommendations are reasoned, not measured, and say so. A property index cannot be costed before it is built: a hypothetical index has to be supplied as a plain
CREATE INDEX, and the expression a property index carries —properties.'key'::text— cannot be written that way. An index written with the jsonb operators instead is not the expression a Cypher filter matches, so the planner ignores it. Build the index and compareEXPLAINbefore and after to confirm. -
agensgraph_health- Cache hit ratio, unused indexes, vacuum backlog, connection use,
auto_gather_graphmeta, and which optional extensions are installed. Each check stands on its own — one whose extension is absent reports that rather than failing the others. - No input required
- Cache hit ratio, unused indexes, vacuum backlog, connection use,
-
top_cypher_queries- The Cypher statements costing the most total time, from
pg_stat_statements. Literals appear as parameters, since Cypher is normalised the same way SQL is. - Input:
limit(int, optional, default 20) - Requires
pg_stat_statements; reports how to enable it when absent.
- The Cypher statements costing the most total time, from
🔒 Read-only mode
--read-only, or AGENSGRAPH_READ_ONLY=true, starts the server without the write tool. Six
tools remain — get_agensgraph_schema, read_agensgraph_cypher, explain_agensgraph_cypher,
recommend_property_indexes, agensgraph_health and top_cypher_queries — and the write tool
is not registered, so a client cannot call it by name either. A read-only server also does not
create the graph it is pointed at, because creating one is a write.
The tools that are left run their statements inside a read-only transaction, which is where the
guarantee actually lives: a write is refused by the database with 25006 whatever the text
looked like. explain_agensgraph_cypher with analyze is the case that shows the difference —
it executes what it is given without reading it, and the transaction refuses the write.
One privilege escapes it, so the server refuses to start holding it: a role that can run
COPY ... TO PROGRAM runs a command on the database server's host, which takes rows out rather
than putting any in, so a read-only transaction has no write to refuse. Connect as a role that
is neither a superuser nor a member of pg_execute_server_program, or pass
--allow-server-programs to accept it deliberately.
🏷️ Namespacing
The server supports namespacing to allow multiple Agensgraph MCP servers to be used simultaneously. When a namespace is provided, all tool names are prefixed with the namespace followed by a hyphen (e.g., mydb-read_agensgraph_cypher).
This is useful when you need to connect to multiple Agensgraph databases or instances from the same session.
🔧 Usage with Claude Desktop
💾 Released Package
On PyPI: https://pypi.org/project/mcp-agensgraph-cypher/
Add the server to your claude_desktop_config.json with the database connection configuration through environment variables. You may also specify the transport method and namespace with cli arguments or environment variables.
If running locally, use the following configuration after running uv sync in the server directory:
"mcpServers": {
"agensgraph-cypher": {
"command": "uv",
"args": [
"--directory",
"/path/to/agensgraph-ai/mcp-agensgraph/servers/mcp-agensgraph-cypher",
"run",
"mcp-agensgraph-cypher"
],
"env": {
"AGENSGRAPH_URL": "postgresql://<host>:<port>",
"AGENSGRAPH_USERNAME": "<your-username>",
"AGENSGRAPH_PASSWORD": "<your-password>",
"AGENSGRAPH_DATABASE": "<dbname>",
"AGENSGRAPH_GRAPHNAME": "<graphname>"
}
}
}
Alternatively, using the released package:
"mcpServers": {
"agensgraph-cypher": {
"command": "uvx",
"args": [ "mcp-agensgraph-cypher", "--transport", "stdio" ],
"env": {
"AGENSGRAPH_URL": "postgresql://<host>:<port>",
"AGENSGRAPH_USERNAME": "<your-username>",
"AGENSGRAPH_PASSWORD": "<your-password>",
"AGENSGRAPH_DATABASE": "<dbname>",
"AGENSGRAPH_GRAPHNAME": "<graphname>"
}
}
}
Multiple Graphs Example
Here's an example of connecting to multiple Graphs within a single agensgraph db using namespaces:
If running locally, use the following configuration after running uv sync in the server directory:
{
"mcpServers": {
"graph1-agensgraph": {
"command": "uv",
"args": [
"--directory",
"/path/to/agensgraph-ai/mcp-agensgraph/servers/mcp-agensgraph-cypher",
"run",
"mcp-agensgraph-cypher"
],
"env": {
"AGENSGRAPH_URL": "postgresql://<host>:<port>",
"AGENSGRAPH_USERNAME": "<your-username>",
"AGENSGRAPH_PASSWORD": "<your-password>",
"AGENSGRAPH_DATABASE": "<dbname>",
"AGENSGRAPH_GRAPHNAME": "graph1"
}
},
"graph2-agensgraph": {
"command": "uv",
"args": [
"--directory",
"/path/to/agensgraph-ai/mcp-agensgraph/servers/mcp-agensgraph-cypher",
"run",
"mcp-agensgraph-cypher"
],
"env": {
"AGENSGRAPH_URL": "postgresql://<host>:<port>",
"AGENSGRAPH_USERNAME": "<your-username>",
"AGENSGRAPH_PASSWORD": "<your-password>",
"AGENSGRAPH_DATABASE": "<dbname>",
"AGENSGRAPH_GRAPHNAME": "graph2"
}
}
}
}
Alternatively, using the released package with namespaces:
{
"mcpServers": {
"graph1-agensgraph": {
"command": "uvx",
"args": [ "mcp-agensgraph-cypher", "--namespace", "graph1" ],
"env": {
"AGENSGRAPH_URL": "postgresql://<host>:<port>",
"AGENSGRAPH_USERNAME": "<your-username>",
"AGENSGRAPH_PASSWORD": "<your-password>",
"AGENSGRAPH_DATABASE": "<dbname>",
"AGENSGRAPH_GRAPHNAME": "graph1"
}
},
"graph2-agensgraph": {
"command": "uvx",
"args": [ "mcp-agensgraph-cypher", "--namespace", "graph2" ],
"env": {
"AGENSGRAPH_URL": "postgresql://<host>:<port>",
"AGENSGRAPH_USERNAME": "<your-username>",
"AGENSGRAPH_PASSWORD": "<your-password>",
"AGENSGRAPH_DATABASE": "<dbname>",
"AGENSGRAPH_GRAPHNAME": "graph2"
}
}
}
}
In this setup:
- The graph1 graph tools will be prefixed with
graph1-(e.g.,graph1-read_agensgraph_cypher) - The graph2 database tools will be prefixed with
graph2-(e.g.,graph2-get_agensgraph_schema)
Syntax with --db-url, --username, --password and other command line arguments is still supported but environment variables are preferred.
Command-line syntax
"mcpServers": {
"agensgraph": {
"command": "uvx",
"args": [
"mcp-agensgraph-cypher",
"--db-url",
"postgresql://<host>:<port>",
"--database",
"<your-db-name>",
"--username",
"<your-username>",
"--graphname",
"<graphname>",
"--namespace",
"mydb"
]
}
}
A password on the command line is readable by every process on the machine, through ps, so
there is no --password here: leave it out and libpq resolves it the way it resolves
everything else -- PGPASSWORD, .pgpass, PGSERVICE, or the authentication method that
needs none.
--transport is left out too. It defaults to stdio, which is what Claude Desktop spawns and
the only transport that is not reachable by another process on the machine. The HTTP and SSE
transports have no authentication, and none is available to configure: initialize and
tools/list are answered without a credential, so anything that can reach the port can run
every tool this server exposes with its database credentials. Binding them to 0.0.0.0
publishes that to the network and also defeats the one check that is there, since the trusted-
host middleware is satisfied by a Host: localhost header the caller writes themselves. If you
serve HTTP, keep the loopback default and put something that authenticates in front of it.
🚀 Development
📦 Prerequisites
A running AgensGraph 2.17 or later. The driver refuses an older server at connect.
- Install
uv(Universal Virtualenv):
# Using pip
pip install uv
# Using Homebrew on macOS
brew install uv
# Using cargo (Rust package manager)
cargo install uv
- Clone the repository and set up development environment:
# Clone the repository
git clone https://github.com/skaiworldwide-oss/agensgraph-ai.git
cd agensgraph-ai/mcp-agensgraph/servers/mcp-agensgraph-cypher
# Create and activate virtual environment using uv
uv venv
source .venv/bin/activate # On Unix/macOS
.venv\Scripts\activate # On Windows
# Install dependencies including dev dependencies
uv sync
- Run Integration Tests
./test.sh
🔧 Development Configuration
# Add the server to your claude_desktop_config.json
"mcpServers": {
"agensgraph": {
"command": "uv",
"args": [
"--directory",
"/path/to/agensgraph-ai/mcp-agensgraph/servers/mcp-agensgraph-cypher",
"run",
"mcp-agensgraph-cypher",
"--transport",
"stdio",
"--namespace",
"dev"
],
"env": {
"AGENSGRAPH_URL": "postgresql://localhost:5432",
"AGENSGRAPH_USERNAME": "<your-username>",
"AGENSGRAPH_PASSWORD": "<your-password>",
"AGENSGRAPH_DATABASE": "<dbname>",
"AGENSGRAPH_GRAPHNAME": "graph"
}
}
}
📄 License
This MCP server is licensed under the Apache License 2.0, which is what LICENSE, NOTICE and the package metadata declare. You are free to use, modify and distribute it subject to that licence; see LICENSE for the terms.
Release files for mcp-agensgraph-cypher 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| mcp_agensgraph_cypher-0.3.1.tar.gz | 44.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| mcp_agensgraph_cypher-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 74.9 kB
Release files / mcp_agensgraph_cypher-0.3.1.tar.gz
| Download URL | mcp_agensgraph_cypher-0.3.1.tar.gz |
|---|---|
| Size | 44.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2840b701bf30e0aa056608d9c9834f516e4eb66dc1d44bea3049a11e592f9210
|
|
BLAKE2b-256 checksum How to use checksums |
9859e85fdf67b07418154f8cbde03b5ca1af5e669c1b5401772febf38af4653a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|
Release files / mcp_agensgraph_cypher-0.3.1-py3-none-any.whl
| Download URL | mcp_agensgraph_cypher-0.3.1-py3-none-any.whl |
|---|---|
| Size | 30.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
844ad8d0b5cab9315cfe71298da38c75959263bdc43975f6abf1849ef0523246
|
|
BLAKE2b-256 checksum How to use checksums |
3f56ad205088e3344a1834a61439c409275a71525021e49500db55a80fd7dc57
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.5
|