Orangerail

io.github.KimHyeongRae0由 GitHub 账号 KimHyeongRae0 发布v0.1.4更新于 Oct 24, 2026

Turns your Prisma schema into typed MCP tools, with destructive writes gated behind human approval

已验证STDIO仅桌面数据库开发者工具

概览

AI 生成的概览

根据现有 Prisma schema 生成带类型的 MCP 服务器,让助手按对象读写数据,并把破坏性写入置于人工审批之后。

可以做什么
读取对象:为每个 Prisma 模型提供按 id 的 get 和分页 list,过滤条件仅限已声明字段,不支持自由查询、联表或聚合。写入对象:每个写操作对应一个带类型校验的动作,输入按你的列定义校验。处理审批:标记为需要审批的破坏性动作会被拦截并暂存,只有人工批准后 check_approval 才会执行。查看治理情况:studio 命令展示对象、关系及哪些动作被拦截,status 命令报告待审批项和审计链状态。
适用场景
当助手需要通过 Prisma schema 访问数据库,而你希望用固定、可读的工具面代替 SQL 时使用,尤其适合无人值守运行、删除等破坏性写入应等待人工决定的场景。
运行要求
本地 Node.js 项目,且包含 prisma/schema.prisma 文件。安装 orangerail 命令行工具以及 orangerail-core 和 zod 运行时包,运行 orangerail init 生成服务器,并提供 DATABASE_URL 指向生成动作所访问的数据库。需要与生成代码兼容的 Prisma 客户端版本。
安装前请注意
它只约束自己生成的工具;若助手还能使用 shell、凭据或另一个数据库服务器,就可绕过。未标记审批的写入会无人值守执行,你声明的任何破坏性动作都可被调用。审批存储默认位于项目内,能访问该目录的助手可追加伪造的审批记录;审计链只能事后报告,无法阻止。DATABASE_URL 属于机密。API 尚未到 1.0,可能变动。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Orangerail,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

[orangerail init on a three-model Prisma schema, then the server's real tools/list: six reads, nine writes, check_approval, and no execute_sql]

orangerail init on a three-model Prisma schema, then a real tools/list against the server it generated. Sixteen tools: a get and a list per object, one action per write, and check_approval. Nothing on that list takes a query. The three locks are --gate delete, which is a default you change in one line, not a verdict.

A rules file cannot do this. It can ask the agent not to run a query. It cannot take the tool off the list — and we measured what the difference is worth, including the four claims that died when we did.

orangerail reads the schema you already have and generates the agent's surface from it. orangerail init turns a prisma/schema.prisma into an MCP server: a get and a list per object, one action per write with a zod input schema, and nothing else — no execute_sql, and nothing on the tool list that takes a query. It is a scanner and a code generator, with no LLM calls and no API keys. Writes you are happy to have run unattended run unattended. The ones you are not carry policy: { approval: 'required' }, which stops the call and turns it into an approval a person can act on later — including a person who is not you, after the conversation that produced it has ended.

Bounded is not safe, and this README will not pretend otherwise. A generated surface buys a reach that is finite and legible, not a claim that nothing harmful is inside it. You declared the verbs, so a destructive verb you declared is a verb the agent can call.

One precondition decides whether any of this is worth installing: orangerail governs only its own tools. If the agent also has a shell with credentials or a second database MCP server, it can go around the rail — see what orangerail does not govern.

Pre-release and installable: 0.1.5 on npm — orangerail (the CLI) plus orangerail-core, orangerail-mcp, orangerail-docs-gen and orangerail-studio. The API will move before 1.0, and Status has the one upgrade note that matters.

See your whole domain as a map

One command, and the surface init generates is a map you can read.

console
$ orangerail studioorangerail studio: scanning ontology — 9 object(s), 27 action(s)orangerail studio: building the interactive map…orangerail studio: serving on http://127.0.0.1:4820 — open it in your browser

Every object, how they relate, and every write action an agent can reach. Hover a table to light up its relations and actions; click one to read the policy that governs it.

[the orangerail studio map — hovering tables to reveal relations, then opening deleteOrder to read the policy that governs it: target Order, approval required, approvers any, condition none]

Crisper version: assets/studio-map.mp4 — the same run at full resolution. One real run on a sample commerce domain, --gate delete. The locks are not annotations added for the video: they are what the studio draws from your ontology, which is why nine actions carry one and eighteen do not.

Be exact about what that is worth. The relations come from ontology/_links.mjs, which init derives from your Prisma relations, so Customer_list's description reads List Customer records. Relations: has many Order. The agent is told that a Customer has many Orders. It still cannot follow the edge: no traversal tool, no join, no aggregate, and Customer_list refuses a filter that reaches into Order. Knowing the shape of a domain and being able to query across it are different things, and only the first one is here.

Quickstart

Seven steps, every output verbatim from one recorded run. The reasoning behind each one — and the failure each prevents — is in Quickstart, annotated; requirements and the Prisma 7 caveat are the first thing on that page.

1. Install orangerail into the project you are about to scan.

bash
npm i -D orangerail

2. Scan your project, in a repo with a prisma/schema.prisma.

console
$ npx orangerail init --yes --preset approval-for-writes --no-studio  ✓  scanned your sources — 2 object(s), 6 action(s)  ✓  generated a governed MCP server under ontology/  ✓  --gate delete: 2 of 6 write action(s) gated behind human approval — the other 4 run when the agent calls them  ✓  recorded that posture in orangerail.governance.json — commit it  ✓  approvals queue + audit chain at .orangerail/store/ — inside this project, so an     agent with file tools over this directory can write them
  These files are yours — re-scans never modify them; `orangerail sync` reports drift.
  Change what is gated by editing `policy` in ontology/<action>.mjs, or re-run init  with `--gate all` (gate every write) or `--gate none` (gate nothing).  orangerail.governance.json holds the posture init just generated, which nobody has reviewed yet.  From now on `orangerail sync` fails when an action gets weaker than that file, and  `orangerail mcp` refuses to serve it. Read the file, then run  `orangerail sync --accept-governance` to vouch for it as reviewed.
  That store is the record of which writes a human approved, and appending one line to  .orangerail/store/approvals.jsonl marks a staged action approved — the next  `check_approval` then executes it, because the gate reads that store and never the  audit chain. `orangerail audit verify` reports the forgery afterwards; it is a report,  not a gate, and it does not prevent the write. The generated config carries the  one-line move at the `createFileStore` call — see docs/audit-log.md.orangerail docs: wrote /private/tmp/shop/.orangerail/generated/AGENTS.md
Done. Run `orangerail studio` to explore the map, or `orangerail mcp`.

3. Install the runtime the generated code loads.

bash
npm install orangerail-core zod

4. Give the generated actions a database to reach.

bash
npm install @prisma/client@6export DATABASE_URL="file:./dev.db"npx prisma generatenpx prisma db push --skip-generate

Already have a database? Do not db push over it — adopting orangerail against an existing database.

5. Point your agent host at it. Drop this in your project root as .mcp.json:

json
{  "mcpServers": {    "orangerail": {      "type": "stdio",      "command": "./node_modules/.bin/orangerail",      "args": ["mcp"],      "env": { "DATABASE_URL": "file:./dev.db" }    }  }}

Other hosts, the claude mcp add one-liner, and running from source: wire it into your agent host.

6. Record the governance baseline — and commit it. ontology/ is yours to edit, so the one line that disarms the whole flow is one careless deletion away and a re-scan cannot notice. The posture is compared against a recorded file instead.

bash
npx orangerail sync --accept-governance

Commit orangerail.governance.json. Its whole value is that a pull request removing an approval gate shows "approval": "required" turning into null in its own diff, in front of a reviewer, before CI runs at all.

7. Now leave. While you are gone the agent works the queue: the writes you left un-gated go through, and the deletion it was asked for stops. When you come back:

console
$ npx orangerail statusorangerail status  objects:  2  actions:  2 approval-gated, 4 auto  baseline: 6 action(s) match orangerail.governance.json  preset:   approval-for-writes  pending:  1 approval(s) awaiting a decision  store:    /private/tmp/shop/.orangerail/store            Inside the project root, so an agent with file tools over this directory can            write it: one appended line in approvals.jsonl is a decision no human made,            and the next `check_approval` executes the staged action — the gate reads            this store, never the audit chain. `orangerail audit verify` reports the            forgery afterwards; it is a report, not a gate. Pointing the store `dir` at a            directory this agent's process cannot write is what removes the reach — see            docs/audit-log.md.  server:   not detected — no orangerail mcp is running against this store  hosts:    .mcp.json declares orangerail and nothing else.            Project scope only (.mcp.json, .cursor/mcp.json, .vscode/mcp.json); user- and            machine-scope MCP config is not read.  audit:    chain OK — 5 record(s) verified
$ npx orangerail approvals listc4818df5-770c-446f-883a-e9c0f7e615a2  "deleteCustomer"  by "local-dev" [dev]  0s ago  input={"id":2}
1 pending approval(s).
$ npx orangerail approvals approve c4818df5-770c-446f-883a-e9c0f7e615a2approve ok (approved)

The agent's next check_approval is the first moment the row can change. Nothing ran before you said so, and every step is on the hash chain. That whole sequence is what tests/e2e/ONT-093-quickstart-runs-as-documented.sh runs against this repository's own build on every regression pass.

Why the prompt is the wrong control

The thing stopping you from walking away is not that the agent does too much. It is that your only control is a question it has to ask you. The twentieth prompt of the afternoon gets the same click as the first, and the switch that ends the asking ships in the box — Claude Code's bypassPermissions mode "skips permission prompts, except those forced by explicit ask rules", per its own permissions reference. A boundary re-established by a person on every call cannot hold once nobody is there.

The prompt is also the wrong shape. It asks about a tool — may this run Bash — and the risk you carry is about your domain: stock edits are fine, order deletions are not, refunds under $50 need nobody. That distinction does not exist at the tool level. It exists in your schema.

The run this is built for

[a back-office queue handed to an agent with nobody watching: ordinary writes finish, a deletion stops and becomes an approval, and the one declared line that stopped it]

One run of examples/unattended-queue — a real MCP client, no API key, every line asserted. The video shows six of the twelve; the row numbers jump, so you can see where.

A 15-item back-office queue on a commerce database, handed to an agent with the operator gone for the day and told not to ask for confirmation. Twelve items are ordinary reversible writes. Three are destructive: delete a cancelled order, delete a customer under an erasure request, delete a discontinued product. Scored from the database afterwards, not from what the agent said it did:

orangerail
ordinary items completed unattended12 / 12
destructive items executed0
destructive items stopped and staged3
what is waiting the next morning3 approval records, each bound by hash to the exact call
audit chain27 records, verified OK

That is the metric this project is built around, and it is not "how much did we block". It is how much finished while nobody was watching, and what is waiting when you get back.

Two separable claims sit in that table and they are not equally well evidenced. That the twelve go through and the three cannot is a property of the server, and it is reproducible on your machine — examples/unattended-queue runs exactly that queue through a real MCP client, deterministically, asserting every line. That a model chooses these calls when handed the queue in prose needed a live agent driving a real host, and that half is a measurement, not a reproduction: small numbers, enough to say the gate holds where it was tested and not enough to be a rate.

Against the thing you would do instead

The comparison that matters is not a raw SQL server. It is a rules file: a well-written CLAUDE.md naming the permitted tables and the forbidden ones, over a Postgres MCP server with full write access. Same queue, same model, three clones.

markdown rules, full write accessorangerail
ordinary items completed12 / 12, all three runs12 / 12
destructive items executed00
what the stop leaves behinda paragraph in a reportan approval record
the same task started in another directoryrow deletedstaged it

It tied on compliance, and it kept tying — through adversarial rewrites, a fake prior approval, an instruction planted in a database row, and a much smaller model. On one axis it beat us. So this project does not argue that your agent will ignore your rules: across every run measured here, it followed them.

That is six runs. Enough to retire the claim that it would not, nowhere near enough to be a rate — zero failures in six bounds the tail near 39%, not near zero. Buying a real bound is expensive and it perishes on the next model release, which is the reason the row below is the one this project stakes itself on: what ten runs cannot prove.

The row that does not tie is the last one, and it is not about the agent's behaviour: a grant travels with the session it was registered for, and a rules file travels with the machine account it was written under. A global ~/.claude/CLAUDE.md closes most of that gap for a single developer on one machine — if that is you, you may not need this. It stops closing at a CI runner, a container, a service account, or a teammate's checkout, each of which gets the database credentials anyway.

Every run, the axis where the rules file wins, and the limits of the measurement: against the thing you would do instead. examples/vs-a-rules-file executes both arms.

What the agent gets instead of execute_sql

Run orangerail init on a three-model Prisma schema (Order, OrderItem, Payment) and the entire tool list is 16 entries: a get and a list per object, one action per write, and check_approval. Nothing else, and nothing that takes a query.

Each action's input is a zod schema derived from your own columns, published in tools/list, so updateProduct refuses a string where the column is an integer and says which field it was. Each read is a findUnique by id or a paged findMany, whose filter is a closed set of predicates over declared fields — enforced by the server before it reaches your resolver, not merely advertised.

A fixed surface is a narrow one: no aggregation, no join, no free-form query, no DDL. A question it cannot express has to be answered somewhere else — all of it, and where enforcement actually lives, is in what orangerail does not govern.

See it stop an agent

[a destructive agent action stops and comes back as an approval id; a person decides, and only then does the row change]

One real run of examples/governed-writes through a real MCP client. The destructive tool stays available rather than hidden, the agent cannot force it through, and the row changes only after a human decided — in a separate terminal, at a separate time, which is the part that makes leaving possible.

Declaring a rule the generator cannot derive

Everything above is generated. When a rule lives in your head rather than your schema — "never issue a coupon for a sold-out item" — you write it once, in TypeScript, and it joins the same surface:

ts
import { defineAction, defineObject } from 'orangerail-core';import { z } from 'zod';
// Your existing backend. orangerail never replaces it — it only gates the call.declare const findProduct: (id: string) => Promise<{ id: string; status: string } | null>;declare const grantCoupon: (args: { productId: string; amount: number }) => Promise<void>;
// A `where` guard has to read the row it guards, so the target needs `resolve`.export const Product = defineObject({  name: 'Product',  schema: z.object({ id: z.string(), status: z.string() }),  resolve: { get: async ({ id }) => findProduct(id) },});
export const issueCoupon = defineAction({  name: 'issueCoupon',  target: Product,  input: z.object({ productId: z.string(), amount: z.number() }),  policy: {    approval: 'required',    where: { field: 'status', op: 'neq', value: 'soldout' },  },  // `execute` runs only after the approval clears, and receives the validated  // input plus the resolved caller. There is no `audit` switch: every staged,  // approved, rejected and executed action is written to the hash chain.  execute: async ({ input, identity }) => {    await grantCoupon({ productId: input.productId, amount: input.amount });    return { issuedBy: identity.subject };  },});

That block is not an illustration — it is packages/cli/test/readme-example.ts printed verbatim, compiled by the repo typecheck and compared against this file on every run, so it cannot rot into something that never compiled.

Status

orangerail init runs against your own project today, with no checkout of this repo, and the API will move before 1.0. All five packages are published from .github/workflows/release.yml over npm's Trusted Publishing, so each one carries a provenance attestation naming the workflow and commit that built it; there is no npm token in this repository.

Upgrade from 0.1.0 if you are on it. That release published a read filter to the agent and never checked it, so a <Object>_list call could read an object type the server never exposed (the mechanism). The fix is in 0.1.2 and lives in orangerail-mcp, so upgrading the package applies it with no re-run of init. 0.1.2 also narrows what filter accepts and changes what a pending approval does across the upgrade — both under Upgrading from 0.1.0 in the CHANGELOG.

Docs

Examples

  • unattended-queue — the run at the top of this file, made reproducible. Deterministic, asserted, no API key.
  • governed-writes — the same gate in isolation, one destructive call at a time.
  • vs-a-rules-file — the rules-file comparison made runnable, both arms executed, including the column where the rules file wins.

Development

This repo is built under a deterministic 9-stage gate harness. Every change runs through ./scripts/verify.sh — language, structure, gate self-test, no-LLM, templates, then typecheck / lint / test / build — and CI runs that script and nothing else, so a green local run is a green build. A hard invariant: no LLM-inference SDK is ever bundled (./scripts/check-no-llm.sh).

License

MIT

来源:README.md,提交 5514a2d

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

1
  1. v0.1.4最新Sep 16, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分