graph-mcp
This server provides an MCP interface to a Neo4j knowledge graph index over a personal markdown corpus, enabling semantic and structural navigation. It integrates with knowledge-mcp for full content retrieval.
semantic_search(query, limit)— Meaning-based retrieval over document chunks. Returns matching chunks with document paths, section breadcrumbs, and similarity scores for further reading viaknowledge-mcp.documents_by_tag(tag)— List all notes carrying a specific frontmatter tag; useful for tag-based navigation.list_tags(min_documents)— Discover the full tag vocabulary and see how many documents use each tag, aiding tag selection.entities_in_document(path)— Inspect a note's neighborhood: its tags, outgoing/incoming wikilinks, and (if semantic extraction ran) mentioned entities.similar_documents(path, limit)— Find notes with similar embeddings to a given note, uncovering related material beyond explicit links.graph_overview()— Report node and edge counts per type, allowing you to check which extraction stages have run.
Provides a knowledge-graph index over a markdown corpus in Neo4j, enabling semantic search, tag navigation, document similarity, and entity relationship discovery.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@graph-mcpfind notes related to distributed systems"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
|
| Relative to the KB root. Also |
|
|
|
|
|
|
|
| One node per unique frontmatter tag. |
Edge | Meaning |
| Resolved |
| Frontmatter tag. |
| Vector index membership. |
| Stage 3. |
| Stage 3. |
| From a |
Related MCP server: ohmyself
Tools
Always available:
Tool | Purpose |
| Meaning-based retrieval over chunks. Use when wording won't match; use |
| Tag navigation. |
| Discover the corpus's tag vocabulary. |
| One note's neighbourhood: tags, links in/out, entities. |
| Related notes by embedding, beyond hand-written links. |
| 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 |
| "What connects to X", "who worked on Y". |
| How two entities are connected. |
| "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_PASSWORDRegister 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-mcpServing 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 -dGRAPH_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 |
|
|
|
|
| Listen address and mount path |
| — | Required bearer token for HTTP |
| unset | Comma-separated |
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 -dAGENT_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 addressdocker compose -f docker-compose.yml -f docker-compose.publish.yml \
--profile server up -dBuild 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 structuralGate: 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 allNot 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 backfillRuns 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.timerThe 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 |
| falls back to | Corpus root. Must exist. |
|
| Bolt endpoint. |
|
| Credentials. |
|
| LM Studio OpenAI-compatible endpoint. |
|
| Embedding model. |
|
| Must match the model and the vector index. |
|
| Texts per embedding request. |
| LM Studio / | Stage 3 extraction. |
|
| Chunk sizing. |
|
| Cosine similarity above which two entities merge. |
| unset |
|
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/*.mdsits 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.mdnotes 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 thesynced_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@serverOr 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_minutesAn 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 neededKnown 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 toolsdocuments_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.
| Name | Required | Description | Default |
|---|---|---|---|
| tag | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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".
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| min_documents | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
semantic_searchA
Find passages in the knowledge base by meaning rather than keywords.
Use when the question is conceptual or the user's wording is unlikely to match the note's wording verbatim — "how do I handle X", "what did I decide about Y". For exact strings, literal identifiers, or filenames, prefer knowledge-mcp's search_knowledge instead.
Returns the best-matching chunks with their document path, section breadcrumb, and similarity score. Read the full note via knowledge-mcp's read_knowledge using the returned path.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | ||
| query | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the transparency burden. It discloses the method (semantic matching), the return contents (document path, section breadcrumb, similarity score), and the recommended follow-up. It does not explicitly state non-destructiveness or error/limit behavior, but for a read-only search tool the provided behavioral context is strong.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is well-structured and concise: it opens with the core purpose, follows with usage guidance, names an alternative, and closes with return details and a follow-up action. Every sentence contributes necessary information without wasted words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's moderate complexity, the description covers purpose, use cases, exclusions, return contents, and a follow-up workflow. An output schema exists, so return type details are not needed in the description. The description is complete for an agent to select and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
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 clarifies that the 'query' is meant to be a natural-language conceptual phrase, but it does not describe the 'limit' parameter beyond what the schema's default value implies. The description adds partial value for query semantics but under-explains limit.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states 'Find passages in the knowledge base by meaning rather than keywords' — a specific verb and resource. It also distinguishes itself from keyword-based search by naming search_knowledge as the alternative, which helps an agent understand the tool's specific role even among sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit usage guidance is provided: 'Use when the question is conceptual or the user's wording is unlikely to match the note's wording verbatim' and 'For exact strings, literal identifiers, or filenames, prefer knowledge-mcp's search_knowledge instead.' This clearly states when to use and when not to use, plus a follow-up call to read_knowledge.
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.
| Name | Required | Description | Default |
|---|---|---|---|
| path | Yes | ||
| limit | No |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
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.
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.
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.
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.
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.
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.
6 tool updates
v0.1.0- First observed
documents_by_tag - First observed
entities_in_document - First observed
graph_overview - First observed
list_tags - First observed
semantic_search - First observed
similar_documents
TDQS
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).
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.
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.
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
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
Personal knowledge graph as an AI memory layer over MCP - read, save, and link your memories.
Serve a folder of Markdown notes as an MCP server: hybrid search, reading, and sourced answers.
Agent-native MCP server over the public saagarpatel.dev corpus. Read-only, stateless.
Markdown-based note-taking with a hosted MCP server. Your notes serve you and your AI.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceBuilds a persistent knowledge graph from notes and conversations, enabling semantic search, entity exploration, and GTD task management from any MCP-compatible AI assistant.3-
- AlicenseNot gradedqualityBmaintenanceExposes a personal markdown-based second brain (Obsidian-style) as an MCP server, enabling agents to search, read, and write notes with privacy controls.MIT
- AlicenseAqualityAmaintenanceMCP 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.13MIT
- FlicenseAqualityCmaintenanceExposes 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.71-
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
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