Agents and MCP¶
graphwalk's MCP server gives any MCP client (Claude Code, Claude Desktop, Cursor, your own harness) the graph as tools.
What we measured¶
On WebQSP (100 questions; 30 with a strong agent), one tool-calling loop, same model and prompt, only the tools varied (section A8 of the phase 7 results):
First 30 questions (A8b), all four tool sets:
| Agent tools | F1, strong agent | $/question | p50 latency |
|---|---|---|---|
| none (closed book) | 0.57 | 0.011 | 7 s |
relations + neighbors |
0.71 | 0.042 | 30 s |
the same + walk |
0.75 | 0.032 | 24 s |
| dense search over the same facts | 0.72 | 0.028 | 40 s |
- Over all 100 questions (A8b + P4),
walkcut the strong agent's cost by 17% (95% CI 8–27%; 25% on the first 30) at equal accuracy (F1 0.755 vs 0.747). The agent still verified confident walks; it explored less and reasoned less. - With a cheap agent model,
walksaved 70% of tokens on questions where the first walk was confident (≥ 0.9), but the cost overall was a wash: the walk's own calls cost about as much as the cheap tokens it saved. - Text search over the same facts was as accurate, on a graph whose names the model
knows. With the names replaced by aliases (P3), agents with graph tools beat it by
0.25–0.32 F1, and
walkadded +0.07 F1 (CI includes 0).
Tools¶
| Tool | Returns |
|---|---|
walk |
Answers reached from the question's entities, each with path and confidence; escalated per the server's threshold |
neighbors, get_node |
Explore an entity's edges and attributes |
status |
What is indexed |
locate, read |
Documents: source spans for a question, then their text |
ingest, ingest_status |
Add documents (async job) |
The resource graphwalk://guide serves the agent guide. The server's
instructions tell the agent the usual loop: walk first; at confidence 0.9 or above,
use the answer; below it, verify with neighbors (or the walk is escalated, if the
server sets GRAPHWALK_ESCALATE_BELOW). walk does not apply constraints: the agent
filters its answers.
stdio: a local graph file¶
pip install 'graphwalk[mcp]'
claude mcp add --env OPENROUTER_API_KEY=sk-or-... --transport stdio graphwalk \
-- graphwalk mcp --db ~/graphs/kg.db
Generic client config (examples/mcp/stdio.json):
{
"mcpServers": {
"graphwalk": {
"command": "graphwalk",
"args": ["mcp", "--db", "/path/to/kg.db"],
"env": { "OPENROUTER_API_KEY": "sk-or-..." }
}
}
}
Keys come from the server's environment.
Remote HTTP: a shared server or the container¶
The server refuses to bind beyond localhost without auth:
export GRAPHWALK_AUTH=bearer GRAPHWALK_AUTH_TOKEN=$(openssl rand -hex 24)
graphwalk mcp --http --db kg.db --host 0.0.0.0 --port 8080
# or the container:
docker run -p 8080:8080 -v graphwalk-data:/data \
-e GRAPHWALK_AUTH=bearer -e GRAPHWALK_AUTH_TOKEN=... ghcr.io/manvirchakal/graphwalk
Clients send their own provider keys as headers (X-OpenRouter-API-Key,
X-Anthropic-API-Key, ...; any setting works as X-Graphwalk-...). The server uses
them for that session only, keeps them in memory, and never logs them. Its own keys are
used only if the operator sets GRAPHWALK_SERVER_KEYS=true.
claude mcp add --transport http graphwalk https://graphwalk.example.com/mcp \
--header "Authorization: Bearer $GRAPHWALK_AUTH_TOKEN" \
--header "X-OpenRouter-API-Key: $OPENROUTER_API_KEY"
Use hyphenated header names: proxies such as nginx drop headers with underscores.
Server settings¶
| Variable | Default | |
|---|---|---|
GRAPHWALK_DB |
graphwalk.db |
The SQLite graph (/data/graphwalk.db in the container) |
GRAPHWALK_AUTH |
none |
none (localhost only), bearer, or oauth |
GRAPHWALK_AUTH_TOKEN |
Bearer token, 32+ characters | |
GRAPHWALK_RESOURCE_URL |
Public URL of /mcp (oauth) |
|
GRAPHWALK_OAUTH_ISSUER |
Your authorization server (oauth) | |
GRAPHWALK_OAUTH_AUDIENCE |
resource URL | Expected aud |
GRAPHWALK_OAUTH_SCOPES |
Required scopes, comma-separated | |
GRAPHWALK_OAUTH_JWKS_URL |
discovered | JWT signing keys |
GRAPHWALK_OAUTH_INTROSPECTION_URL |
Validate opaque tokens instead (with ..._CLIENT_ID/..._CLIENT_SECRET) |
|
GRAPHWALK_ALLOWED_HOSTS |
Host names clients use (DNS-rebinding protection) | |
GRAPHWALK_ALLOWED_BASE_URLS |
Base URLs clients may point providers at via headers | |
GRAPHWALK_SERVER_KEYS |
false |
Let clients use the server's own provider keys |
GRAPHWALK_INGEST_ROOT |
Directory remote ingest may read; otherwise inline documents only |
|
GRAPHWALK_MAX_K, _MAX_READ_CHARS, _SESSION_CONCURRENCY, _MAX_INGEST_CHARS |
50, 20000, 4, 5000000 | Request limits |
GRAPHWALK_LOG_LEVEL |
WARNING |
With oauth, graphwalk is a resource server (MCP authorization spec): it serves
/.well-known/oauth-protected-resource/mcp pointing clients at your issuer, and checks
each token's signature, issuer, audience, expiry, and scopes. It does not issue tokens.
A harness from scratch¶
examples/harness.py is a small tool-calling agent (about
150 lines) that talks to graphwalk over either transport. The harness used for the
measurements above is graphwalk.eval.agent (see
scripts/eval/agent_arms.py).