feat(mcp): RCLL MCP server and MCP-registry manifest

Recovered from the 2026-06-27 snapshot import by classifying the base..snapshot delta at line granularity. Upstream base: d054b884 (2026-04-10).
This commit is contained in:
RCLL 2026-08-23 23:50:03 +03:00
parent 77dcc6b80b
commit d2f74c5126
5 changed files with 1623 additions and 0 deletions

73
mcp-server/README.md Normal file
View file

@ -0,0 +1,73 @@
# RCLL MCP Server
Standalone [MCP](https://modelcontextprotocol.io) server that exposes **RCLL** memory tools to any MCP-compatible client (Claude Code, OpenClaw, etc). RCLL is self-hosted, hierarchical **shared** memory for a *team* of AI agents — rooms for per-agent vs shared recall, L0L3 depth, pgvector under the hood.
> RCLL — team memory for agent fleets. Built on Hindsight (github.com/vectorize-io/hindsight, MIT).
RCLL is a fork of [`vectorize-io/hindsight`](https://github.com/vectorize-io/hindsight) (MIT). It keeps Hindsight's storage engine and adds rooms — shared, isolated memory for a team of agents — plus a hierarchical depth model (L0L3). The room/hall/layer taxonomy is prior art in the hierarchical-memory space; the implementation here is our own.
## Tools
| Tool | Description |
|------|-------------|
| `memory_retain` | Save memories with room/hall/layer classification |
| `memory_recall` | Scoped semantic search with room filtering |
| `memory_reflect` | Deep reasoning + synthesis over stored memories |
| `memory_compress` | Create closet summaries from accumulated facts |
| `memory_bridge` | Cross-bank tunnels between related memories |
## Quick Start
```bash
cd mcp-server
npm install
RCLL_URL=http://localhost:5100 node server.js
```
## Claude Code
Add to `~/.claude/mcp.json`:
```json
{
"mcpServers": {
"rcll": {
"command": "npx",
"args": ["-y", "rcll-mcp"],
"env": {
"RCLL_URL": "http://localhost:5100",
"RCLL_BANK": "my-agent-bank"
}
}
}
}
```
## Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `RCLL_URL` | `http://127.0.0.1:5100` | RCLL backend base URL |
| `RCLL_BANK` | `mempalace-main` | Default memory bank ID. The default keeps the pre-rebrand value on purpose, so an install that never set it stays on the same bank after upgrading. Set it explicitly. |
### Deprecated (still read, with a notice on stderr)
Installs created before the rebrand keep working — these are used only when the
`RCLL_*` equivalent is unset, and they will be dropped in a future major.
| Legacy variable | Replaced by |
|-----------------|-------------|
| `HINDSIGHT_URL` | `RCLL_URL` |
| `MEMPALACE_BANK` | `RCLL_BANK` |
## Memory Taxonomy
**Rooms** (topics): auth, pipeline, schema, infrastructure, ui, api, deployment, monitoring, agent, general
**Halls** (knowledge types — the `hall` field): fact, event, decision, preference, discovery, procedure, warning
**Layers** (depth):
- L0 — Surface / identity (always at hand)
- L1 — Critical (recalled by default)
- L2 — Session (default for new memories)
- L3 — Deepest burrow / archive (deep search only, compressed into closets)

1142
mcp-server/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

30
mcp-server/package.json Normal file
View file

@ -0,0 +1,30 @@
{
"name": "rcll-mcp",
"version": "0.1.0",
"mcpName": "io.github.holetron-lab/rcll",
"description": "RCLL — self-hosted, hierarchical shared memory for a team of AI agents over MCP (rooms, L0L3 depth, pgvector). Fork of vectorize-io/hindsight.",
"type": "module",
"main": "server.js",
"bin": {
"rcll-mcp": "./server.js"
},
"scripts": {
"start": "node server.js"
},
"keywords": [
"mcp",
"memory",
"rcll",
"hindsight",
"ai-agents",
"multi-agent",
"pgvector",
"long-term-memory",
"self-hosted",
"model-context-protocol"
],
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.1"
}
}

341
mcp-server/server.js Normal file
View file

@ -0,0 +1,341 @@
#!/usr/bin/env node
/**
* RCLL Standalone MCP Server
* (fork of vectorize-io/hindsight; self-hosted shared memory for a team of agents)
*
* Provides 5 memory tools over MCP (stdio transport):
* memory_retain save memories with room/hall/layer classification
* memory_recall scoped semantic search
* memory_reflect deep reasoning over stored memories
* memory_compress create closet summaries
* memory_bridge cross-bank tunnels
*
* Connects to the RCLL backend (default: http://127.0.0.1:5100)
*
* Usage:
* RCLL_URL=http://localhost:5100 node server.js
*
* Claude Code config (~/.claude/mcp.json):
* {
* "mcpServers": {
* "rcll": {
* "command": "node",
* "args": ["/path/to/mcp-server/server.js"],
* "env": { "RCLL_URL": "http://localhost:5100" }
* }
* }
* }
*/
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// --- Config ---
// RCLL_* are the current names. The pre-rebrand names are still honored as a
// fallback so an existing install keeps working after `npm update` — each one
// hit prints a deprecation notice on stderr (stdout is the MCP transport).
const LEGACY_ENV = [
['RCLL_URL', 'HINDSIGHT_URL'],
['RCLL_BANK', 'MEMPALACE_BANK'],
];
function envWithFallback(current, legacy) {
if (process.env[current]) return process.env[current];
if (process.env[legacy]) {
console.error(`rcll-mcp: ${legacy} is deprecated — rename it to ${current}.`);
return process.env[legacy];
}
return undefined;
}
const RCLL_URL = envWithFallback(...LEGACY_ENV[0]) || 'http://127.0.0.1:5100';
const RCLL_BASE = `${RCLL_URL}/v1/default/banks`;
// The default bank literal deliberately stays 'mempalace-main': that is the value
// hindsight-mempalace-mcp@1.0.0 shipped with, so anyone who never set the env var is
// already living in that bank. Renaming the default would silently drop them into an
// empty bank on upgrade and read as "the update erased my memory".
// Drop this at the 60-day mark, together with the legacy image alias.
const DEFAULT_BANK = envWithFallback(...LEGACY_ENV[1]) || 'mempalace-main';
// --- RCLL HTTP client ---
async function hindsightRequest(method, path, body = null) {
const url = `${RCLL_BASE}${path}`;
const opts = {
method,
headers: { 'Content-Type': 'application/json' },
};
if (body) opts.body = JSON.stringify(body);
const res = await fetch(url, opts);
const text = await res.text();
let data;
try {
data = JSON.parse(text);
} catch {
throw new Error(`RCLL backend returned non-JSON: ${text.slice(0, 200)}`);
}
if (!res.ok) {
throw new Error(data.detail || `RCLL API error ${res.status}: ${JSON.stringify(data)}`);
}
return data;
}
// --- MCP Server ---
const server = new McpServer({
name: 'rcll',
version: '1.0.0',
});
// Tool 1: memory_retain
server.tool(
'memory_retain',
'Save a fact, observation, or document to long-term memory with automatic room/hall classification. ' +
'Rooms: auth, pipeline, schema, infrastructure, ui, api, deployment, monitoring, agent, general. ' +
'Halls: fact, event, decision, preference, discovery, procedure, warning. ' +
'Layers: L0=Identity (always loaded), L1=Critical, L2=Session (default), L3=Deep.',
{
text: z.string().describe('The text to memorize — a fact, observation, or document content'),
bank_id: z.string().optional().describe(`Memory bank ID (default: ${DEFAULT_BANK})`),
context: z.string().optional().describe('Context label (e.g. "meeting notes", "client call")'),
document_id: z.string().optional().describe('Document ID to group related facts'),
tags: z.array(z.string()).optional().describe('Tags for categorization'),
room: z.string().optional().describe('Topic room (auto-classified if omitted)'),
hall: z.enum(['fact', 'event', 'decision', 'preference', 'discovery', 'procedure', 'warning']).optional().describe('Knowledge type (auto-classified if omitted)'),
layer: z.enum(['L0', 'L1', 'L2', 'L3']).optional().describe('Priority layer (default: L2)'),
},
async ({ text, bank_id, context, document_id, tags, room, hall, layer }) => {
const bankId = bank_id || DEFAULT_BANK;
const item = { content: text };
if (context) item.context = context;
if (document_id) item.document_id = document_id;
if (tags) item.tags = tags;
if (room) item.room = room;
if (hall) item.hall = hall;
if (layer) item.layer = layer;
const result = await hindsightRequest('POST', `/${bankId}/memories`, {
items: [item],
});
const storedIds = (result.items || []).map(i => i.id || i.uuid).filter(Boolean);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
bank_id: bankId,
items_stored: result.items_count || 1,
ids: storedIds.length ? storedIds : null,
room: room || 'auto',
hall: hall || 'auto',
layer: layer || 'L2',
}, null, 2),
}],
};
}
);
// Tool 2: memory_recall
server.tool(
'memory_recall',
'Search long-term memory for relevant facts. Uses semantic search with optional room/hall scoping ' +
'for significantly improved retrieval accuracy. Supports layer cascade (L0 results always prioritized).',
{
query: z.string().describe('What to search for in memory'),
bank_id: z.string().optional().describe(`Memory bank ID (default: ${DEFAULT_BANK})`),
limit: z.number().optional().describe('Max results (default: 10)'),
room: z.union([z.string(), z.array(z.string())]).optional().describe('Filter by room(s) — applied before semantic search'),
hall: z.union([z.string(), z.array(z.string())]).optional().describe('Filter by hall(s): fact, event, decision, etc.'),
max_layer: z.enum(['L0', 'L1', 'L2', 'L3']).optional().describe('Max layer depth to search (default: L3 = all)'),
},
async ({ query, bank_id, limit, room, hall, max_layer }) => {
const bankId = bank_id || DEFAULT_BANK;
const body = {
query,
limit: limit || 10,
};
if (room) body.room = Array.isArray(room) ? room : [room];
if (hall) body.hall = Array.isArray(hall) ? hall : [hall];
if (max_layer) body.max_layer = max_layer;
const result = await hindsightRequest('POST', `/${bankId}/memories/recall`, body);
const memories = (result.results || []).map(r => ({
id: r.id || r.uuid || null,
text: r.text,
type: r.type,
entities: r.entities,
occurred: r.occurred_start || null,
room: r.room || null,
hall: r.hall || null,
}));
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
bank_id: bankId,
count: memories.length,
memories,
}, null, 2),
}],
};
}
);
// Tool 3: memory_reflect
server.tool(
'memory_reflect',
'Deep reasoning over memory — synthesizes facts, finds patterns, answers complex questions with citations. ' +
'Use for analysis: "What patterns emerge from recent events?" or "Summarize everything about X."',
{
query: z.string().describe('Question to reason about over stored memories'),
bank_id: z.string().optional().describe(`Memory bank ID (default: ${DEFAULT_BANK})`),
},
async ({ query, bank_id }) => {
const bankId = bank_id || DEFAULT_BANK;
const result = await hindsightRequest('POST', `/${bankId}/reflect`, { query });
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
bank_id: bankId,
answer: result.answer || result.response || result.text || JSON.stringify(result),
citations: result.based_on || result.citations || [],
}, null, 2),
}],
};
}
);
// Tool 4: memory_compress
server.tool(
'memory_compress',
'Create compressed memory summaries (closets) from stored facts. Groups memories by room+hall ' +
'and creates AI-generated summaries with source pointers. Use when a topic has accumulated many facts.',
{
bank_id: z.string().optional().describe(`Memory bank ID (default: ${DEFAULT_BANK})`),
room: z.string().optional().describe('Topic to compress (e.g. "auth", "pipeline")'),
hall: z.string().optional().describe('Knowledge type to compress (e.g. "fact", "decision")'),
min_sources: z.number().optional().describe('Min memories needed to create a closet (default: 5)'),
query: z.string().optional().describe('Query to guide compression focus'),
},
async ({ bank_id, room, hall, min_sources, query }) => {
const bankId = bank_id || DEFAULT_BANK;
const body = {};
if (room) body.room = room;
if (hall) body.hall = hall;
if (min_sources) body.min_sources = min_sources;
if (query) body.query = query;
const result = await hindsightRequest('POST', `/${bankId}/closets`, body);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
bank_id: bankId,
closets_created: result.closets_created || 0,
closets: result.closets || [],
}, null, 2),
}],
};
}
);
// Tool 5: memory_bridge
server.tool(
'memory_bridge',
'Create a cross-bank memory bridge (tunnel) between two related memories in different banks. ' +
'Relations: same_concept, depends_on, contradicts, extends. Use when concepts in separate banks are related.',
{
source_bank: z.string().describe('Source bank ID'),
source_memory: z.string().describe('UUID of the source memory'),
target_bank: z.string().describe('Target bank ID'),
target_memory: z.string().describe('UUID of the target memory'),
relation: z.enum(['same_concept', 'depends_on', 'contradicts', 'extends']).describe('Relationship type'),
confidence: z.number().min(0).max(1).optional().describe('Confidence score 0.01.0 (default: 0.8)'),
},
async ({ source_bank, source_memory, target_bank, target_memory, relation, confidence }) => {
const body = {
source_bank,
source_memory,
target_bank,
target_memory,
relation,
};
if (confidence !== undefined) body.confidence = confidence;
const result = await hindsightRequest('POST', `/${source_bank}/tunnels`, body);
return {
content: [{
type: 'text',
text: JSON.stringify({
success: true,
tunnel: result.tunnel || result,
}, null, 2),
}],
};
}
);
// --- Resources ---
server.resource(
'memory-config',
'rcll://config',
async () => ({
contents: [{
uri: 'rcll://config',
mimeType: 'application/json',
text: JSON.stringify({
rcll_url: RCLL_URL,
hindsight_url: RCLL_URL, // legacy alias, drop after 60d
default_bank: DEFAULT_BANK,
rooms: ['auth', 'pipeline', 'schema', 'infrastructure', 'ui', 'api', 'deployment', 'monitoring', 'agent', 'general'],
halls: ['fact', 'event', 'decision', 'preference', 'discovery', 'procedure', 'warning'],
layers: {
L0: 'Identity — always loaded',
L1: 'Critical facts — per-space',
L2: 'Session context — per-conversation (default)',
L3: 'Deep memory — full search only',
},
}, null, 2),
}],
})
);
// --- Start ---
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
if (!process.env.RCLL_BANK && !process.env.MEMPALACE_BANK) {
console.error(
`rcll-mcp: no bank configured — falling back to the pre-rebrand default '${DEFAULT_BANK}'. ` +
'Set RCLL_BANK explicitly; this fallback goes away in a future major.'
);
}
console.error(`rcll-mcp running (backend: ${RCLL_URL}, bank: ${DEFAULT_BANK})`);
}
main().catch(err => {
console.error('Fatal:', err);
process.exit(1);
});

37
server.json Normal file
View file

@ -0,0 +1,37 @@
{
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.holetron-lab/rcll",
"title": "RCLL",
"description": "RCLL — team memory for agent fleets. Built on Hindsight (github.com/vectorize-io/hindsight, MIT). Self-hosted shared memory over MCP: rooms, hierarchical L0L3 depth, pgvector.",
"version": "0.1.0",
"websiteUrl": "https://rcll.ai",
"repository": {
"url": "https://github.com/holetron-lab/rcll",
"source": "github"
},
"packages": [
{
"registryType": "npm",
"identifier": "rcll-mcp",
"version": "0.1.0",
"runtimeHint": "npx",
"transport": {
"type": "stdio"
},
"environmentVariables": [
{
"name": "RCLL_URL",
"description": "Base URL of the RCLL backend (self-hosted). Defaults to http://127.0.0.1:5100. The pre-rebrand name HINDSIGHT_URL is still read as a fallback.",
"isRequired": false,
"default": "http://127.0.0.1:5100"
},
{
"name": "RCLL_BANK",
"description": "Default memory bank ID for retain/recall. The pre-rebrand variable MEMPALACE_BANK is still read as a fallback; see mcp-server/README.md.",
"isRequired": false,
"default": "mempalace-main"
}
]
}
]
}