Set up the MCP server¶
ZettelForge ships a Model Context Protocol (MCP) server over stdio. Once wired in, any MCP-compatible AI agent or IDE can call seven tools to read, write, and reason over your local CTI knowledge base without writing a line of integration code.
This guide takes you from a fresh pip install to a working Claude Code or Claude Desktop session with ZettelForge tools available.
Prerequisites:
- ZettelForge 2.7.0+ installed:
pip install zettelforge - Python 3.10+
- An MCP-compatible host (Claude Code, Claude Desktop, Cursor, or any stdio MCP client)
Step 1: Verify the server starts¶
Before touching any config files, confirm the server responds:
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}' \
| python3 -m zettelforge.mcp
The server prints:
{"jsonrpc": "2.0", "id": 1, "result": {"protocolVersion": "2024-11-05", "capabilities": {"tools": {"listChanged": false}}, "serverInfo": {"name": "zettelforge", "version": "2.7.0"}}}
If you get ModuleNotFoundError, check that python3 resolves to the environment where you installed ZettelForge. See Step 3 below.
Step 2: Wire in your MCP host¶
Claude Code¶
Create or edit .claude.json in your project root (global: ~/.claude/.claude.json):
{
"mcpServers": {
"zettelforge": {
"command": "python3",
"args": ["-m", "zettelforge.mcp"]
}
}
}
Claude Desktop¶
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the equivalent path on your OS:
{
"mcpServers": {
"zettelforge": {
"command": "python3",
"args": ["-m", "zettelforge.mcp"]
}
}
}
Restart Claude Desktop after saving.
Cursor¶
Open Settings → MCP → Edit config and add the same mcpServers block above.
Any stdio MCP client¶
Point the client at:
command: python3
args: ["-m", "zettelforge.mcp"]
The server reads from stdin and writes to stdout. It prints no startup banner — the first line of output is always a JSON-RPC response.
Step 3: Virtualenv paths¶
If ZettelForge is installed in a virtual environment, the system python3 will not find it. Use the full path to the virtualenv interpreter:
{
"mcpServers": {
"zettelforge": {
"command": "/home/user/.venvs/zettelforge/bin/python",
"args": ["-m", "zettelforge.mcp"]
}
}
}
To find the right path:
which python3 # if ZettelForge is in the active venv
# or
source ~/.venvs/zettelforge/bin/activate && which python3
Step 4: Verify tools are available¶
In Claude Code, after reloading:
What tools do you have available from zettelforge?
Claude Code lists seven tools:
| Tool | Purpose |
|---|---|
zettelforge_remember |
Store threat intelligence; extracts entities, evolves memory |
zettelforge_recall |
Blended vector + graph retrieval |
zettelforge_synthesize |
RAG answer from stored memory |
zettelforge_entity |
O(1) lookup by entity type and value |
zettelforge_graph |
Traverse the knowledge graph from an entity |
zettelforge_stats |
Total notes, entity counts, retrieval count |
zettelforge_sync |
OpenCTI sync (ThreatRecall.ai SaaS only) |
To confirm from the command line:
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
| python3 -m zettelforge.mcp
The second line of output is the full tools/list response with all seven schemas.
Step 5: Test a live tool call¶
Call zettelforge_stats to confirm the server connects to your knowledge base:
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{}}}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"zettelforge_stats","arguments":{}}}' \
| python3 -m zettelforge.mcp
A populated store returns something like:
{
"version": "2.7.0",
"total_notes": 4594,
"retrievals": 0,
"entity_index": {
"intrusion_set": {"unique_entities": 35, "total_mappings": 2228},
"cve": {"unique_entities": 16, "total_mappings": 145},
"tool": {"unique_entities": 6, "total_mappings": 98}
}
}
total_notes: 0 on a fresh install is correct — you haven't stored anything yet. See the Quickstart for how to populate your store.
Configuration options¶
The server respects ZETTELFORGE_DATA_DIR and your ~/.amem/config.yaml (or the path from ZETTELFORGE_CONFIG). No additional environment variables are required to start the MCP server. The server defaults to SQLite backend (ZETTELFORGE_BACKEND=sqlite).
To override data directory for a specific host config:
{
"mcpServers": {
"zettelforge": {
"command": "python3",
"args": ["-m", "zettelforge.mcp"],
"env": {
"ZETTELFORGE_DATA_DIR": "/data/cti-memory"
}
}
}
}
Backward-compatible entry point¶
Existing .mcp.json configurations that reference web/mcp_server.py continue to work — it is a retained shim that calls the same run_stdio() function. New installations should use python -m zettelforge.mcp.
TLP and trust boundaries¶
zettelforge_recall, zettelforge_synthesize, and zettelforge_entity all accept an optional actor field (caller identity string) and tlp_override_reason. Without actor, the server treats the caller as unauthenticated and caps shared content at TLP:GREEN. AMBER and RED content requires both a valid actor and a tlp_override_reason.
All note content in MCP responses is wrapped with content_is_untrusted: true. Your host must treat it as potentially adversarial input.
See the MCP protocol reference for full JSON-RPC request and response schemas and the complete TLP policy.
zettelforge_sync and OpenCTI (ThreatRecall.ai SaaS)¶
zettelforge_sync is listed in the tool schema and appears in tools/list. In the ZettelForge OSS release it returns a JSON-RPC error response indicating that sync requires the SaaS extension. The tool is present in the schema so MCP host configs do not need to change when connecting to ThreatRecall.ai.
OpenCTI sync is a ThreatRecall.ai SaaS capability.
Next steps¶
- Memory Manager API — call
remember(),recall(), andsynthesize()directly from Python - MCP protocol reference — full JSON-RPC schemas, error codes, and the lazy-singleton lifecycle
- Integrate with your LLM agent — wire ZettelForge into a custom agent loop