Standalone RLM (Recursive Language Model) MCP server in Rust.
Implements the MIT CSAIL paper pattern: context is external — load long text into sessions, filter with peek, map with paginated chunks, reduce in the agent.
Independent project — no dependency on codebase-memory-mcp or any graph index.
| Need | Use |
|---|---|
| Long logs, docs, transcripts, multi-file text analysis | rlm-mcp (this repo) |
| Code graph, symbol lookup, call-path tracing | cbm-mcp (separate repo) |
| Both long-context sessions and graph search | Run both MCP servers side by side — separate processes, no code coupling |
cd D:\rlm-mcp
.\install.ps1
rlm-mcp --versioninstall.ps1 / install.sh download the latest GitHub Release binary by default. Agents can install directly from a checkout without compiling Rust.
For agents and automation: run .\install.ps1 from the checkout, or run the raw release installer URL below. Do not compile or use target/release unless you explicitly pass -FromSource.
Windows:
irm https://raw.githubusercontent.com/stevenke1981/rlm-mcp/main/packaging/windows/install.ps1 | iexLinux:
curl -fsSL https://raw.githubusercontent.com/stevenke1981/rlm-mcp/main/packaging/linux/install.sh | bashmacOS Apple Silicon:
curl -fsSL https://raw.githubusercontent.com/stevenke1981/rlm-mcp/main/packaging/macos/install.sh | bashThe release installer verifies SHA256SUMS.txt, installs the binary to a stable path, and registers both OpenCode and Codex MCP entries automatically.
On Windows, if the stable binary is currently locked by a running agent, the installer installs a versioned side-by-side binary and configures agents to use that path.
.\install.ps1Downloads the latest release archive, verifies SHA256SUMS.txt, installs to %USERPROFILE%\.config\rlm-mcp\bin\rlm-mcp.exe, registers OpenCode and Codex, and copies the rlm skill for Codex, Claude Code, OpenCode, and agents.
chmod +x install.sh
./install.shInstalls to ~/.config/rlm-mcp/bin/rlm-mcp and symlinks ~/.local/bin/rlm-mcp.
Pin a version:
.\install.ps1 -Version v0.1.7RLM_VERSION=v0.1.7 ./install.shOnly use this for development or local unreleased changes:
.\install.ps1 -FromSource./install.sh --from-sourceTemplates: packaging/mcp/ (OpenCode, Codex, Claude, generic).
{
"rlm-mcp": {
"type": "local",
"command": ["rlm-mcp"],
"enabled": true,
"timeout": 120000
}
}Replace command with the absolute path from install.ps1 / install.sh, or use {{RLM_BINARY}} in templates.
The installer updates both existing opencode.json and opencode.jsonc files and %USERPROFILE%\.codex\config.toml. Restart the agent or open a new session after installation because MCP tools are loaded when a session starts.
The MCP protocol boundary uses the official Rust SDK, rmcp 3.x (the exact
version is pinned in Cargo.lock), for stdio
framing, protocol negotiation, capabilities, request IDs, and error envelopes.
The 33-tool contract remains locked by
packaging/mcp/tools-list.snapshot.json.
Stdout is protocol-only in MCP mode; diagnostics are written to stderr.
Stable binary path:
| OS | Path |
|---|---|
| Windows | %USERPROFILE%\.config\rlm-mcp\bin\rlm-mcp.exe |
| Linux / macOS | ~/.config/rlm-mcp/bin/rlm-mcp (symlinked from ~/.local/bin/rlm-mcp) |
Push a version tag to trigger the GitHub release workflow:
git tag v0.1.0
git push origin v0.1.0Artifacts: per-target .zip / .tar.gz + SHA256SUMS.txt. Local packaging:
cargo build --release
.\scripts\package-release.ps1cargo build --release
./scripts/package-release.shImages are published to GitHub Container Registry on every main push and on version tags:
| Event | Tags on ghcr.io/stevenke1981/rlm-mcp |
|---|---|
Push to main |
latest, sha-<git-sha> |
Tag vX.Y.Z |
X.Y.Z, X.Y, latest |
# Pull published image (public package; first publish may need package visibility = Public)
docker pull ghcr.io/stevenke1981/rlm-mcp:latest
# Run in MCP stdio mode (for agent integration)
echo '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"smoke","version":"1.0"}}}' | docker run -i --rm ghcr.io/stevenke1981/rlm-mcp:latest
# Run CLI command
docker run -i --rm ghcr.io/stevenke1981/rlm-mcp:latest workflow --json
# Pin a release
docker run -i --rm ghcr.io/stevenke1981/rlm-mcp:0.1.7 workflow --json
# Build locally instead of pulling
docker build -t rlm-mcp .
# Compose (uses GHCR image by default; see docker-compose.yml)
docker compose up -dSee docker-compose.yml for cache volumes, resource limits, and sandboxed
command provider mounts. CI builds the image on every PR (no push); only main
and release tags push to GHCR.
| Variable | Default | Purpose |
|---|---|---|
RLM_CACHE_DIR |
%LOCALAPPDATA%\rlm-mcp / ~/.cache/... |
Dedicated cache root (auto-creates rlm-sessions, rlm-artifacts, …) |
RLM_ALLOW_SYSTEM_TEMP |
unset | Set 1 only if you must use bare OS temp as cache |
RLM_MAX_FILE_BYTES |
524288 |
Max single file size |
RLM_MAX_TOTAL_BYTES |
8388608 |
Max total session bytes |
RLM_MAX_CHUNKS |
10000 |
Max chunks per session |
RLM_MAX_SESSIONS |
50 |
Max persisted sessions |
RLM_SESSION_TTL_SECS |
3600 |
Session expiry |
RLM_CHUNK_LINES |
200 |
Lines per chunk |
RLM_ALLOW_NETWORK |
unset (offline) | Set to 1 to enable openai provider |
RLM_OPENAI_API_KEY |
— | OpenAI-compatible API key (never persisted) |
RLM_OPENAI_BASE_URL |
https://api.openai.com/v1 |
Compatible API base URL |
RLM_OPENAI_MODEL |
gpt-4o-mini |
Model name for sub-calls |
RLM_OPENAI_TIMEOUT_SECS |
120 |
Async HTTP request timeout for openai provider |
RLM_PROVIDER_COMMAND |
— | Executable for command provider |
RLM_PROVIDER_ARGS |
[] |
JSON array or whitespace-separated args |
RLM_PROVIDER_SANDBOX |
warn |
Sandbox mode: strict, warn, or off (see security) |
RLM_PROVIDER_ALLOWED_DIRS |
— | Semicolon-separated allowed directories (strict mode) |
RLM_PROVIDER_MAX_WALL_SECS |
300 |
Kill command provider after N seconds (0 = no limit) |
RLM_PROVIDER_MAX_OUTPUT_BYTES |
1048576 |
Positive byte cap per command stdout/stderr pipe and OpenAI HTTP response body; exceeding it fails the call |
RLM_PROVIDER_MAX_RETRIES |
3 |
Provider retry attempts |
RLM_PROVIDER_RETRY_DELAY_MS |
200 |
Base delay between retries (exponential backoff) |
RLM_LOG_FORMAT |
pretty |
stderr logs: pretty or json |
RLM_OPENAI_PROMPT_COST_PER_1K |
— | Optional USD/1K prompt tokens for cost est. |
RLM_OPENAI_COMPLETION_COST_PER_1K |
— | Optional USD/1K completion tokens |
| Phase | Tools | Purpose |
|---|---|---|
| Load | rlm_scan, rlm_env_info |
Load path or text into session; inspect metadata |
| Filter | rlm_peek, rlm_slice |
Narrow candidates (substring, glob, regex, line range) |
| REPL | rlm_env_info, rlm_slice, rlm_transform, rlm_artifact_* |
Safe snippet transforms and derived artifacts |
| Map | rlm_chunk, rlm_map_plan, rlm_map_claim, rlm_map_complete |
Paginated chunks; coordinated parallel workers |
| Reduce | rlm_reduce_schema, rlm_reduce_merge |
Merge worker JSON; decide if recursion needed |
| Recurse | rlm_task_create, rlm_task_list, rlm_task_result, rlm_task_reduce |
Sub-tasks (mock/dry-run offline; command/openai opt-in) |
| Observe | rlm_trajectory_get, rlm_trajectory_final, rlm_budget_status |
Trajectory + budget/tail-cost reporting |
| Control | rlm_budget_configure, rlm_task_cancel |
Session limits, fail-fast/soft-warning, cancel trees |
| Help | rlm_workflow, rlm_tools_reference |
Phase guidance + full tool schema |
Also: rlm_session_list, rlm_session_delete, rlm_benchmark_list, rlm_benchmark_run
Full parameter reference: docs/tools.md
Walkthrough and examples: docs/rlm-loop.md, examples/
Paper ↔ implementation map: docs/paper-mapping.md
Limitations and benchmarks: docs/limitations.md, docs/benchmarks.md
Run without args to start MCP stdio server. With a subcommand, outputs JSON:
Try this offline walkthrough in PowerShell after installation. It uses synthetic
text and the mock provider; no API key or paid model is required. Keep the same
RLM_CACHE_DIR for all commands so separate CLI processes share the session.
$env:RLM_CACHE_DIR = Join-Path ([IO.Path]::GetTempPath()) ("rlm-demo-" + [guid]::NewGuid())
$scan = rlm-mcp scan --content "INFO ready`nERROR retry exhausted`nINFO stopped" --virtual-path demo.log --json | ConvertFrom-Json
$sessionId = $scan.session_id
rlm-mcp env-info --session-id $sessionId --json
$peek = rlm-mcp peek --session-id $sessionId --query error --regex --ignore-case --limit 3 --json | ConvertFrom-Json
$chunkId = $peek.matches[0].chunk_id
rlm-mcp chunk --session-id $sessionId --chunk-id $chunkId --json
$task = rlm-mcp task-create --session-id $sessionId --chunk-id $chunkId --prompt "Summarize the error" --provider mock --json | ConvertFrom-Json
rlm-mcp task-reduce --root-id $task.root_id --json
rlm-mcp session-delete --session-id $sessionId --jsonThe mock result validates the local workflow; it does not provide model reasoning.
session-delete removes the persisted session and its chunk bodies. Other cache
records may remain in the demo directory. An invalid regex returns an error
(MCP: isError=true; CLI: nonzero exit with stderr). --ignore-case applies to
both substring and regex searches.
Equivalent MCP calls: rlm_scan → rlm_env_info → rlm_peek → rlm_chunk →
rlm_task_create(provider="mock") → rlm_task_reduce → rlm_session_delete.
Read each returned session_id, chunk_id, and root_id before the next call.
Individual commands:
# Load directory
rlm-mcp scan --path . --json
# Load inline text
rlm-mcp scan --content "long prompt text" --virtual-path prompt.txt --json
# Filter
rlm-mcp peek --session-id <id> --query ERROR --limit 10 --json
# Map
rlm-mcp chunk --session-id <id> --chunk-id c-0 --json
rlm-mcp map-plan --session-id <id> --batch-size 3 --json
# Reduce
rlm-mcp reduce-schema --json
rlm-mcp reduce-merge --workers '[{"batch_id":"b0","findings":[]}]' --json| Doc | Topic |
|---|---|
docs/rlm-loop.md |
Load → filter → map → reduce loop |
docs/security.md |
Sandbox, network, path safety |
docs/extending.md |
Custom providers, transforms, plugins |
docs/lazy-chunk-store.md |
Metadata session + on-disk chunk bodies |
docs/embedding-roadmap.md |
Deferred semantic embedding peek |
docs/paper-mapping.md |
Paper ↔ implementation mapping |
examples/ |
Agent prompt templates and walkthroughs |
Agent (LLM plans filter/map/reduce)
↓ MCP stdio or CLI
rlm-mcp
↓ local sessions (RLM_CACHE_DIR/rlm-sessions)
External files / logs / docs / text blobs
rmcp implements the MCP transport boundary. The external-context, filter,
map, reduce, recursion, provider, budget, and trajectory behavior remains this
project's RLM implementation.
| Repo | Role |
|---|---|
| rlm-mcp | This repo — standalone RLM |
| cbm-mcp | Optional separate graph MCP (not required) |
| Symptom | Fix |
|---|---|
rlm-mcp not found in agent |
Use absolute path from install output; avoid bare rlm-mcp unless ~/.local/bin is on PATH |
OpenCode shows no rlm-mcp connection after install |
Re-run .\install.ps1; then restart OpenCode or open a new session because MCP servers are loaded at session start |
| MCP server exits immediately | Run without subcommand for stdio MCP; use rlm-mcp workflow --json to verify CLI |
tools/list test fails after adding tools |
cargo test write_tools_snapshot -- --ignored then commit snapshot |
| Session not found across processes | Same RLM_CACHE_DIR; use rlm-mcp session-list --json |
| Permission denied on cache dir | Set RLM_CACHE_DIR to a writable directory |
| Windows build slow | Default install does not build; use .\install.ps1 for release binary or .\install.ps1 -FromSource -SkipBuild after a local dev build |
Windows says rlm-mcp.exe is being used by another process |
Re-run the current installer; it falls back to %USERPROFILE%\.config\rlm-mcp\bin\rlm-mcp-<version>.exe and updates agent config |
| Release smoke skipped | Run cargo build --release then cargo test --test release_smoke --release |
Use a writable, dedicated RLM_CACHE_DIR for manual runs. Integration tests use
temporary synthetic fixtures; the default suite does not call paid providers.
$env:RLM_CACHE_DIR = Join-Path ([IO.Path]::GetTempPath()) ("rlm-tests-" + [guid]::NewGuid())
cargo test --locked --test mcp_contract --test rmcp_protocol --test cli_contract
cargo test --locked --all-targets
cargo fmt --check
cargo clippy --locked --all-targets -- -D warningsrelease_smoke reports skipped checks until a release binary exists. To exercise
it fully, run cargo build --locked --release followed by
cargo test --locked --release --test release_smoke. CI also runs a dependency
audit and Docker build. Optional large/nightly fixtures and snapshot generation
are ignored by the default suite.
See TODO.md for the full paper-complete implementation backlog.
Current status: Paper-complete core (P0–P3); offline benchmarks: S-NIAH, OOLONG, CodeQA, BrowseComp-Plus, OOLONG-Pairs (mini in CI).