diff --git a/MEMORY_SERVER.md b/MEMORY_SERVER.md new file mode 100644 index 0000000..6b99838 --- /dev/null +++ b/MEMORY_SERVER.md @@ -0,0 +1,152 @@ +# Memory Server (Cognee MCP) + +A persistent, centralized **AI memory** service you can attach to any agentic +harness (OpenCode, Hermes, Claude Desktop, custom agents). It runs on the Unraid +box at `192.168.86.2` as two Docker containers managed by a single compose file +(`/mnt/user/appdata/cognee/docker-compose.yml`): + +| Container | Purpose | Port (host) | +|----------------------|-------------------------------------------|-------------| +| `shane-cognee` | Cognee API + graph/vector store | `8002` (REST) | +| `shane-cognee-mcp` | MCP frontend — **Streamable HTTP** | `8003` (`/mcp`) | +| `shane-cognee-mcp-sse` | MCP frontend — **SSE** (for clients that can't do Streamable HTTP) | `8004` (`/sse`) | + +Both MCP frontends bridge to the same Cognee API; all memory state lives in the +API container, so the two frontends are interchangeable. + +## Endpoints + +- **Streamable HTTP (preferred for most clients):** `http://192.168.86.2:8003/mcp` +- **SSE (for OpenCode 1.18.21, Claude Desktop, etc.):** `http://192.168.86.2:8004/sse` +- **REST API (custom apps):** `http://192.168.86.2:8002` (docs at `/docs`) + +## Connecting an agentic harness + +Pick the transport your client supports. Use **SSE (8004)** if the client only +does SSE, otherwise **Streamable HTTP (8003)**. + +### OpenCode + +OpenCode 1.18.21 only supports SSE (`type: "remote"`). Already configured in +`~/.config/opencode/opencode.jsonc`: + +```jsonc +"mcp": { + "cognee": { + "type": "remote", + "url": "http://192.168.86.2:8004/sse" + } +} +``` + +Verify with `opencode mcp list` — it should show `✓ cognee connected`. + +### Hermes + +Hermes speaks Streamable HTTP natively. Already configured in +`~/.hermes/config.yaml` under `mcp_servers`: + +```yaml +mcp_servers: + cognee: + url: http://192.168.86.2:8003/mcp + transport: http + enabled: true +``` + +### Claude Desktop / other MCP-aware clients + +Use the SSE endpoint. Example `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "cognee": { + "url": "http://192.168.86.2:8004/sse" + } + } +} +``` + +For clients that support Streamable HTTP, point them at +`http://192.168.86.2:8003/mcp` instead. + +### Stdio bridge (legacy clients only) + +If a client can only spawn a stdio MCP server, bridge with `mcp-remote` +(pointed at the **SSE** endpoint — this `mcp-remote` build is SSE-only): + +```json +{ + "mcpServers": { + "cognee": { + "command": "npx", + "args": ["-y", "mcp-remote", "http://192.168.86.2:8004/sse"] + } + } +} +``` + +### Custom applications (REST) + +Call the Cognee REST API directly at `http://192.168.86.2:8002`. See +`/docs` for the OpenAPI schema. The MCP tools below are thin wrappers over +these endpoints. + +## Authentication + +The current deployment has no API token (`ENABLE_BACKEND_ACCESS_CONTROL: +"false"`). If you later enable auth on the Cognee API, pass the token to the +MCP frontend via the `API_TOKEN` env var in the compose file and restart the +containers. + +## LAN access / Host guard + +`cognee-mcp` enforces a DNS-rebinding Host/Origin guard. The container is +configured to accept the Unraid LAN IP via the env var +`MCP_ALLOWED_HOSTS: "192.168.86.2:*"` (comma-separated, requires the `:*` glob). + +If you connect from a client that reaches the server by a **different hostname +or IP** (e.g. a DNS name, or a different interface), add that pattern to +`MCP_ALLOWED_HOSTS` in `docker-compose.yml` and `docker compose up -d`, or you +will get `421 Misdirected Request`. Example for two hosts: + +```yaml +MCP_ALLOWED_HOSTS: "192.168.86.2:*,cognee.lan:*" +``` + +To disable the guard entirely (LAN-only, not exposed to the internet), set +`MCP_DISABLE_DNS_REBINDING_PROTECTION: "true"`. + +## Tools + +The server exposes 5 MCP tools: + +| Tool | Description | +|----------------|-------------| +| `remember` | Store data in memory. Without `session_id` → permanent memory (runs the full add + `cognify` pipeline: ingest, entity extraction, graph build). With `session_id` → fast session-cache memory only (no graph). | +| `recall` | Search memory with auto-routing and session awareness. Use this for "what do I know about X". | +| `forget` | Delete data from memory (by dataset/id). | +| `search_tools` | Find a tool by natural-language description (meta-tool for the agent). | +| `call_tool` | Invoke a tool by name with arguments (meta-tool). | + +## Usage patterns + +- **Persist something for good:** `remember` without `session_id`. This triggers + `cognify`, which can take a while (ingest + embed + graph). Don't call it in a + tight loop; batch related content into one call. +- **Scratch / per-conversation notes:** `remember` with a `session_id` for fast, + non-graph storage you don't need long-term. +- **Retrieve:** `recall` with a natural-language query. It auto-routes between + graph and vector search and is session-aware. +- **Remove:** `forget` when memory is stale. +- Keep payloads reasonable; the MCP frontend rejects uploads over 10 MB. + +## Troubleshooting + +- `421 Misdirected Request` → Host header not in `MCP_ALLOWED_HOSTS`. Add the + client's target hostname/IP (with `:*` glob) and recreate the container. +- Connection refused on `8003`/`8004` → container not healthy yet; check + `docker ps` on Unraid and the API container's health (`/health` on `8002`). +- `opencode mcp list` shows the server but not `connected` → check the URL uses + the SSE endpoint (`/sse`) for OpenCode, and that the Host pattern is allowed. diff --git a/agent/dispatch_mcp.py b/agent/dispatch_mcp.py index c740d86..187f960 100644 --- a/agent/dispatch_mcp.py +++ b/agent/dispatch_mcp.py @@ -56,3 +56,7 @@ async def _run_worker(worker: "TaskWorker", task_id: str): except Exception as e: logger.exception("Task %s crashed: %s", task_id, e) registry.fail(task_id, str(e)) + + +if __name__ == "__main__": + mcp.run(transport="stdio")