Add MCP server — expose brain (recall/remember/ingest/stats) to any MCP host
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.
This commit is contained in:
parent
eb5b67dee2
commit
ebdaafa52d
3 changed files with 114 additions and 0 deletions
21
README.md
21
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
|
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
|
## Use it as a library
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
|
|
||||||
90
mcp_server.py
Executable file
90
mcp_server.py
Executable file
|
|
@ -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()
|
||||||
3
requirements-mcp.txt
Normal file
3
requirements-mcp.txt
Normal file
|
|
@ -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
|
||||||
Loading…
Reference in a new issue