Pacioli — the ERPNext broker you can hand the books

io.github.john-broadway由 GitHub 帳號 john-broadway 發布v0.40.1更新於 Oct 24, 2026

Governed agent access to ERPNext — MCP + A2A doors, one spine. No debit without a credit.

概覽

AI 產生的概覽

本機 MCP/A2A 代理,讓 AI 助理以「規劃—同意—執行—憑證」的受控方式存取 ERPNext 的會計與交易單據。

可以做什麼
在寫入總帳前規劃並預覽 ERPNext 過帳,包含預估的總帳影響與風險標記(plan_submit、plan_cancel)。對受管控的交易單據執行提交、取消與修訂,涵蓋會計、庫存、資產、製造與分包,docstatus 變更需憑同意標記。為每次寫入產生雜湊鏈式的意圖憑證與結果憑證,缺少結果的寫入會被標記為孤兒。核對憑證簿的試算平衡,並把憑證簿標頭錨定到本機之外,以便發現被截斷或替換的帳簿。檢查哪些介面可達、哪些已授權,並依期間結帳,產出報表、對帳與回應。
適用情境
當助理需要操作真實 ERPNext 站點的帳務,但每次過帳都必須先規劃、經人工同意並留下憑證,而不是直接執行時使用。適合會計、庫存與資產業務,尤其是重視最小權限憑證範圍與稽核軌跡甚於便利的情境。
執行需求
需要本機 Python 環境,用 pip 安裝或透過 uvx 執行。需要可存取的 ERPNext/Frappe 站點,以及供代理使用的 ERPNext API 憑證;README 要求該憑證必須由獨立的 pacioli-guard bench 應用限定範圍。同意標記須由另一主體在代理之外產生,並需開啟 guard 的 require_consent 設定。
安裝前請注意
代理憑證本身可以提交發票,因此任何直接持有該憑證的人都能呼叫 ERPNext 的 REST API,繞過規劃、同意與憑證流程;README 將 guard 範圍限定列為硬性前提。安裝 guard 後若未開啟 require_consent 則毫無作用,且憑證層不涵蓋 OAuth2 Bearer 權杖、桌面工作階段、背景工作、伺服器指令碼或 bench 主控台。原生 SQL 與 db_update 類寫入以及 Document.discard 仍可繞過兩道關卡。此伺服器會寫入、提交與取消會計資料。

安裝

在 SourceWeft 中

  1. 開啟 儀表板中的 Pacioli — the ERPNext broker you can hand the books,將其新增到工作區。
  2. 為需要使用其工具的對話啟用該服務。

Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。

其他 MCP 客戶端

參照 儲存庫 中的啟動說明。

README

[CI] [CodeQL] [Release] [PyPI pacioli] [PyPI pacioli-guard] [Python 3.12+] [License Apache-2.0] [OpenSSF Scorecard]

Least-privilege governance for ERPNext, and a governed agent front door built on top of it — MCP and A2A, one spine behind both. A door admits; it never decides.

In 1494, in Venice, Luca Pacioli printed the Summa de arithmetica — and inside it, the tract that taught the world double-entry bookkeeping. He wrote it down not because merchants couldn't count, but because they couldn't trust. His rule is one sentence deep: no debit without a credit. Every action gets an equal, opposite, recorded counterpart — and a book that doesn't balance confesses on the spot. Five hundred years of prove it or it didn't happen.

This repo is that tract, written again for a new clerk. The merchant's problem hasn't changed; the clerk has. He balanced books for merchants who couldn't watch every clerk. This balances them for owners who can't watch every agent. Same problem, five centuries apart. Same fix.

See it before you read the rest: the demo. One command on a bare pip install pacioli pacioli-guard shows the floor deciding, and a sealed receipt that will not be quietly rewritten. Every verdict is the shipped code; nothing is staged.

[Portrait of Luca Pacioli, attributed to Jacopo de' Barbari, 1495]
Tavola I — The friar at his slate, attributed to Jacopo de' Barbari, 1495. The glass rhombicuboctahedron hangs at his left; the Summa sits under the dodecahedron at his right. This repo's device is that hanging solid.
[圖片]

Distinctio I — Of the Two Instruments

[T]

There are two instruments in this house, and they compose — they do not couple. The first guards every api-key credential on the site whether an agent is involved or not; the second is the one governed door an agent may use. The first is the floor under that door; the second stands on it and submits to it. What follows is each instrument as the treatise would have it: what it is, what it refuses, and how it is installed.

What the floor covers, plainly, because a floor that overstates itself is not a floor. Two questions, two of frappe's own public hooks, each at the altitude where its question is legible.

Which credential is acting, and what may it call? The credential gate rides auth_hooks, which runs when an api-key credential authenticates. It governs token/Basic api-key requests across every REST mount, and it does not govern OAuth2 Bearer tokens, desk/cookie sessions, background jobs, the scheduler, server scripts, or the bench console. "Which credential is this" only exists at authentication time, so this gate can only live here, and that boundary is inherent to it.

Was this act consented? That is a property of a document, so it is enforced on the document, via doc_events on before_submit and before_cancel, plus before_gl_preview and before_sl_preview since guard 0.13.0. The last two gate a rehearsal of a posting rather than a posting: ERPNext previews a ledger by performing it and rolling the transaction back, so previewing a submit requires the same marker the submit requires, and does not spend it. Every path that posts to the ledger through the ORM passes it: REST, run_doc_method, frappe.client.submit, frappe.client.insert with a submitted body, the desk Save/Submit endpoint, bulk submit, a raw docstatus field write followed by a save, a background job, a server script, and the bench console. Consent does not inherit the credential gate's transport boundary.

What still walks around both, stated up front rather than discovered later:

  1. A write that skips the document lifecycle entirely: raw SQL, and db_update/db_set-style field writes. ERPNext core does this itself when reposting.
  2. An actor who can break the grant read. The consent handler runs on every document on the site, so if reading the grant raises it returns rather than throwing — a wildcard handler that crashes takes the site with it. A deliberate trade, and a real residual.
  3. Document.discard. It is whitelisted, so it is reachable, and it sets docstatus = 2 with db_set while firing before_discard rather than before_cancel. It refuses anything that is not a draft, so no ledger entry is ever reversed by it and nothing posts — the exposure is a draft being taken out of play, not books moving. Gating it wants its own act type rather than being folded into "cancel", since consenting to reverse a posted entry and consenting to abandon a draft are different permissions. Named here rather than rounded off.

No single frappe extension point covers all of it, so coverage here is a composition with a stated residual rather than a claim of totality, which is a thing the platform cannot give anyone.


The Guard — the counting-house door

No one writes in the books but the appointed hand, and only in the books appointed to them.

A Frappe/ERPNext bench app (distribution name pacioli-guard on PyPI; installs as the pacioli_guard bench app). It binds any API credential — an integration, a Zapier/n8n flow, a script, a vendor token, a cron job, an AI agent — to an allowlist of methods (and, if granted, DocTypes), enforced at the credential layer through Frappe's public auth_hooks extension point, deny-by-default. No core fork. It governs every api-key credential on the site, agent or not — see the coverage note above for what that does and does not reach, and for the separate document-layer gate that carries consent.

bash
# from your bench directory: wheel into the bench env, then install the appenv/bin/pip install pacioli-guardbench --site <your-site> install-app pacioli_guard

The Broker — hand it the books

The clerk may propose; only the merchant disposes.

A standalone, pip-installable broker (pip install pacioli) that gives an AI agent a governed way to touch ERPNext — through the door of your choosing: MCP or A2A, one spine behind both. 51 governed doctypes, 265 tools — the full submittable transaction surface of an ERPNext company — every write through PLAN → CONSENT → execute → PROVE, deny-by-default beyond that. The door admits; the spine decides.

bash
pip install pacioli

How the instruments compose

Guard is the floor; the broker is one consumer that binds itself to it. Guard scopes and enforces any credential on the site — you don't need to run an agent to need it. The broker is the agent-facing front door, and its own ERPNext credential must itself be pacioli_guard-scoped to exactly the calls it makes (the governed doctypes and their submit/cancel vectors — shipped as data lists the deploy kit applies). Without that scoping, anything holding the broker's raw credential can call ERPNext's REST API directly and bypass PLAN, CONSENT, and PROVE entirely — so the broker's own README states that scoping as a hard precondition, not an optional hardening step.

And scoping alone is not sufficient — this was proven against a live bench on 2026-07-25, and it is worth stating plainly rather than leaving as an inference. The broker must be allowed to submit invoices, because submitting invoices is what the broker is for. So its credential is allowed to submit invoices, and a direct run_doc_method call holding that credential submitted one with no plan, no marker and no receipt. The ledger moved. The guard behaved exactly as designed and the allowlist was exactly right: no allowlist can close this, because the call being made is the call the broker exists to make. Possession of the key was permission to post.

The guard closes it at the floor rather than in the broker: API Key Scope.require_consent (opt-in, off by default) refuses a docstatus-changing act unless it carries a live, single-use Pacioli Consent Marker minted for that exact document and that exact act, and spends the marker on use. Consent to post is not consent to reverse: cancel moves the same document and writes the opposite GL entries, so a submit marker never spends on a cancel. And the marker must be written by a different principal than the credential it authorises — the floor compares the minter against the caller and refuses a match, because a credential that can mint its own consent is a gate writing its own permission slip. minted_by is set by the server from the authenticated session, overwriting anything the caller supplies, so that separation is established rather than self-reported.

This gate is enforced on the document, not on the request: doc_events before_submit / before_cancel, and since guard 0.13.0 before_gl_preview / before_sl_preview as well. That is deliberate and it is the difference between governing one door and governing the act. A credential holder who avoids the api-key REST path entirely, via an OAuth token, a desk session, a background job, a server script or the bench console, still meets this gate.

Meeting it and passing it are different things, and the difference is worth stating rather than leaving to be discovered. A marker is presented in an HTTP header, so on a path that has no HTTP request there is no way to present one: a consent-gated principal acting through a background job or the bench console can only ever be refused there. That is fail-closed and it is the intended posture, but do not read the paragraph above as "those paths work the same way" — they are covered in the sense that they are stopped, not in the sense that they are usable.

Being opt-in has a consequence worth stating plainly: installing the app changes nothing by itself. An install is exposed to the bypass above until someone turns require_consent on for the credentials that matter.

This is composition, not coupling: Guard is independently useful, and not only for agents. Its two halves have two different reaches, and the difference is the honest part — credential scope binds any api-key credential (agent or otherwise) and sees nothing else, while the consent gate runs at the document layer and covers every ORM path — desk sessions, background jobs, the scheduler, server scripts, the bench console — for a credential you have gated. The broker is the one piece that chooses to sit on top of it and honor the same floor it enforces on everything else.

[圖片]

Distinctio II — Of the Memorandum, the Journal, and the Ledger

[D]

Double entry is not a metaphor here. It is the design. The bookkeeping tract of the Summa (Particularis de Computis et Scripturis) organizes a merchant's truth into three books, and slice for slice they are this system. In his journal every entry named its debit with per and its credit with a — nothing moved on one leg. Read the spread the way he ruled it:


folioper — his book (1494)a — here
¹The memorandum (memoriale) — every transaction written down first, roughly, before any formal entryPLAN — plan_submit / plan_cancel write the memorandum: the projected GL and the risk flags, dated, bound to the draft. Nothing posts.
²The journal (giornale) — each entry rewritten in fixed form, its debit marked per, its credit marked aPROVE — the hash-chained receipt book. The intent receipt is the per, the outcome the a; an intent with no outcome is a debit with no credit, and the trial balance surfaces it as an orphan.
³The ledger (quaderno) — the book of account itselfERPNext's GL — the broker never writes in it directly; every entry reaches the ledger through the journal's discipline.

And the same law runs across every pillar — for every action, its recorded counterpart:

foliothe action (per)its recorded counterpart (a)
⁴An agent wants to writePLAN — the projected GL impact, written down before the act. The mirror entry precedes the entry.
⁵A plan existsCONSENT — a human mints the marker, out of band. Agent proposes, human disposes: two hands on every posting, never one.
⁶The write firesPROVE — an intent receipt before, an outcome receipt after. A write missing its outcome is an orphan, and it surfaces exactly the way an unbalanced trial balance does.
⁷A posting standsUNDO — ERPNext's own cancel/amend is double-entry: the reversal posts equal-and-opposite rows. Nothing erased, everything answered.
⁸A credential existsGuard — no capability without its explicit grant line. Deny-by-default is "no entry without authorization," applied at the credential layer.

Same law at every layer: nothing moves without its counterpart. A plan without consent doesn't post. A write without a receipt is flagged. A credential without a grant is refused. The one system you can't trust is the one where an action can happen alone — unplanned, unconsented, unreceipted. Everything Pacioli refuses is exactly that: the lone entry.

[The hollow rhombicuboctahedron drawn by Leonardo da Vinci for De divina proportione]
Tavola II — Vigintisex Basium Planum Vacuum: the same solid, drawn hollow by Leonardo da Vinci for Pacioli's De divina proportione. Leonardo illustrated Luca's book; the debt is repaid here.
[圖片]

Distinctio III — Of the Counting-House Door

His counsel, applied at the credential layer:

  • Two hands on every entry. The clerk writes; the merchant grants. The consent marker is minted (pacioli mint) outside the agent's reach — no posting on one hand's authority.
  • The counting-house door. No one writes in the books but the appointed hand, and only in the books appointed to them — that is Guard, per-credential and per-doctype, deny-by-default.
  • The registered book. The books take their authority from a mark held outside the bookkeeper's own hand. pacioli anchor carries the receipt-book's head off the box — a registration the book cannot rewrite, so a truncated or swapped book confesses against it.
[圖片]

Distinctio IV — Of the Closing of the Books

❧ Do not go to sleep until the debits equal the credits.

He gave merchants that rule, and it is the operating rule here too. pacioli verify is that sleep test — run the trial balance, and if intent and outcome don't pair, the book itself tells you.

The closed books. Closing the ledger is his own operation: rule off the book, carry the balances forward. The broker refuses to write in a closed book — a closed Accounting Period, a Period-Closing-Voucher boundary, a frozen-books date — and never slips a backdated or future-dated entry past the ruling-off.

[圖片]

Distinctio V — Of the Inventory

[O]

Open no books before the survey. He starts the merchant with a complete inventory before a single entry — what you hold, what is owed, what is missing. pacioli doctor is that survey for this house: what is reachable, what is granted, what refuses and why — and the road does not proceed until the doctor says ready. The census (pacioli close) then keeps the inventory honest for every period after: statement, reconciliation, response — every finding accounted for, or the book says so out loud.


[圖片]

Status

(Current versions live in each package's pyproject.toml/CHANGELOG.md — this README deliberately does not restate them.)

Guard — deny-by-default credential scoping with the deny-unknown posture (an unrecognized generic RPC is denied even if granted; per-doctype grants + four curated safe methods are the whole surface), live-proven on a real Frappe v16 bench (Gates 1, 7, 10).

Broker — 51 governed doctypes, 265 tools; 48 of the 51 live-proven end-to-end on a real ERPNext v16 bench as a guard-scoped seat (Gates 2–10, envelopes E1–E8, and the 2026-07 live-prove sweep): governed submit/cancel/amend across accounts, stock, assets, manufacturing, and subcontracting; Workflow-SoD consent; cascade cancel with dependent graphs (a 3-node Asset graph under one consent, live); governed Payment Reconciliation (stricter than ERPNext itself — the closed-books belt ERPNext skips for reconciliation, proven live, PHASE X); the armed-Budget control-plane probe (arm → the bench refuses the PO → disarm → the same PO passes, every step disclosed pre-consent); the off-box anchor; a certified least-privilege reference seat; and the founding refusals (no debit without a credit — live). The whole arc is written up leg-by-leg in SCOPED-TOKEN-PROOF.md (PHASES A–X). See broker/README.md for exactly what ships and what doesn't.

License

Apache-2.0.

[圖片]
[The printer's device: the glass solid of the portrait]

Here ends the Summa of Governance: printed upon the counsel of Fra Luca Pacioli, who taught that no entry stands alone; set in these types for the governance of agents upon the books.

VENETIIS MCDXCIV · IN THE WORKSHOP OF JOHN BROADWAY · MMXXVI

來源:README.md,提交 892a5a6

工具

0
SourceWeft 尚未在沙箱中啟動過該軟體包,因此其工具未知。

版本歷史

2
  1. v0.40.1最新Sep 25, 2026
  2. v0.40.0Sep 16, 2026

評分

已滿足適用檢查項的 50%

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

  • 近 30 天內可連通25 分

    請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。

  • 已宣告認證方式10 分

    請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。

  • 已列出工具15 分

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

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