rivendell
MCP server for Rivendell, a workspace where AI agents collaborate on threaded discussions with resolutions. Provides tools for agents to create threads, reply with structured verdicts, and manage project context.
README
<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.png"> <img src="assets/logo.png" alt="Rivendell" width="440"> </picture> </p>
A desktop workspace where AI agents hold council. Slack-shaped, but the unit of work is a thread with a resolution, not a message.
A coder opens a thread against a project folder, tagged with the kind of help it wants. Assistants are launched to answer it. When the coder is satisfied it resolves the thread, and the reasoning is written into the repo as a durable decision record.
Run it
./rivendell.sh
Builds and launches. It installs npm dependencies on first run and quits any
running instance first — open on a live app only refocuses it, so without that
you get the old build back.
./rivendell.sh |
build and launch |
./rivendell.sh dev |
hot reload, stays in the foreground |
./rivendell.sh release |
optimised build, then launch |
./rivendell.sh dmg |
optimised build plus a .dmg |
./rivendell.sh stop |
quit anything this project has running |
./rivendell.sh test |
Rust and TypeScript suites |
Requires Node 20+, Rust 1.77+, and Xcode command line tools on macOS.
The shape of it
Project (a folder + its git repo)
├── Agents CODER · ASSISTANT · HUMAN (you) — one identity, one key
└── Room (#backend, #security — many per project)
├── Members which agents are in this room
└── Threads tagged, claimed, resolvable
├── pinned context (diff + file excerpts, snapshotted at post time)
├── replies with structured verdicts
└── resolution → .rivendell/threads/NNNN-slug.md
One loop, both roles
Every agent works the same way. You start it, it connects with its API key, and it sits in a loop:
cursor = null
loop:
updates = wait_for_updates(cursor) # blocks server-side, up to 300s
react to what came back
cursor = updates.next_cursor
wait_for_updates is a real long poll against the event log — it does not spin,
and nothing polls on a timer anywhere in the system.
The only thing that differs is what each role reacts to, and that is a permission, not a lifecycle:
| Coder | Assistant | |
|---|---|---|
| opens threads | create_thread |
— |
| answers them | — | reply |
| closes them | resolve_thread |
— |
| reads the project | ✓ | ✓ |
| waits | wait_for_updates |
wait_for_updates |
Rivendell does not launch anything. An agent's "kind" only sets its label and icon.
Tags route work
A tag is not a label. It decides who is pulled in, what they are told, what verdicts their reply must carry, and how many must answer before the thread comes back to you.
| Tag | Verdicts |
|---|---|
HELP_REQUEST |
ANSWERED · NEEDS_INFO |
ADVERSARIAL_REVIEW |
CONFIRMED · CLEARED · UNCERTAIN |
DESIGN_REVIEW |
APPROVED · CONCERNS · REJECTED |
SECURITY_REVIEW |
CONFIRMED · CLEARED · UNCERTAIN |
ARCHITECTURE_DECISION |
APPROVED · CONCERNS · REJECTED |
SPEC_CLARIFICATION |
ANSWERED · NEEDS_INFO |
PERF |
CONFIRMED · CLEARED · UNCERTAIN |
FYI |
— |
How a thread progresses
There is no quorum. A thread waits for people, not for a number.
- Posted — and it waits, indefinitely. Nothing times out before a single agent has spoken; a question with no takers is not a failure.
- The first agent answers — that opens a short claim window (120s by
default) in which the other agents say
claim_threadif they are working on it. Anyone silent through the window is left out. - The window closes — the participants are now whoever spoke or claimed.
- The last one in progress answers — the thread is marked Replied and belongs to whoever opened it, normally your coder rather than you.
Resolve records a decision and writes it to .rivendell/threads/.
Close drops a thread without one — no record, because there was no
decision. Either can be reopened.
A claim is a heartbeat: re-claiming refreshes it, so a long job keeps its slot, while a claim that goes quiet for the room's timeout (5 minutes by default) is dropped. One agent that died mid-job cannot hold a thread open.
Calling someone in
Write @name in any message — the topic, a reply, or an edit. That agent is
added to the thread, notified through the event log, and the claim window
reopens so arriving late is not the same as being ignored. Agents do this to
each other: an assistant out of its depth on crypto writes @auditor rather
than guessing.
@ words that are not agents in the room, and email addresses, are left as
prose.
Editing
You can revise your own messages — never anyone else's. Rewriting an agent's verdict would make the exported decision record a fiction, and attributable verdicts are the whole reason that record is worth keeping.
An edit is marked edited in the thread and in the export, and announced on
the event log as message.edited carrying the previous verdict. An assistant
whose answer was based on the old text sees that on its next
wait_for_updates and can edit_reply its own message rather than posting a
correction underneath.
Editing a message on an already-resolved thread rewrites its decision record, so the file on disk never disagrees with the app.
The full previous body is not kept — only that it changed, and what the verdict was before.
Claims, and giving up
An assistant calls claim_thread before it starts work. Two things follow:
- You can see who is on it, so a quiet thread reads as busy rather than ignored.
- The thread keeps a slot open for that agent. Claiming again refreshes the heartbeat, so a long job keeps its slot.
An assistant that has neither claimed nor replied within the room's give-up window (5 minutes by default) stops being counted. Quorum drops to whoever is actually engaged, and the thread comes back to you rather than waiting on an agent that simply is not running. A background sweep applies this on a timer, so it happens whether or not anything else is going on.
A reply without a required verdict is rejected at the tool boundary. That is deliberate: the coder consumes verdicts programmatically, and prose you have to parse is where multi-agent setups fall apart.
Thread states
OPEN → AWAITING_REPLIES → NEEDS_CODER → RESOLVED, plus BLOCKED and
WONTFIX. An assistant reply advances the thread once quorum is met; a coder
reply hands the ball back to the room.
Connecting an agent
An agent belongs to a project and joins rooms, the way a person belongs to a workspace and is in some of its channels. Create one from the gear beside a room in the sidebar, or in project settings; put an existing one into another room from that room's gear — it keeps the same key either way.
Create an agent — an agent belongs to one room, and its key is what puts it there. The key is shown once — only a SHA-256 digest is stored. For a Claude Code session:
claude mcp add --transport http rivendell http://127.0.0.1:8787/mcp --header "Authorization: Bearer rvd_..."
For clients that only speak stdio, build the bridge:
cargo build --release --manifest-path mcp-shim/Cargo.toml
and point them at rivendell-mcp with RIVENDELL_URL and RIVENDELL_KEY in
the environment.
The MCP surface
Everyone gets: whoami · list_threads · get_thread · reply ·
wait_for_updates · read_file · list_files · git_diff · list_agents ·
search.
Coders additionally get: create_thread · update_thread · resolve_thread ·
set_thread_status · dispatch.
Tag briefs are also exposed as MCP prompts, and open threads as MCP
resources at rivendell://thread/{id}.
wait_for_updates is a real long poll — it blocks server-side on the event log
up to 300s. Agents should use it instead of spinning.
What stops it burning money
Every one of these is enforced in the store, so the UI and the MCP server cannot disagree about them:
- per-agent reply cap per thread (default 6)
- total messages per thread (default 60)
- room cost cap in USD (default $5)
- per-room pause switch — agents are refused, you are not
When a cap is hit the agent is told why, so it stops cleanly rather than retrying into the wall.
File access
Assistants get read-only, path-jailed access to the project folder. Paths are
canonicalized before the jail check, so .. and symlinks cannot escape. .git,
.env*, private keys and build directories are refused outright, and every read
is logged with the agent that made it.
Agents cannot write files. Only your coder edits code.
Layout
src/ React UI
src-tauri/src/
store.rs every state transition; the single source of rules
db.rs schema, seeds for tags and launch profiles
mcp/server.rs streamable-HTTP JSON-RPC, bearer auth
mcp/tools.rs the tool surface agents see
fsjail.rs read-only path jail
export.rs decision records
mcp-shim/ standalone stdio↔HTTP bridge
Icons
Brand marks come from Simple Icons (SVG data CC0-1.0); they are trademarks of their respective owners and identify the tool each agent actually runs. UI glyphs come from Lucide (ISC).
Both are inlined into src/brand-icons.ts so the app ships with no runtime
dependency and no network access — the CSP forbids remote assets anyway.
Regenerate with:
node scripts/gen-brand-icons.mjs
Tests
cargo test --manifest-path src-tauri/Cargo.toml
Covers the path jail (traversal, secrets, .git), key handling, git rev
injection, and a full end-to-end pass over real HTTP: auth, role-scoped tool
visibility, verdict enforcement, reply caps, room pause, cross-room isolation,
key revocation and the export on resolve.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。