Skip to main content
Glama

graph-mcp

A Neo4j knowledge-graph index over a personal Knowledge/ markdown corpus, exposed to LLM agents over MCP. Companion to knowledge-mcp: that server owns the files, this one owns navigation.

The graph is an index, never a second copy of the corpus. Nodes carry paths, titles and hashes; agents traverse here and then read real content through knowledge-mcp's read_knowledge. The only stored text is chunk text, which has to exist to be embedded and returned as a search snippet.

Architecture

Knowledge/*.md ──> graph-sync ──> Neo4j (Bolt, private interface)
                       │                    ▲
                       │                    │
                       └─> embedding model (OpenAI-compatible endpoint)
                                            │
    LLM agent ──MCP──> graph-mcp ───────────┘

The three pieces can live on one machine or three. The reference deployment runs Neo4j on a home server and graph-sync/graph-mcp on a workstation alongside a locally-served embedding model.

Two MCP servers, deliberately split: connection profiles differ (Neo4j pooling vs plain file I/O), the graph can be added or removed independently, and scoped tool descriptions keep the model routing to the right one.

Schema

Node

Key

Notes

:Document

path

Relative to the KB root. Also title, category, subcategory, content_hash, created, updated, word_count. stub: true marks a wikilink target with no file.

:Chunk

id (path#ordinal)

text, breadcrumb, embedding (4096-d).

:Entity

key (slug)

name, type, aliases, embedding. Stage 3 only.

:Tag

name

One node per unique frontmatter tag.

Edge

Meaning

(:Document)-[:LINKS_TO]->(:Document)

Resolved [[wikilink]].

(:Document)-[:TAGGED]->(:Tag)

Frontmatter tag.

(:Document)-[:HAS_CHUNK]->(:Chunk)

Vector index membership.

(:Document)-[:MENTIONS {count}]->(:Entity)

Stage 3.

(:Entity)-[:RELATES_TO {type, confidence}]->(:Entity)

Stage 3.

(:Document)-[:SUPERSEDES]->(:Document)

From a supersedes: frontmatter key.

Related MCP server: ohmyself

Tools

Always available:

Tool

Purpose

semantic_search(query, limit)

Meaning-based retrieval over chunks. Use when wording won't match; use knowledge-mcp's search_knowledge for exact strings.

documents_by_tag(tag)

Tag navigation.

list_tags(min_documents)

Discover the corpus's tag vocabulary.

entities_in_document(path)

One note's neighbourhood: tags, links in/out, entities.

similar_documents(path, limit)

Related notes by embedding, beyond hand-written links.

graph_overview()

Node/edge counts — check which stages have run.

Registered only when GRAPH_MCP_SEMANTIC_TOOLS=1 (after Stage 3 populates the edges they traverse):

Tool

Purpose

find_related_entities(entity, max_hops)

"What connects to X", "who worked on Y".

shortest_path(entity_a, entity_b)

How two entities are connected.

recent_related_changes(entity, since)

"What's changed lately about X".

Setup

Requires uv, a Neo4j 5.x instance, and an OpenAI-compatible embeddings endpoint.

git clone https://github.com/cao-jacky/graph-mcp
cd graph-mcp
uv sync
cp .env.example .env    # set GRAPH_MCP_KB_ROOT and NEO4J_PASSWORD

Register with Claude Code:

claude mcp add --scope user graph \
  --env GRAPH_MCP_KB_ROOT=/path/to/your/Knowledge \
  --env NEO4J_URI=bolt://127.0.0.1:7687 \
  --env NEO4J_PASSWORD=... \
  -- uv run --directory /path/to/graph-mcp graph-mcp

Serving over HTTP

Desktop MCP clients spawn graph-mcp as a local stdio process and need nothing here. HTTP is for clients that cannot spawn a local process — an agent running in another container or on another host.

A container running the stdio entrypoint has nothing attached to its stdin and will simply block, so the image sets GRAPH_MCP_TRANSPORT=streamable-http.

export GRAPH_MCP_AUTH_TOKEN=$(openssl rand -hex 32)
export KB_ROOT=/path/to/your/Knowledge
export EMBED_BASE_URL=http://<host-reachable-from-the-container>:1234/v1
docker compose --profile server up -d

GRAPH_MCP_AUTH_TOKEN is required for HTTP — the server refuses to start without it rather than serving the corpus unauthenticated. Every request must carry Authorization: Bearer <token>; anything else gets a 401.

Note EMBED_BASE_URL must be reachable from inside the container. 127.0.0.1 refers to the container itself, so unless the embedding model runs there too, use the host's LAN/VPN address.

Env var

Default

Purpose

GRAPH_MCP_TRANSPORT

stdio

stdio or streamable-http

GRAPH_MCP_HTTP_HOST / _PORT / _PATH

127.0.0.1 / 8000 / /mcp

Listen address and mount path

GRAPH_MCP_AUTH_TOKEN

Required bearer token for HTTP

GRAPH_MCP_ALLOWED_HOSTS

unset

Comma-separated Host allowlist, e.g. graph-mcp:8000,10.0.0.5:*

On GRAPH_MCP_ALLOWED_HOSTS: the SDK can reject unrecognised Host headers to block DNS rebinding, but its allowlist matches exactly or on a host:* port pattern — * alone is not a wildcard and would reject everything. DNS rebinding is a browser attack, MCP clients are not browsers, and the bearer token already gates every request, so the check is disabled unless you set an allowlist.

Registering with Hermes Agent

Hermes has two unrelated extension surfaces, and this is the MCP one, not a native plugin: a repo without plugin.yaml/__init__.py is a valid MCP server but not a Hermes plugin, and hermes plugins install will say so. Add to ~/.hermes/config.yaml:

mcp_servers:
  graph:
    url: "http://graph-mcp:8000/mcp"        # or http://<host>:8000/mcp
    headers:
      Authorization: "Bearer ${GRAPH_MCP_AUTH_TOKEN}"

${VAR} resolves from ~/.hermes/.env, so put the token there and keep it out of the config file and out of any notes directory that syncs to a git remote.

Tools surface to the agent prefixed by server name — mcp_graph_semantic_search, mcp_graph_documents_by_tag, and so on.

The networks must be shared

[Errno -2] Name or service not known means exactly this and nothing else: Docker's embedded DNS resolves service names only within a shared user-defined network. An agent deployed as its own stack is on its own network, so graph-mcp is not a resolvable name there — and the two could not reach each other by IP either.

Join the client's network from this side, so the client's container is never modified or recreated:

# find it — this is the DOCKER network name, project-prefixed. It is not the
# key used in the client's compose file: `networks: {hermes-net: ...}` under
# project `hermes` becomes `hermes_hermes-net`.
docker inspect -f '{{range $k,$v := .NetworkSettings.Networks}}{{$k}} {{end}}' hermes

# then, for this stack — BOTH variables are needed
AGENT_NETWORK=hermes_hermes-net AGENT_NETWORK_EXTERNAL=true \
  docker compose --profile server up -d

AGENT_NETWORK_EXTERNAL=true says "join this, don't create it". With only AGENT_NETWORK set, Compose would try to create a network of that name and the client would not be on it.

docker network connect hermes_hermes-net graph-mcp does the same thing immediately, but is lost when the container is recreated; the variables survive redeploys.

Once shared, the client reaches http://graph-mcp:8000/mcp over that network. No host port is published, deliberately: it would only invite collisions (port 8000 is a popular default) without being needed.

If a client on another host needs it, add an override file and bind it to a private interface — never 0.0.0.0, which would expose corpus snippets to every network the host can reach:

# docker-compose.publish.yml
services:
  graph-mcp:
    ports:
      - "10.0.0.5:8000:8000"     # a VPN/LAN address
docker compose -f docker-compose.yml -f docker-compose.publish.yml \
  --profile server up -d

Build plan and validation gates

Each stage has a gate. Don't start the next stage until the current one's check passes — the two risky points are Stage 3 (extraction quality and dedup) and Stage 5 (automating before the pipeline is trustworthy).

Stage 0 — Neo4j

echo "NEO4J_PASSWORD=$(openssl rand -base64 24)" > .env
docker compose up -d
docker compose ps                      # healthy
docker compose exec neo4j cypher-shell -u neo4j -p "$NEO4J_PASSWORD" "RETURN 1"

Portainer: Stacks → Add stack → Repository, point it at this repo. The root docker-compose.yml is the stack file; set NEO4J_PASSWORD (and BOLT_BIND_ADDR, if needed) in Portainer's environment-variables editor.

Bolt publishes on 127.0.0.1 by default, which is correct when graph-sync and graph-mcp run on the same host as Neo4j. If they run elsewhere, set BOLT_BIND_ADDR to a private address — a VPN/WireGuard/Tailscale address or a LAN address. Never 0.0.0.0, which would expose Bolt to every network the host can reach.

Gate: container healthy, RETURN 1 succeeds, and uv run graph-sync status reports counts rather than a connection error.

Rollback: docker compose down -v neo4j — its own volume, no blast radius on the rest of the stack.

Stage 1 — Structural extraction

uv run graph-sync parse-check     # offline, no Neo4j needed
uv run graph-sync schema
uv run graph-sync structural

Gate: document count matches the file count (find "$GRAPH_MCP_KB_ROOT" -name '*.md' -not -path '*/.*' | grep -v '/index.md' | wc -l), and notes you know cross-reference each other show LINKS_TO edges.

Rollback: idempotent — fix the script and re-run rather than hand-cleaning. Re-running also prunes documents, edges and orphan tags that no longer exist, so renames and deletions self-correct.

Stage 1b — Vector index

uv run graph-sync embed          # ~7 min for 1408 chunks; --force to redo all

Not in the original plan; added because the local embedding model makes semantic retrieval and reliable entity dedup free. Skips unchanged documents by content hash.

Gate: graph_overview shows embedded_chunks == chunks, and semantic_search on a topic you know returns the right note in the top few.

Stage 2 — Minimal graph-mcp

Register the server (above) and leave GRAPH_MCP_SEMANTIC_TOOLS unset.

Gate: ask an agent a tag-navigation question whose answer you know ("what notes are tagged hermes") and confirm the tool is called and returns the right set — before trusting it with anything semantic.

Stage 3 — Semantic extraction

uv run graph-sync semantic --limit 8      # validate on notes you know first
uv run graph-sync semantic                # then the full backfill

Runs against the local LM Studio model by default: no API cost, and no note content leaves the machine. Entity dedup happens before insert — exact key match, then Qwen3 embedding similarity above GRAPH_MCP_ENTITY_MERGE_THRESHOLD (0.92) within the same entity type.

Throughput

Extraction dominates the run, and two settings govern it.

GRAPH_MCP_LLM_CONCURRENCY (default 1) parallelises both across documents and across the windows of a single long document, with a shared cap so the two levels cannot multiply. Measured on a local llama.cpp: 1.79x at 4, 1.4x at 2.

Raising it is only safe if the server has the context to match. Local servers commonly split one context budget across slots, so N concurrent requests each get n_ctx/N tokens. At n_ctx=8192, four-way concurrency leaves ~2048 tokens per request and every real note fails with Context size has been exceeded — while the same note succeeds serially. Budget roughly 8192 tokens per concurrent request, and raise n_ctx before raising concurrency.

Note that batched decoding changes floating-point accumulation order, so extraction stops being reproducible even at temperature: 0 — the same note can yield a different entity set between runs. Set concurrency to 1 if you need determinism more than speed.

Reasoning models must have reasoning disabled. Measured on a 28-word note, the model spent 2744 reasoning tokens to produce ~200 tokens of JSON: 93% of generation, 103s instead of 14s. reasoning_effort: "none" is sent for this; verify any change against usage.completion_tokens_details.reasoning_tokens rather than latency, because an ignored parameter still returns a valid response.

Gate: inspect the extraction for 5–10 notes you know well before running at scale. Watch for systematic misses — bullet-heavy notes tend to yield fewer relations than prose. Check find_related_entities on a familiar entity for wrongly-merged or wrongly-split entities; tune the threshold and re-run rather than cleaning up afterwards.

Rollback: additive and idempotent per document.

MATCH ()-[r:RELATES_TO]->() DELETE r;
MATCH (e:Entity) DETACH DELETE e;
MATCH (d:Document) REMOVE d.extracted_hash;

Stage 1 data is untouched by this.

Stage 4 — Relationship tools

Set GRAPH_MCP_SEMANTIC_TOOLS=1 and restart the MCP client.

Gate: ask a genuinely multi-hop question you don't already know the answer to and check the returned path is sane. This is the first point where the graph does something plain search could not.

Stage 5 — Automation

Only after Stages 1–4 have been run by hand enough times to trust their behaviour on renames, deletions and malformed frontmatter.

cp deploy/graph-sync.{service,timer} ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now graph-sync.timer

The timer runs structural + embed only; the semantic pass stays manual.

Gate: edit one note, wait for the trigger, confirm the graph updated without a manual run. Keep the manual path working as an escape hatch.

Configuration

Env var

Default

Purpose

GRAPH_MCP_KB_ROOT

falls back to KNOWLEDGE_MCP_ROOT, then ~/Knowledge

Corpus root. Must exist.

NEO4J_URI

bolt://127.0.0.1:7687

Bolt endpoint.

NEO4J_USER / NEO4J_PASSWORD / NEO4J_DATABASE

neo4j / — / neo4j

Credentials.

GRAPH_MCP_EMBED_BASE_URL

http://127.0.0.1:1234/v1

LM Studio OpenAI-compatible endpoint.

GRAPH_MCP_EMBED_MODEL

text-embedding-qwen3-embedding-8b

Embedding model.

GRAPH_MCP_EMBED_DIM

4096

Must match the model and the vector index.

GRAPH_MCP_EMBED_BATCH

16

Texts per embedding request.

GRAPH_MCP_LLM_BASE_URL / _MODEL / _API_KEY

LM Studio / qwen3.5-122b-a10b / lm-studio

Stage 3 extraction.

GRAPH_MCP_CHUNK_WORDS / _OVERLAP

350 / 60

Chunk sizing.

GRAPH_MCP_ENTITY_MERGE_THRESHOLD

0.92

Cosine similarity above which two entities merge.

GRAPH_MCP_SEMANTIC_TOOLS

unset

1 registers the Stage 4 tools.

Corpus quirks the parser handles

Discovered by running against the real 277-note corpus; the tests in tests/test_parse.py pin each one:

  • Most notes have no frontmatter. 163 of 277 (the imported ai-systems/ tree). Absent frontmatter is the common case, not an error; dates fall back to file mtime.

  • Nested subdirectories. ai-systems/08-memory-and-state/*.md sits two levels deep. The walk is fully recursive.

  • Wikilinks inside code must not become edges. A grep "^[[:space:]]*$" snippet and prose about `[[wikilinks]]` would otherwise create bogus nodes. Fenced blocks and inline code are blanked before extraction.

  • The synced *-SKILL.md notes have malformed fencing — a bare ``` preview block containing further ``` fences — so by CommonMark their shell snippets are not* code. A plausibility filter rejects targets containing : or `while keeping real names likeCyberpunk 2077`.

  • All 56 skill notes open with # SKILL.md. A heading that is merely a filename is rejected in favour of the synced_from: directory name.

  • Tags come in both inline ([a, b]) and block (- a) YAML form.

  • Short sections are packed together. One chunk per heading gave 4024 chunks averaging 90 words; packing yields 1408 averaging 258.

Troubleshooting

Everything here was hit during a real deployment, in this order.

Could not perform discovery. No routing servers available

You are connecting with the neo4j:// scheme, which performs cluster routing discovery that a single instance does not offer. Use bolt:// — in the Browser's connect dialog, in NEO4J_URI, everywhere. neo4j:// is only for clusters and Aura.

AuthError after setting NEO4J_USER, or an unknown-database error

Neo4j Community Edition has exactly one user and one database, both named neo4j. CREATE USER and CREATE DATABASE are Enterprise features, and SHOW DATABASES returns only neo4j and system. The compose file's NEO4J_AUTH creates neo4j/<password>; leave NEO4J_USER and NEO4J_DATABASE at their defaults.

If you want an isolated graph, run a second container with its own volume — that is the Community-edition equivalent of a second database.

Bolt works but the Browser doesn't (or vice versa)

BOLT_BIND_ADDR and BROWSER_BIND_ADDR are independent, and default to 127.0.0.1 separately. Publishing one does not publish the other.

This matters for SSH tunnels: -L 7687:127.0.0.1:7687 resolves 127.0.0.1 on the server, so it only works if that port is published on the server's loopback. If you set BOLT_BIND_ADDR=10.0.0.5, the tunnel must target that address:

ssh -L 7474:127.0.0.1:7474 -L 7687:10.0.0.5:7687 user@server

Or skip the tunnel for Bolt and point the Browser straight at bolt://10.0.0.5:7687, which is what graph-sync uses anyway.

The server restarts mid-embed, or Bolt writes fail with ServiceUnavailable

The container is being OOM-killed, and restart: unless-stopped brings it back — so the port looks healthy afterwards and the cause is easy to miss. Confirm it:

CALL dbms.queryJmx('java.lang:type=Runtime') YIELD attributes
RETURN attributes.Uptime.value / 60000 AS uptime_minutes

An uptime far shorter than the container's age is the tell. Check heap too — if heap use is low, the JVM is fine and it is the container limit being hit, not the heap.

NEO4J_MEM_LIMIT must cover heap + pagecache + JVM overhead (metaspace, direct buffers, thread stacks) and Lucene's off-heap allocations, which are substantial when building 4096-dim vector indexes. The 4g default with a 2G heap and 1G pagecache is marginal for a full embedding pass; 8g is a safer floor for vector workloads. memswap_limit equals mem_limit by design, so there is no swap cushion — the limit has to be genuinely sufficient.

The embed stage watermarks each document as its last chunk lands, so a run killed this way keeps completed documents; just re-run it.

Tests

uv run python tests/test_parse.py     # 23 checks, no Neo4j or network needed

Known gap in knowledge-mcp

knowledge-mcp's _entry_files() walks only the root and one level of category directories, so the ~135 notes nested deeper (ai-systems/*/*.md) are invisible to search_knowledge, list_knowledge and the generated index.md. graph-mcp indexes them, which means semantic_search can return a path that read_knowledge will still happily read but that search_knowledge would never have found. Worth fixing there separately.

Available Tools

6 tools
documents_by_tagA

List documents carrying a frontmatter tag, e.g. "hermes" or "homelab".

Use for tag-based navigation — "what notes are tagged X". Call list_tags first if unsure which tags exist. Returns paths; read content via knowledge-mcp's read_knowledge.

ParametersJSON Schema
NameRequiredDescriptionDefault
tagYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It discloses that the output is paths (not content) and directs the user to read_knowledge for content, adding meaningful behavioral context. However, it does not mention error handling, case sensitivity, or explicitly state that it is a read-only operation beyond the listing verb.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences front-load the core purpose, then add usage context and return-value guidance without any fluff. Every sentence earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple (one parameter) and an output schema exists, so the description does not need to explain return values in detail. It covers purpose, usage, parameters, and even the next step (read_knowledge), making it complete for the tool's complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 0% description coverage, but the description explains the 'tag' parameter as a 'frontmatter tag' and gives examples, which is exactly what the schema lacks. It also connects the parameter to list_tags, effectively compensating for the schema gap.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific action 'List documents carrying a frontmatter tag' with concrete examples, clearly distinguishing it from siblings like list_tags and semantic_search. The verb+resource+condition structure makes the tool's purpose immediately obvious.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It explicitly states the intended use case ('tag-based navigation') and provides an actionable prerequisite ('Call list_tags first if unsure which tags exist'). This gives the agent clear when-to-use guidance and points to an alternative tool, satisfying the highest bar.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

entities_in_documentA

Show what a specific note is about: its tags, its wikilinks in and out, and (once the semantic pass has run) the entities it mentions.

Use when you already know which note you care about and want its neighbourhood — "what does this note connect to". path is relative to the knowledge base root, e.g. "projects/my-project.md".

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the transparency burden. It discloses that results include tags, wikilinks, and entities, and importantly notes the conditional dependency: entities appear 'once the semantic pass has run.' This goes beyond the name and schema, though it doesn't mention auth, side effects, or error behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise: two sentences deliver the core purpose, usage context, and parameter guidance. It is front-loaded with the action and outputs, making it easy for an agent to parse quickly.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the output schema exists (so return format is covered), the description provides all necessary context: what to use it for, when to use it, the conditional semantic pass, and path semantics. Sibling tool names further clarify boundaries. The description is complete for this simple, single-parameter tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has one parameter with no description (0% schema coverage), so the description compensates by explaining that `path` is 'relative to the knowledge base root' and gives a concrete example ('projects/my-project.md'). This adds meaningful semantic information, though it leaves minor details like extension requirements implied.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function with a specific verb: 'Show what a specific note is about' and lists the concrete outputs (tags, wikilinks in/out, entities). It distinguishes itself from siblings by focusing on a known note's neighborhood rather than search or global aggregation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives explicit guidance: 'Use when you already know which note you care about and want its neighbourhood.' It doesn't explicitly name alternative tools or say when not to use it, but the usage context is clear and distinct from sibling tools like semantic_search or similar_documents.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

graph_overviewA

Report what the graph currently contains — node and edge counts per type.

Use to check which extraction stages have run before trusting a query that depends on them.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden. It conveys a read-only reporting action and adds interpretive value by explaining the counts indicate which extraction stages have run. Although it does not explicitly state 'no side effects', the verb 'Report' strongly implies a non-mutating operation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise and front-loaded: the first sentence states the core function, and the second provides usage context. Every word earns its place with no redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter overview tool with an output schema, the description is complete. It tells the user what to expect (node/edge counts per type), why to use it (checking extraction stages), and this is sufficient for an agent to select and invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the baseline is 4. The description adds no parameter-specific details, which is appropriate since there is nothing to document.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool reports the current graph contents with node and edge counts per type. The verb 'Report' with the specific resource ('graph') and what it returns makes the purpose unambiguous, and it is distinct from sibling tools like semantic_search or list_tags.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear guidance on when to use it: 'Use to check which extraction stages have run before trusting a query that depends on them.' It does not explicitly mention when not to use it or name alternatives, but the context is sufficiently clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_tagsA

List every tag in the corpus with how many documents use it.

Use to discover the corpus's own vocabulary before calling documents_by_tag.

ParametersJSON Schema
NameRequiredDescriptionDefault
min_documentsNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description provides core behavioral information: it lists tags and their document counts, implying a read-only operation. However, it does not disclose the effect of the min_documents parameter or any other behavioral nuances, but for a simple list tool, the main behavior is adequately transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the main action, no redundant information. The structure is clean and efficient, earning a top score for conciseness.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the core purpose and usage context well. The output schema is present, so return values are defined. The main gap is the unexplained min_documents parameter, but overall the description is fairly complete for a simple tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description does not mention the min_documents parameter at all. The parameter's purpose is only inferable from its name, but the description adds no meaning beyond the schema, so the agent lacks clear semantic guidance.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a clear verb and resource: "List every tag in the corpus with how many documents use it." It specifically differentiates from the sibling tool documents_by_tag by positioning itself as a vocabulary discovery step, which is evident from the second sentence.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly directs when to use the tool: "Use to discover the corpus's own vocabulary before calling documents_by_tag." This gives a clear context and names a specific sibling alternative, fulfilling the requirement for usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

similar_documentsA

Find notes covering similar ground to a given note, by embedding similarity rather than by explicit links.

Use to surface related material the wikilinks miss — near-duplicates, or an older note on the same topic. Complements entities_in_document, which only sees links you wrote by hand.

ParametersJSON Schema
NameRequiredDescriptionDefault
pathYes
limitNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden, and it explains the core behavior: embedding-based similarity rather than explicit links, and what kinds of results to expect (near-duplicates, older notes). It does not mention edge cases like missing paths or limit semantics, but the main behavioral trait is clearly disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three tight sentences front-load the core purpose, then add usage context and a sibling comparison. Every sentence earns its place with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple two-parameter tool with an output schema, this is largely complete: you know what it does, why you would use it, and how it differs from a sibling. The only gap is explicit parameter guidance, which the input schema's defaults partially cover.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It refers to a 'given note' but never explicitly describes the required 'path' parameter or how 'limit' affects results; only the schema's minimal titles and default provide any information.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb and resource: 'Find notes covering similar ground to a given note'. It clearly distinguishes the tool from siblings by stating it uses 'embedding similarity rather than by explicit links', and explicitly contrasts it with entities_in_document.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives concrete scenarios: 'surface related material the wikilinks miss — near-duplicates, or an older note on the same topic'. It names an alternative (entities_in_document) and explains the difference, though it does not discuss when not to use the tool relative to semantic_search.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 6 tool updatesv0.1.0
    • First observeddocuments_by_tag
    • First observedentities_in_document
    • First observedgraph_overview
    • First observedlist_tags
    • First observedsemantic_search
    • First observedsimilar_documents

TDQS

A4.4/5.0
Disambiguation5/5

Each tool addresses a distinct concern: semantic search, tag listing, tag-based document retrieval, per-document entity/neighborhood details, document similarity, and overall graph statistics. There is no meaningful overlap; even semantic_search and similar_documents differ by input type (query text vs. document path).

Naming Consistency3/5

Names are readable and follow snake_case, but they mix verb-led patterns (list_tags) with noun-phrase patterns (documents_by_tag, entities_in_document, similar_documents, graph_overview, semantic_search). This is a mild inconsistency rather than chaos, since each name is still descriptive.

Tool Count5/5

Six tools form a focused, well-scoped set for a knowledge-graph exploration server. The count is neither sparse nor bloated, and each tool earns its place by addressing a distinct query pattern.

Completeness5/5

The server covers the full range of graph navigation queries: search by meaning, browse by tags, inspect a document's neighborhood, find similar documents, and get a structural overview. Gaps like reading raw content or exact-string search are explicitly delegated to the complementary knowledge-mcp server, so no dead ends remain within this server's intended domain.

Maintenance

ActivityMaintained
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Builds a persistent knowledge graph from notes and conversations, enabling semantic search, entity exploration, and GTD task management from any MCP-compatible AI assistant.
    3
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Exposes a personal markdown-based second brain (Obsidian-style) as an MCP server, enabling agents to search, read, and write notes with privacy controls.
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    MCP server for managing a local, domain-agnostic knowledge base using Markdown notes with frontmatter. Enables AI agents to capture, read, search, link, and maintain notes with atomic writes and privacy controls.
    13
    MIT
  • F
    license
    A
    quality
    C
    maintenance
    Exposes Obsidian notes as a semantic search and RAG knowledge base over MCP, enabling AI assistants to index, retrieve, and analyze personal notes via natural language.
    7
    1
    -

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/cao-jacky/graph-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server