class

Engram::McpServer

Inherits Reference < Object

Hand-rolled newline-delimited JSON-RPC 2.0 MCP server, driven over injected IO so it can be exercised through IO::Memory pairs in specs and wired to real stdio in production.

Reads one JSON-RPC message per line from input; writes at most one JSON-RPC response line per request to output (notifications get no reply at all); EOF on input ends #run (there is no shutdown method).

Seam: search and sync collaborators

search.cr and sync.cr are built in a parallel stage. Rather than reference their concrete types directly, this server takes three narrow Procs plus the already-built Store:

  • search_memories : SearchProc(query, topic, limit, include_superseded) -> Array(MemoryHit)
  • recent_memories : RecentProc(topic, limit) -> Array(MemoryHit)
  • run_sync : RunSyncProc() -> Nil, a full re-sync (used by remember after writing a new migration file, so apply/rollback/supersedes-recompute stay the sole responsibility of sync.cr)

memory_status reports embedder state and last-sync time by reading engram_meta keys "embedder_enabled" and "last_sync_at" off the Store — the assumption is that sync.cr writes those keys on every run. This isn't spelled out in docs/SPEC.md and is called out again in this builder's final report as the concrete contract the integrator must honor.

Constants

PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"]

Protocol versions this server understands, newest first.

TOOLS = (JSON.parse("[\n {\n \"name\": \"search_memories\",\n \"description\": \"Full-text + recency ranked search over active (non-superseded) memories.\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"properties\": {\n \"query\": {\"type\": \"string\", \"description\": \"Search text, matched via FTS5 bm25 + recency.\"},\n \"topic\": {\"type\": \"string\", \"description\": \"Restrict to memories tagged with this topic.\"},\n \"limit\": {\"type\": \"integer\", \"description\": \"Max results to return (default 10).\"},\n \"include_superseded\": {\"type\": \"boolean\", \"description\": \"Include superseded memories (default false).\"}\n },\n \"required\": [\"query\"]\n }\n },\n {\n \"name\": \"recent_memories\",\n \"description\": \"Newest active memories, most recent first.\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"properties\": {\n \"topic\": {\"type\": \"string\", \"description\": \"Restrict to memories tagged with this topic.\"},\n \"limit\": {\"type\": \"integer\", \"description\": \"Max results to return (default 10).\"}\n },\n \"required\": []\n }\n },\n {\n \"name\": \"get_memory\",\n \"description\": \"Full body and metadata for one memory by id.\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"properties\": {\n \"id\": {\"type\": \"integer\", \"description\": \"The memory's 14-digit migration id.\"}\n },\n \"required\": [\"id\"]\n }\n },\n {\n \"name\": \"remember\",\n \"description\": \"Writes a new memory migration file under .agents/memories and applies it to the local cache. The agent MUST commit the resulting file afterwards or the memory is lost.\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"properties\": {\n \"title\": {\"type\": \"string\"},\n \"body\": {\"type\": \"string\", \"description\": \"Markdown body, conventionally Decision/Why/Rejected sections.\"},\n \"topics\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}},\n \"supersedes\": {\"type\": \"array\", \"items\": {\"type\": \"integer\"}, \"description\": \"IDs of older memories this one replaces.\"}\n },\n \"required\": [\"title\", \"body\"]\n }\n },\n {\n \"name\": \"memory_status\",\n \"description\": \"Active/superseded memory counts, embedder state, DB path, and last sync time.\",\n \"inputSchema\": {\n \"type\": \"object\",\n \"properties\": {},\n \"required\": []\n }\n }\n]")).as_a

Static tools/list payload for the 5 tools this server exposes.

Constructors

new(input : IO, output : IO, store : Store, memories_dir : String, db_path : String, search_memories : SearchProc, recent_memories : RecentProc, run_sync : RunSyncProc)

Wires an MCP server against injected stdio and its collaborators; see the seam note on the class.

Source

Instance methods

negotiated_version

The protocol version this session negotiated in initialize (defaults to our newest before that happens).

Source
run

Reads and dispatches one JSON-RPC message per line until input hits EOF.

Source

Nested types