Skip to content

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