From ebdaafa52d57add1afdd5a0a25536c55120ea3bd Mon Sep 17 00:00:00 2001 From: Atlas Date: Wed, 1 Jul 2026 22:13:50 +0200 Subject: [PATCH] =?UTF-8?q?Add=20MCP=20server=20=E2=80=94=20expose=20brain?= =?UTF-8?q?=20(recall/remember/ingest/stats)=20to=20any=20MCP=20host?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Makes SecondBrain a first-class connector for Claude Desktop, Open WebUI, LibreChat, Cursor, and any MCP-speaking client. Optional requirements-mcp.txt. Atlas Corporation proprietary. --- README.md | 21 +++++++++++ mcp_server.py | 90 ++++++++++++++++++++++++++++++++++++++++++++ requirements-mcp.txt | 3 ++ 3 files changed, 114 insertions(+) create mode 100755 mcp_server.py create mode 100644 requirements-mcp.txt diff --git a/README.md b/README.md index 86698fb..768fa6c 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,27 @@ docker run --rm -v sbdata:/data -e OLLAMA_URL=http://host.docker.internal:11434 secondbrain recall "quarterly numbers" --hybrid ``` +## Plug into any AI (MCP server) + +Expose your brain as tools to any MCP host — **Claude Desktop, Open WebUI, LibreChat, Cursor**, +etc. — so the assistant gains local, private, long-term memory: + +```bash +pip install "mcp[cli]" +python3 mcp_server.py # stdio MCP server +``` + +Add to your MCP host's config: +```json +{ "mcpServers": { "secondbrain": { + "command": "python3", "args": ["/opt/secondbrain/mcp_server.py"], + "env": { "BRAIN_HOME": "/opt/secondbrain/data", "OLLAMA_URL": "http://localhost:11434" } } } } +``` + +Tools exposed: **`recall`** (hybrid search of your memory), **`remember`** (store a note, scrubbed), +**`ingest_path`** (index a folder), **`stats`**. This is how SecondBrain becomes a first-class +*connector* in a Claude-style product surface — the same way MCP servers extend Claude. + ## Use it as a library ```python diff --git a/mcp_server.py b/mcp_server.py new file mode 100755 index 0000000..d8b96bb --- /dev/null +++ b/mcp_server.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# SecondBrain — (c) 2026 Atlas Corporation. All Rights Reserved. Proprietary; see LICENSE. +"""SecondBrain MCP server — expose your brain as tools to any MCP host. + +Add this server to Claude Desktop, Open WebUI, LibreChat, Cursor, or any MCP client, +and the assistant gains long-term, local, privacy-scrubbed memory + recall. + +Run (stdio transport, the default MCP wiring): + pip install "mcp[cli]" + python3 mcp_server.py + +Example Claude Desktop / MCP host config: + { + "mcpServers": { + "secondbrain": { + "command": "python3", + "args": ["/opt/secondbrain/mcp_server.py"], + "env": { "BRAIN_HOME": "/opt/secondbrain/data", + "OLLAMA_URL": "http://localhost:11434" } + } + } + } + +Tools exposed: recall · remember · ingest_path · stats. +Config is the same env as the engine (BRAIN_HOME, OLLAMA_URL, …). +""" +import os, sys +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import brain + +try: + from mcp.server.fastmcp import FastMCP +except Exception: + sys.stderr.write("SecondBrain MCP needs the MCP SDK: pip install \"mcp[cli]\"\n") + raise + +mcp = FastMCP("secondbrain") + + +@mcp.tool() +def recall(query: str, k: int = 6, hybrid: bool = True) -> list: + """Search long-term memory (the Second Brain). Returns the top-k most relevant + stored snippets with their source, project, timestamp and relevance score. + Use this to remember prior decisions, facts, documents, emails, code, and context. + `hybrid=True` fuses semantic + exact-keyword search (best for names/IDs/paths).""" + fn = brain.brain_recall_hybrid if hybrid else brain.brain_recall + return fn(query, k) + + +@mcp.tool() +def remember(text: str, project: str = "note", ref: str = "manual") -> str: + """Store a note/fact in long-term memory. Credentials are scrubbed automatically. + It becomes searchable immediately (lexical) and semantically after the next embed.""" + conn = brain.db() + n = brain._stage(conn, "note", project, "", ref, "", "note", text) + conn.commit(); conn.close() + try: + brain.embed(limit=64) + except Exception: + pass + return f"stored {n} new chunk(s)" + + +@mcp.tool() +def ingest_path(path: str) -> str: + """Index a local directory of files (code/docs/notes) into memory, then embed.""" + p = os.path.expanduser(path) + if not os.path.isdir(p): + return f"not a directory: {p}" + brain.ingest_files(p) + try: + brain.embed(limit=20000) + except Exception: + pass + return f"ingested + embedded from {p}" + + +@mcp.tool() +def stats() -> str: + """Report memory coverage: total chunks, embedded count, and sources.""" + conn = brain.db() + tot = conn.execute("SELECT count(*) FROM chunks").fetchone()[0] + emb = conn.execute("SELECT count(*) FROM chunks WHERE embedded=1").fetchone()[0] + bysrc = dict(conn.execute("SELECT source,count(*) FROM chunks GROUP BY source").fetchall()) + conn.close() + return f"chunks={tot} embedded={emb} by_source={bysrc}" + + +if __name__ == "__main__": + mcp.run() diff --git a/requirements-mcp.txt b/requirements-mcp.txt new file mode 100644 index 0000000..50cbd8d --- /dev/null +++ b/requirements-mcp.txt @@ -0,0 +1,3 @@ +# Optional — only needed to run the MCP server (mcp_server.py). +# The engine, wizard and connectors do not require this. +mcp[cli]>=1.2