"""notes: a small remote MCP server that remembers things for all your agents. From https://exe.dev/deploy/mcp-server. It keeps notes in SQLite on the VM's disk and serves MCP over Streamable HTTP at http://:8000/mcp, which exe.dev's proxy publishes as https://.exe.xyz/mcp. Settings come from the environment (see ~/mcp-server.env): PORT port to listen on (default 8000) NOTES_DB SQLite file (default ~/data/notes.db) ALLOWED_HOSTS extra host names clients may use, comma-separated (.exe.xyz and localhost are always allowed) """ import os import socket import sqlite3 from datetime import datetime, timezone from pathlib import Path from mcp.server.mcpserver import Context, MCPServer from mcp.server.transport_security import TransportSecuritySettings from mcp.types import ToolAnnotations DB = Path(os.environ.get("NOTES_DB") or Path.home() / "data" / "notes.db") PORT = int(os.environ.get("PORT") or 8000) mcp = MCPServer( "notes", instructions=( "Notes kept on the user's own server, shared by every agent they use. " "Search them before asking the user to repeat something; save decisions, " "preferences and facts worth keeping." ), ) def db() -> sqlite3.Connection: con = sqlite3.connect(DB, timeout=10) con.row_factory = sqlite3.Row return con def setup() -> None: DB.parent.mkdir(parents=True, exist_ok=True) with db() as con: con.executescript(""" PRAGMA journal_mode = WAL; CREATE TABLE IF NOT EXISTS notes ( id INTEGER PRIMARY KEY, text TEXT NOT NULL, tags TEXT NOT NULL DEFAULT '', author TEXT NOT NULL DEFAULT '', created TEXT NOT NULL ); CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(text, tags, content='notes', content_rowid='id'); CREATE TRIGGER IF NOT EXISTS notes_ai AFTER INSERT ON notes BEGIN INSERT INTO notes_fts(rowid, text, tags) VALUES (new.id, new.text, new.tags); END; CREATE TRIGGER IF NOT EXISTS notes_ad AFTER DELETE ON notes BEGIN INSERT INTO notes_fts(notes_fts, rowid, text, tags) VALUES ('delete', old.id, old.text, old.tags); END; """) def caller(ctx: Context) -> str: """Who is calling, as exe.dev's proxy (or mcp-gate) vouches for it.""" headers = ctx.headers or {} return ( headers.get("x-exedev-source-vm") # another of your VMs, via a VM-to-VM integration or headers.get("x-exedev-email") # a signed-in user, a VM token, or an OAuth sign-in or "unknown" ) def rows(cur: sqlite3.Cursor) -> list[dict]: return [dict(r) for r in cur.fetchall()] @mcp.tool() def save_note(text: str, ctx: Context, tags: list[str] | None = None) -> dict: """Save a note. Use tags (lowercase words) to group related notes.""" now = datetime.now(timezone.utc).isoformat(timespec="seconds") with db() as con: cur = con.execute( "INSERT INTO notes (text, tags, author, created) VALUES (?, ?, ?, ?)", (text, " ".join(tags or []), caller(ctx), now), ) return {"id": cur.lastrowid, "saved": now} @mcp.tool(annotations=ToolAnnotations(read_only_hint=True)) def search_notes(query: str, limit: int = 10) -> list[dict]: """Full-text search over notes and their tags, best matches first.""" with db() as con: try: return rows(con.execute( "SELECT n.* FROM notes_fts JOIN notes n ON n.id = notes_fts.rowid" " WHERE notes_fts MATCH ? ORDER BY rank LIMIT ?", (query, limit), )) except sqlite3.OperationalError: # not valid FTS syntax: fall back to a substring match like = f"%{query}%" return rows(con.execute( "SELECT * FROM notes WHERE text LIKE ? OR tags LIKE ? ORDER BY id DESC LIMIT ?", (like, like, limit), )) @mcp.tool(annotations=ToolAnnotations(read_only_hint=True)) def recent_notes(limit: int = 10) -> list[dict]: """The most recently saved notes, newest first.""" with db() as con: return rows(con.execute("SELECT * FROM notes ORDER BY id DESC LIMIT ?", (limit,))) @mcp.tool(annotations=ToolAnnotations(destructive_hint=True, idempotent_hint=True)) def delete_note(id: int) -> str: """Delete one note by its id.""" with db() as con: n = con.execute("DELETE FROM notes WHERE id = ?", (id,)).rowcount return f"deleted note {id}" if n else f"no note {id}" def security() -> TransportSecuritySettings: """Accept only requests addressed to this VM, from no web page but its own.""" names = [f"{socket.gethostname()}.exe.xyz"] names += [h.strip() for h in os.environ.get("ALLOWED_HOSTS", "").split(",") if h.strip()] return TransportSecuritySettings( allowed_hosts=[*names, *(f"{n}:*" for n in names), "localhost:*", "127.0.0.1:*"], allowed_origins=[f"https://{n}" for n in names], ) if __name__ == "__main__": setup() mcp.run( "streamable-http", host="0.0.0.0", port=PORT, stateless_http=True, # no sessions to lose when the service restarts transport_security=security(), )