Rithmo

ai.rithmo由 rithmo.ai 發布官方發布者v0.1.0更新於 Oct 24, 2026

AI agent governance with resolved business context, current decisions, provenance, and supersession.

已驗證Streamable HTTP可網頁執行效率與工作流知識與記憶

概覽

AI 產生的概覽

唯讀的 Rithmo MCP 伺服器,讓助理查詢組織已記錄的決策,並在採取行動前輪詢決策變更。

可以做什麼
  • 以自然語言問題查詢決策記錄,可依類型、負責人、領域或外部系統篩選,回傳目前答案及狀態、負責人、來源、理由、條件與替代歷程
  • 回傳明確的「無記錄」結果,包括說明前提為何被擱置、是否可安全推進的型別化拒絕
  • 依游標輪詢狀態變更的決策串流,包含已撤銷與被替代的項目,可作為觸發條件
  • 透過 whoami 呼叫回傳組織與權限範圍,用來驗證權杖與連線
適用情境
適合讓代理或自動化在行動前先確認組織對某件事的實際決策,並在沒有可靠答案時暫停或轉交人工處理。也適合需要在決策被撤銷或被替代時做出反應的工作流程。
執行需求
需要 Rithmo MCP 遠端端點(Rithmo Cloud 或自架執行個體),以及依組織簽發的 Rithmo MCP 服務權杖,透過 Authorization Bearer 標頭送出,並具備 mcp:read 權限範圍。README 範例為 Node.js 腳本,透過 RITHMO_MCP_URL 與 RITHMO_MCP_TOKEN 設定。
安裝前請注意
Authorization 權杖是限定單一組織的機密,應存放在憑證儲存區而非程式碼中。伺服器為唯讀,不會在你的系統中執行操作,但其答案用於把關行動,因此應把無記錄與被擱置的結果視為停止訊號,而非猜測依據。reason 以 record_unavailable 開頭代表讀取暫時失敗,應重試。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Rithmo,將其新增到工作區。
  2. 連接 Authorization token 憑證。它們會被私密儲存,絕不會在公開頁面上展示。
  3. 為需要使用其工具的對話啟用該服務。

Web executable,透過 Streamable HTTP。 遠端服務在工作區中設定後即可從網頁執行環境執行。

交給代理程式安裝

把這段提示詞貼上到 Claude Code、Codex、Cursor 等代理程式,或 SourceWeft 對話中。代理程式會讀取該伺服器的安裝指南,先向你展示發布者、端點和登入方式,經你同意後再把它新增到你的客戶端。金鑰不會寫進任何命令。

閱讀 https://sourceweft.com/mcp/ai.rithmo%2Frithmo/install.md,並按其中說明把這個 MCP 伺服器接入我的客戶端。修改任何設定之前,先告訴我它的發布者、端點和登入方式,等我確認。不要把 API 金鑰寫進命令或我會提交的檔案;問我金鑰應從哪裡取得。

檢視代理程式所依據的安裝指南

其他 MCP 客戶端

把它新增到你客戶端的 mcpServers 設定中。

{
  "mcpServers": {
    "rithmo": {
      "type": "http",
      "url": "https://app.rithmo.ai/api/mcp",
      "headers": {
        "Authorization": "Bearer <YOUR_TOKEN>"
      }
    }
  }
}

README

Rithmo MCP — examples

Runnable examples for connecting to Rithmo's read-only MCP server.

Rithmo is a system of record for decisions. This server lets your tools query the decision record before they act — "what did we actually decide about X?" — and react when decisions change. Rithmo emits the record; your tools act on it. Rithmo never takes actions in your systems.

It speaks the Model Context Protocol, so it works with n8n, AI agents, and anything else that speaks MCP.

New here? Start with the Integration Guide — what it is, how to get access, how to connect, and the full tool reference.

Run it in a few minutes

bash
git clone https://github.com/Rithmo-Inc/rithmo-mcp-examplescd rithmo-mcp-examplesnpm installcp .env.example .env      # then edit .env

Set two values in .env:

  • RITHMO_MCP_URL — the MCP endpoint of a Rithmo instance. Rithmo Cloud is https://app.rithmo.ai/api/mcp; a self-hosted deployment exposes the same server at your own address.
  • RITHMO_MCP_TOKEN — a per-organization Rithmo MCP service token (read-only, scoped to one org, carrying the mcp:read scope).

Then:

bash
node examples/query_decision.mjs "What did we decide about GitHub integration?"node examples/no_record.mjsnode examples/list_decision_changes.mjs

What you'll see

query_decision — the current decision, or no_record:

Q: What did we decide about GitHub integration?✓ found (confidence 0.66)  answer: Look into GitHub integration details, including permissions and OAuth requirements.  status: open  owner:  participant_50332672  source: meeting · <source ref>  decided_at: 2026-07-09T14:39:48.296Z

no_record — Rithmo will not fabricate a decision (the differentiator):

Q: Did we decide anything about office snacks?∅ no_record — Rithmo will not fabricate a decision.   Branch here: pause the automation, route to a human, or use a safe default.

A no_record may carry a typed refusal explaining why it is held:

Q: Are we still migrating to the new billing provider?∅ no_record — held (closed · superseded)   subject: Billing provider migration   held_because: That decision was superseded; acting on the old premise is unsafe.   safe_to_proceed: false

list_decision_changes — a cursor-drained feed of decisions changing state:

changes: 20   has_more: truenext cursor: 1783606904319_f901849e-...   ← pass this as the argument on the next poll  [open] Push the local build after the meeting ends.  (changed 2026-07-09T14:21:44.319Z)  ...

The tools

query_decision

Look up the current authoritative decision for a natural-language question.

Input

fieldtyperequirednotes
questionstringyese.g. "Are we still shipping the new onboarding flow?"
typeaction_item | follow_up | decisionnorestrict to one type
ownerstringnorestrict to a named owner (substring match, case-insensitive)
areastringnotopical hint to bias retrieval
surfacestringnothe external system this task concerns (e.g. "jira", "salesforce"). When Rithmo does not observe that surface, a miss returns a typed not_observed instead of a bare no_record — absence on an unobserved surface is unknown, not "no".

Output — a discriminated union on result with three variants: "found", "found_record", and "no_record".

⚠️ Do not write result === "found" ? act : treat-as-no-record. found_record is a real answer, and two-state branching silently routes valid answers into your no-record path.

But do not flip to ["found", "found_record"].includes(result) either — that is also wrong. A found_record can name a subject that is genuinely on record while carrying no answer text (subject.currentAnswer === null). The correct rule gates on a usable answer:

js
const r = res.structuredContent;const answer =  r.result === 'found'        ? r.answer :  r.result === 'found_record' ? r.subject.currentAnswer :  null;if (answer != null && String(answer).trim() !== '') { /* act */ }else { /* hold — no answer to act on */ }
result: "found" — a live answer from the commitment ledger
jsonc
{  "result": "found",  "answer": "<verbatim decision text>",  "status": "open | in_progress | blocked | fulfilled",  "lifecycle_state": "confirmed",  "owner":  { "state": "owned", "name": "...", "user_id": "..." },   // or { "state": "unowned" }  "source": { "state": "resolved", "system": "meeting|slack|github|...", "ref": "...", "url": "..." }, // or { "state": "unresolved" }  "decided_at": "2026-07-19T15:04:00Z",  "due_at": "2026-07-25T00:00:00Z",   // or null  "rationale":  "<the WHY behind the decision>",   // or null  "conditions": "<what it depends on>",            // or null  "director":   "<who assigned/directed it>",      // or null  "context":    { /* arbitrary payload */ },       // or null  "supersedes": {                                  // or null    "answer": "<the decision this one replaced>",    "decided_at": "2026-06-02T10:00:00Z",    "supersedes": null                             // recurses — walk it for the full chain  },  "spine": {                                       // or null; present when spine context resolves    "decision_id": "...",    "lifecycle_state": "<canonical live state>",    "verified": true,    "board_status": "..." ,                        // or null    "presence": [{ "provider": "github", "external_status": "...", "external_url": "..." }],    "related_count": 3  },  "confidence": 0.71}

supersedes is the supersession history. It is a linked list, newest-first: each node is a prior decision this answer replaced, with its own supersedes pointer. Walk it to reconstruct how the answer changed over time (e.g. cloud → private env → customer servers). null means this decision superseded nothing. The chain is cycle-guarded and capped at 12 hops.

result: "found_record" — a live answer from the subject record

Returned when the subject record holds a current answer that the commitment ledger does not. This is an answer, not a refusal.

jsonc
{  "result": "found_record",  "subject": {    "subjectId": "...",    "label": "<human-readable subject>",    "state": "<subject state>",    "currentAnswer": "<the current answer>"   // may be null  },  "observation": {                            // optional    "sinceMs": 1783606904319,    "claim": "<nothing observed since … bears on this subject>",    "surfaces": [      { "surface": "slack", "observedFromMs": 0, "observedThroughMs": 0, "basis": "..." }    ]  }}

The answer lives at subject.currentAnswer, not answer.

currentAnswer may be null, and that case is not actionable. The subject is genuinely on record — Rithmo knows what you are asking about — but there is no answer text to act on. That is a meaningful signal (the subject exists, its answer does not), and it is distinct from no_record, which says Rithmo has no confident match at all. Either way you must hold: route a null-answer found_record down the same path as no_record.

result: "no_record" — no confident answer
jsonc
// bare form — no confident match, no refusal detail{ "result": "no_record" }
// typed-refusal form — `record` explains WHY{  "result": "no_record",  "record": { /* one of the four shapes below */ }}

When present, record.status is one of:

record.statusmeaningextra fields
closedthe subject is settled and the premise is deaddisposition: reversed | abandoned | superseded; subject
no_recordthe record was consulted and genuinely has nothingreason (string)
not_observedRithmo does not observe the surface you asked about, so absence proves nothingunobserved_surfaces: string[]

All three carry premise_check, and closed/no_record/not_observed may also carry an observation.surfaces array describing what was read and over what window.

jsonc
"premise_check": {  "safe_to_proceed": false,  "held_because": "<why acting on this premise is unsafe>"}

premise_check.safe_to_proceed is always false when the field is present. It is not a boolean to evaluate — it is a marker that the answer is held, and held_because tells you why. Never proceed on a no_record, with or without a premise_check.

One reason value is worth special handling: a record.status: "no_record" whose reason starts with record_unavailable: means the record could not be read (a transient failure). That is not "no record" and not "we decided no" — hold and retry.

Notes:

  • Deterministic answer. For the same resolved decision, every found field except confidence is byte-identical every call (it's the record verbatim — no LLM in the response path). confidence is a ranked-retrieval signal and may vary slightly; don't gate on its exact value.
  • owner and source are discriminated states, never bare nulls — unowned and unresolved are real, meaningful answers your code can branch on.
  • no_record is expected, not an error. Treat it as a branch: Rithmo has no confident answer, so don't act on a guess.

list_decision_changes

Drain decisions whose state changed, since a cursor. Built for a polling trigger.

Input

fieldtyperequirednotes
sincestringnoopaque cursor from a prior call. Omit on the first call to start "from now" (no backfill); pass "0_" to backfill from the start.
limitintegerno1–200, default 50. Out-of-range or non-integer values are rejected as a validation error, not silently clamped.

Output

jsonc
{  "changes": [    {      "decision_id": "...",      "answer": "...",      "status": "open | in_progress | blocked | fulfilled | dropped | superseded",      "lifecycle_state": "confirmed",      "owner": { ... }, "source": { ... },      "decided_at": "ISO", "due_at": "ISO|null",      "rationale": "...|null", "conditions": "...|null",      "director": "...|null",  "context": {...}|null,      "changed_at": "ISO"    }  ],  "cursor": "<opaque — pass as `since` next time>",  "has_more": true   // page was full; call again to drain the rest}

Each change carries the same record core as query_decision's found — including rationale, conditions, director, and context. It does not carry supersedes, spine, or confidence; those are query_decision-only.

status is wider here than in found. Reversals are emitted as changes, so status can be dropped or superseded — values query_decision's found never returns (it only resolves to live rows). If you mirror this feed into your own store, handle those two values explicitly.

Cursor semantics:

  • First call (no since) returns changes: [] and the current head cursor. You start from now and do not replay history. has_more is false.
  • has_more is rows.length === limit — a full page. It can be true on an exactly-full final page, so the next call legitimately returns zero changes. That is not an error; it is how the drain terminates.
  • When a page returns no rows, cursor is echoed back unchanged, so re-polling with it is safe and idempotent.
  • Ordering is by (changed_at, decision_id).

whoami

No arguments. Returns { org_id, scopes } — a quick way to verify your token and connection.

Auth

Every request carries a per-organization service token as a Bearer header:

Authorization: Bearer rmcp_...

The token resolves to exactly one organization and must carry the mcp:read scope; every result is scoped to that org. Keep it in a secret/credential store, never in code.

A request with no token, or an unknown token, is rejected with 401. There is no anonymous access.

Registry

Rithmo's MCP server is described by server.json in this repo, under the registry identity ai.rithmo/rithmo (version 0.1.0). Full docs live at https://rithmo.ai/mcp.

Deployment model

The RITHMO_MCP_URL above points at Rithmo Cloud. Self-hosted Rithmo deployments expose the same MCP server at the customer's own address — same tools, same contract, different host.

Using it from n8n

See n8n/ for an importable workflow (query a decision before an execution step) and setup notes.

來源:README.md,提交 c141954

工具

0
該伺服器需要 API 金鑰等憑證,因此 SourceWeft 無法在沒有憑證的情況下讀取其工具,也未收錄。

版本歷史

1
  1. v0.1.0最新Sep 22, 2026

評分

已滿足適用檢查項的 75%

該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。

  • 近 30 天內可連通必需25 分
  • 已宣告認證方式必需10 分
  • 已列出工具15 分

    請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。

  • 描述必需15 分
  • 圖示10 分
  • 文件15 分
  • 授權條款10 分

    請在儲存庫或註冊中心條目中宣告授權條款。