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:
parent
77dcc6b80b
commit
d2f74c5126
5 changed files with 1623 additions and 0 deletions
73
mcp-server/README.md
Normal file
73
mcp-server/README.md
Normal 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, L0–L3 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 (L0–L3). 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
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
30
mcp-server/package.json
Normal 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, L0–L3 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
341
mcp-server/server.js
Normal 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.0–1.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
37
server.json
Normal 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 L0–L3 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Reference in a new issue