Visimark

io.github.michal-niedzwiedzki由 GitHub 账号 michal-niedzwiedzki 发布v0.4.1更新于 Oct 24, 2026

MCP server for VisiMark, a document integrity layer for Markdown: checks numbers against formulas.

概览

AI 生成的概览

VisiMark 的 MCP 服务器,让助手检查、修复并推断 Markdown 文档中数字背后的公式。

可以做什么
  • 检查 Markdown 文档:重新计算所有公式并报告已不一致的数字,不写入任何内容
  • 就地修复过期数值,只改动计算单元格和锚定值,不碰输入列与正文
  • 推断能复现普通表格中已有数字的规则,并可选地写入文件
  • 评估文档:输出计算值、解释工作表的输入与求值顺序、查询内置函数
  • 以资源形式提供写作规范,写入操作需由操作者开启写入门控
适用场景
适合助手撰写或修改带有数字的 Markdown 文档(发票、报价、预算、报告)时,希望核对算术而不是直接采信。也适合把这类文档放在 Git 中、希望在 CI 里获得可核查审计轨迹的团队。
运行要求
以 npm 包 visimark-mcp 通过 stdio 在本机运行,可全局安装或用 npx/bunx 运行;PATH 中需有 Node 或 Bun。未声明任何认证、环境变量或请求头。在 Windows 上 npm/npx 的包装脚本需要 PATH 中有 sh,Git Bash 或 WSL 可提供。
安装前请注意
多数工具为只读,但修复工具和带写入选项的推断工具会就地修改 Markdown 文件,提交前应查看差异。除非操作者开启写入门控,服务器不会写入。文档在本地处理;该格式刻意不提供网络、配置或插件访问。

安装

在 SourceWeft 中

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

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

其他 MCP 客户端

参照 仓库 中的启动说明。

README

VisiMark

[visimark MCP server – quality and maintenance score on Glama]

A document integrity layer for Markdown: it checks that every number in a document still matches the formula that produced it, so an agent's correct formula can't ship with a wrong total.

VisiMark checks the numbers in Markdown documents, especially ones an agent wrote. Every computed value carries its formula, a machine proves the two still agree, and CI fails when they don't. The document stays plain Markdown.

This matters most when the Markdown is written or edited by an AI agent. Agents are reliable at writing formulas and unreliable at the arithmetic those formulas describe: Net = Price * Qty is something an agent gets right, 50.00 is something it guesses. Write the formula instead of the number and the number stops being a claim and becomes a derivation — reviewable in a diff, re-runnable, enforceable in CI. The agent writes, VisiMark verifies, Git records the result.

A VisiMark document is an ordinary Markdown file. It renders correctly on GitHub, in VS Code preview, and through pandoc to both HTML and Word, today, with no plugin — verified, not assumed. What VisiMark adds is that every computed number in it carries the formula that produced it, that a machine can prove the two still agree, and that a change to either shows up as a small, readable diff.

Who it's for

Six audiences so far, each with a specific entry point. Kept short on purpose — it grows when one of these gets a real user, not before.

A wrong invoice that renders clean

docs/example-invoice-drift.md is a real B2B invoice after someone raised the on-call hours from 12 to 20 and updated nothing that depends on it. It renders as a clean, plausible invoice on GitHub, in a Markdown preview, anywhere — which is the entire argument for this project. Every number in it carries the formula that produced it; the syntax follows further down. Here is visimark check reading the same file a reviewer just skimmed and approved:

console
$ visimark check docs/example-invoice-drift.mddocs/example-invoice-drift.md
  STALE   lines.Net       · On-call support         3120.00 ≠ 5200.00    Qty * Rate  STALE   lines.VAT       · On-call support          717.60 ≠ 1196.00    Net * vat  STALE   lines.Gross     · On-call support         3837.60 ≠ 6396.00    Net + VAT  STALE   lines.Gross     · Discovery workshop      4428.50 ≠ 4428.00    Net + VAT  STALE   lines.net_total                          23300.00 ≠ 25380.00   SUM(Net)  STALE   lines.vat_total                           5359.00 ≠ 5837.40    SUM(VAT)  STALE   lines.gross_total                        28659.00 ≠ 31217.40   SUM(Gross)  STALE   lines.gross_total                        28659.00 ≠ 31217.40   SUM(Gross)  STALE   schedule.Amount · Signature               8597.70 ≠ 9365.22    Share * lines.gross_total  STALE   schedule.Amount · Delivery of backend    11463.60 ≠ 12486.96   Share * lines.gross_total  STALE   schedule.Amount · Acceptance              8597.70 ≠ 9365.22    Share * lines.gross_total  STALE   schedule.covered                         28659.00 ≠ 31217.40   SUM(Amount)  STALE   terms.early_pay_total                    28085.82 ≠ 30593.05  STALE   terms.early_pay_saved                      573.18 ≠ 624.35  STALE   8 prose anchors bound to the values above
  DATE    schedule.Due    · Delivery of backend   "15.10.2026"          Dates must be ISO 8601 calendar dates: YYYY-MM-DD.          Unambiguous — `visimark fmt --fix-dates` rewrites it to 2026-10-15.
  DATE    schedule.Due    · Acceptance            "11/12/2026"          Dates must be ISO 8601 calendar dates: YYYY-MM-DD.          Ambiguous: 2026-12-11 or 2026-11-12, 29 days apart. Fix by hand.
  NOTE    schedule.Days   · 2 rows not verified (upstream DATE errors)
  UNDEF   terms.eur_total   unknown name `fx_rate`          did you mean `fx_eur`?
  VECTOR  recon.variance    `schedule.Amount` is a column, not a value.          Wrap it in an aggregate: SUM(schedule.Amount)
  CYCLE   late_fees.base → late_fees.fee → late_fees.total → late_fees.base
  27 problems (22 stale, 5 errors)$ echo $?1

Twenty-seven problems: a payment date ambiguous by twenty-nine days, a cell someone nudged by hand to make a column look right, a circular reference — all invisible on the rendered page, all caught before a human had to notice.

Why this matters now

More and more of the documents that carry numbers are written as text and kept in Git: reports and research notes, estimates and budgets, quotes and invoices, project plans, financial summaries. Text is excellent for collaboration, review and version control. But ordinary Markdown has no way to say this number came from these inputs, and it must still agree with them — so the moment an input changes, every figure downstream of it is a guess until a human re-checks it by hand.

VisiMark adds that missing layer. The working stack for collaborating with an agent is a text editor over lightly formatted artifacts; Markdown already covers the prose, and this covers the calculation. The arithmetic is not the value — the audit trail is.

What it looks like

markdown
| Item  | Price | Qty |   Net ||-------|------:|----:|------:|| pen   |  5.00 |  10 | 50.00 || paper |  0.10 | 100 | 10.00 |
```vmark #orderNet   = Price * Qtytotal = SUM(Net)```
Order total: **60.00**<!--vmark=order.total-->

Four ideas, and that is the whole format:

  • A vmark block declares formulas for the table above it, and names the sheet.
  • Columns are uniform — Net = Price * Qty is one rule for every row, not a formula per cell. Columns with no rule are inputs, and are never overwritten.
  • Aggregates are scalars, declared alongside. This is what replaces the totals row; tables stay rectangular.
  • An anchor materialises a scalar into a sentence. The HTML comment is invisible in every target renderer, so the prose reads normally while the number stays machine-checkable.

The tutorial

docs/tutorial.md is the end-to-end tutorial: twenty-eight short chapters from a plain Markdown table to a checked document, a CI job and a script reading values back out. It teaches the language in dependency order, every finding check can report, and the one habit that keeps a green check meaningful. Every transcript in it is real.

There is a side-by-side reader for it at docs/tutorial.html, which shows each block's Markdown source next to its rendering, in lockstep.

Worked examples

docs/example-invoice.md is a complete B2B invoice that computes itself: line items, VAT, a payment schedule derived from the gross total, early-payment terms, a currency conversion, and a reconciliation that proves the instalments sum to the invoice. Its appendix explains each mechanism.

docs/example-invoice-drift.md is that same invoice with the drift shown at the top of this README — the 27 problems transcript above is check reading this exact file, and its appendix walks through every one of the 27 problems.

docs/example-quote-plain.md is the other direction: a quote with no VisiMark in it at all — no vmark block, no anchor, just a table and a total written in prose, the way an agent hands one over before anyone has wired it up. visimark infer reads it and proposes the rules that reproduce every number already there:

console
$ visimark infer docs/example-quote-plain.mddocs/example-quote-plain.md  table at line 10 — 4 rows, 7 columns
  column rules    Revenue    = Seats * Fee                  4/4 rows    Materials  = Revenue * 0.08               4/4 rows    Delivered  = Revenue + Materials          4/4 rows
  constants worth naming    0.08   also appears as "8%" in prose, line 18
  scalars matching figures in prose    72        line 17  = SUM(Seats)                seats_total    27600.00  line 18  = SUM(Revenue)              revenue_total    2208.00   line 19  = SUM(Materials)            materials_total    29808.00  line 19  = SUM(Delivered)            delivered_total    530.00    line 20  = AVG(Fee)                  fee_avg
  no rule found — treating as inputs    Module, Format, Seats, Fee
  also fits, not proposed    Delivered = Revenue * 1.08    prefers a rule over materialised columns    Delivered = Materials * 13.5  prefers a rule over materialised columns
docs/example-quote-plain.md  table at line 24 — 3 rows, 4 columns
  column rules    Amount  = Share * unnamed1.delivered_total  3/3 rows
  scalars matching figures in prose    29808.00  line 30  = SUM(Amount)               amount_total
  no rule found — treating as inputs    Stage, Share, Due
  also fits, not proposed    Amount = Share * 29808    prefers a rule over materialised columns
4 rules, 0 aliases, 6 scalars, 6 anchors.

A rule is proposed only if it reproduces every row exactly, at that column's own precision — never a best fit, never a threshold, and also fits, not proposed is listed rather than silently dropped, because a rule over materialised columns beating one with a bare constant is a judgment call worth seeing. --write inserts exactly the blocks and anchors above and rewrites nothing else. A rule that fits every row but one is never written; it is reported as a near-miss instead — the tool telling you the document already has a wrong number in it, before anyone runs check on it. The document's own appendix walks through every mechanism, including that near-miss case.

infer is the way in for the document check would otherwise have nothing to say about: one with no formulas at all. Pairing the two closes the loop — infer gets a plain table wired up, and check keeps it that way.

How it works

VisiMark parses the document, builds a dependency graph across every sheet, sorts it topologically, and evaluates in decimal arithmetic. Circular dependencies are reported with the full path through the cycle.

mermaid
flowchart LR  parse[Parse Markdown] --> deps[Build dependency graph]  deps --> sort[Topological sort]  sort --> evaluate[Evaluate in decimal]  evaluate --> cycle{"Cycle?"}  cycle -->|yes| report[Report the CYCLE path]  cycle -->|no| values[Computed values]

The CLI is the product. An agent must be able to verify a document without an editor; a VS Code extension is a later, thin wrapper.

The commands

Seven of them. check is the one that matters; the rest exist to get a document into a state check can be strict about, to explain what it did, or to ask it what-if questions.

mermaid
flowchart LR  plain[Plain table] -->|"infer --write"| wired[Rules and anchors in the file]  wired -->|edit an input or a rule| stale[Stored values disagree]  stale -->|fmt| wired  stale -->|check| fail[Exit 1]  wired -->|check| pass[Exit 0]  wired -.-> evalCmd["eval / explain"]
CommandWhat it doesOptionsWhat it writesExit codes
visimark check FILE...Recomputes every formula and reports the numbers that no longer agree with it—nothing, ever0 clean · 1 findings · 2 bad usage or unreadable file
visimark fmt FILE...Repairs stale values in place, by splicing the bytes of each number it owns--fix-dates rewrites unambiguous non-ISO datescomputed cells and anchored values only — never inputs, prose or headings0 clean · 1 problems it cannot fix remain · 2 bad usage or unreadable file
visimark infer FILE...Works out which rules reproduce the numbers a document already has, and proposes them--write inserts what it proposednothing, unless --write — and then it only ever inserts0 whatever it finds, because it is advisory · 2 bad usage or unreadable file
visimark eval FILEPrints the computed values — all of them, or one by name--get NAME, --jsonnothing0 · 2 bad usage, unreadable file, or no such name
visimark explain FILEPrints each sheet's inputs, rules and evaluation order#sheet limits it to one sheetnothing0 · 2 bad usage, unreadable file, or no such sheet
visimark ref [NAME]Prints what a builtin function does — signature, parameters, errors, worked examples — or lists all sixteen--jsonnothing0 · 2 no such function
visimark simulate FILE...Asks every question a document's lattice params declare, plus the defaults, and prints the readings its report statements ask for--fail-on-fault fails the run when a report sheet cannot start · --progress reports progress on stderrnothing0 readings printed, even where assertions fail · 1 no report anywhere, or a sheet cannot start under --fail-on-fault · 2 bad usage or unreadable file

Every option, every exit code and every finding check can report is tabulated in docs/cli-reference.md.

check is read-only, so it is safe to point at anything. fmt repairs stale values and only stale values: every other kind of problem is a question a person has to answer, so it reports those and leaves them alone.

A green check has to mean something

A document with no formulas in it has nothing to disagree with, so a checker that only compares numbers to rules would call it clean — the most misleading answer it could give. check reports a table with no rules attached to it as a problem in its own right:

console
$ visimark check quote.mdquote.md
  COVERAGE a table with no `vmark` rules — nothing in this document is checked           run `visimark infer` to derive them, or mark it `<!--vmark:no-formulas-->`
  1 problem (0 stale, 1 error)

Two things keep that from being annoying. It needs a table to be present, so prose — a README, a changelog — is never asked for arithmetic it does not have. And it is counted across the whole document, so a reference table that really is all input passes as long as some other table carries a rule.

When a document genuinely has nothing to derive, say so in the document:

markdown
<!--vmark:no-formulas-->

That marker is the only way out, and deliberately so. It lives in the file rather than in a workflow flag, so it travels with the content, shows up in review, and turns up in a grep. visimark infer --write writes it for you when it finds nothing whatsoever to derive — and refuses to when it found a near-miss or two rules it cannot choose between, because those mean the document does have arithmetic and wants a person to look. The marker is checked like anything else: add rules to a marked document later and check tells you the marker is now wrong.

In CI

The whole point of check is that it runs somewhere other than a human's judgment, so the CI story is one line:

bash
npx visimark check **/*.md

That exits non-zero on the first disagreement, which is all most CI systems need. A GitHub Actions workflow can do the same with the composite action this repo ships (action.yml) instead of hand-rolling the npx line:

yaml
- uses: michal-niedzwiedzki/[email protected]  with:    files: "docs/**/*.md"

There is nothing to configure and no strictness dial to find: pointing it at a glob is the whole setup. Any document under that glob with a table and no rules is a failure, which is why the <!--vmark:no-formulas--> marker above belongs in the file rather than in this workflow — the decision is about a document, not about a CI run.

A project already on remark/remark-lint adds the same checks with remark-lint-visimark instead — see docs/ci.md chapter 25.

A project on markdownlint adds them with markdownlint-rule-visimark — see docs/ci.md chapter 26.

An agent reaches the same engine over MCP with visimark-mcp, which serves every command except simulate as a tool, the authoring discipline as resources, and writes nothing unless an operator opens the write gate:

bash
npm i -g visimark-mcp   # or: bun add -g visimark-mcpnpx visimark-mcp        # or: bunx visimark-mcpclaude mcp add visimark -- npx -y visimark-mcp

The full surface is docs/mcp.md, and chapter 27 of docs/ci.md covers running it beside a CI check.

Diffable by construction

An .xlsx is a zip of XML: change one cell and code review can tell you the file changed, and essentially nothing more. VisiMark documents review like source, and that is a design constraint rather than a side effect of being text.

fmt never re-renders the Markdown. It locates each value it owns by position and splices the original byte buffer, so a rewrite touches the characters of that number and nothing else — no reflowed paragraphs, no renormalised emphasis markers, no realigned table columns, none of the four-hundred-line diff a round-trip through a Markdown printer would produce for a one-cell change. It also writes only what it owns: computed cells and anchored values. Input columns, prose and headings are human territory and are never touched.

Raising one input in the worked invoice — on-call hours from 12 to 20, the very edit the drift example above leaves unpropagated — makes fmt update 6 cells and 9 anchors, and the result is a 13-line diff in a 127-line document. Every changed line is a figure that genuinely depends on that input, so the diff is the propagation: a reviewer sees the VAT, the three milestone instalments, the early-payment terms and the EUR conversion all move together, and can check that they moved for the right reason.

The other half is that the diff contains everything. The formula lives in the document, so a changed rule shows up as a changed rule. Nothing outside the file can alter a number — no plugins, no config, no clock. And because an aggregate takes a column rather than an expression, every intermediate is materialised on the page: a total is always the sum of numbers the reviewer can see.

What it refuses to do

Where a value could mean two things, VisiMark errors rather than guesses.

Dates are ISO 8601 only — YYYY-MM-DD, ten characters. 15.10.2026 is rejected with an offered fix, because 15 cannot be a month. 11/12/2026 is rejected outright, because it is 11 December or 12 November depending on where its author lives, and no amount of care catches that by reading. Thousands separators are rejected for the same reason.

A column may carry a currency symbol or a physical unit — $5.50, 12 N — and VisiMark strips it to compute and puts it back when it writes. What it will not do is let one column mean two things: a column holding both $5.00 and €5.00 is an error, not a sum. A unit declared in a bracket — Weight [kg] on a header, fx_eur [PLN/EUR] on a binding — goes further: it propagates through every formula, so adding kilograms to metres, or multiplying by an exchange rate instead of dividing, fails check. Nothing is ever converted by a factor, and $ is never read as USD.

A name bound twice in one scope is an error rather than a silent overwrite.

There are no boolean literals. Comparisons produce booleans and IF() consumes them, but a boolean is never written into a cell — a materialised value is a number, a date, or a string, so the word true in a column stays the string it looks like.

There is no plugin architecture, and there will not be one. A document's numbers depend on its own text and the version of VisiMark reading it, and on nothing else — no extension modules, no config file, no environment, no network, no clock. A registry of host-supplied functions would produce documents whose arithmetic cannot be checked from the document, which is the one thing the format exists to prevent. When the built-in vocabulary is too small the answer is a new primitive in the engine, readable by everyone and runnable by everyone; when a value genuinely comes from outside, it belongs in an input column where a human wrote it down. Requests to grow that vocabulary — and proposals for any other language or tooling change — go through docs/vocabulary-catalogue.md, which records every one and the decision on it; the review process is docs/issue-runbook.md.

This makes the format smaller, not merely stricter: there is no locale, no configuration, and no rule for what a bare / means.

Status

All seven commands are implemented, in TypeScript. Install the visimark command with npm i -g visimark or bun add -g visimark; it runs under whichever of Node or Bun is on your PATH. To run it without installing, use npx visimark or bunx visimark. (On Windows the npx / npm i -g shims need sh on PATH, which Git Bash or WSL provide.) The three worked examples are the acceptance suite: check on the drift invoice reproduces the transcript above byte-for-byte, fmt leaves the clean invoice untouched, and infer on docs/example-quote-plain.md, a quote with no formulas in it at all, reproduces the transcript in that document's own appendix. The design, with the deferred work and the known tensions, is docs/visimark-design.md.

ArtifactWhere it is publishedRead more
visimark, the CLInpmdocs/cli-reference.md
visimark, the librarynpm, the same packagedocs/library.md
visimark-mcp, the MCP servernpm and the official MCP registrydocs/mcp.md
Language server and VS Code extensionVS Code Marketplace and Open VSXeditors/vscode/README.md
Obsidian pluginObsidian community plugins, and its own GitHub releases (tags without a v)editors/obsidian/README.md
Joplin pluginNot published; a .jpl from a CI buildeditors/joplin/README.md
codemirror-visimarknpm, from the first tag that contains itpackages/codemirror-visimark/README.md
markdown-it-visimarknpm, from the first tag that contains itpackages/markdown-it-visimark/README.md
remark-lint-visimarknpmpackages/remark-visimark/README.md
markdownlint-rule-visimarknpmpackages/markdownlint-visimark/README.md

docs/distribution.md is the record of every registry and directory each artifact is listed in. A vX.Y.Z tag publishes the npm packages, the MCP registry entry and the VS Code extension; docs/releasing.md is the checklist, and CONTRIBUTING.md says how to build and install the editor plugins from a clone.

For agents

skills/visimark/SKILL.md is an agent skill for authoring and verifying these documents. Copy it to ~/.claude/skills/visimark/ to install it. Its central warning is one worth stating here too: a green check is evidence of agreement, not of derivation. Change an input and confirm the checker starts complaining before believing a document is wired up. The COVERAGE finding described above exists so that an agent cannot report a green build on a document with no build in it, but the habit is still the better safeguard.

There is also an MCP server, visimark-mcp, for an agent working in a repository it has never seen: npx visimark-mcp or bunx visimark-mcp, or claude mcp add visimark -- npx -y visimark-mcp. It serves the skill above as a resource, so the discipline arrives with the verifier rather than separately. It is read-only unless started with --allow-write and given a host-declared root.

Editor support is specified in docs/visimark-editor-plugins-design.md: one language server — continuous check as diagnostics, fmt behind the editor's own format-on-save, quick fixes, and inlay hints that show the computed value without touching the bytes — with VS Code as the first client.

git clone … && cd visimark && bun installbun run test                # the full suite, the three examples includedbunx visimark check docs/example-invoice-drift.md

bun install builds the engine and links the visimark command into node_modules/.bin, so bunx visimark works in a fresh clone. To run the CLI straight from source without a build, use bun packages/visimark/src/cli/main.ts check FILE.

The project began as a CSV-based idea and moved to Markdown so that several small sheets can live inside one master document, and so that the file renders as a document rather than as data. The name is a nod to VisiCalc — the first spreadsheet software, originally developed for the Apple II by VisiCorp and later ported to the IBM PC.

Out of scope

VisiMark is deliberately not a spreadsheet replacement. No grid, no presentation layer, no cell styling, no locale, no Excel file compatibility, and no attempt at Excel's function library. Use other tools for neat presentation — and a spreadsheet when what you want is a spreadsheet.

Use VisiMark when what you want is a document: plain text, readable without the tool, reviewable in an ordinary pull request, writable by a human or an agent — with numbers that can be checked on every commit.

来源:README.md,提交 0283d16

工具

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

版本历史

5
  1. v0.4.1最新Oct 17, 2026
  2. v0.4.0Oct 15, 2026
  3. v0.2.0Oct 4, 2026
  4. v0.1.11Oct 1, 2026
  5. v0.1.10Sep 28, 2026

评分

已满足适用检查项的 50%

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

  • 近 30 天内可连通25 分

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

  • 已声明认证方式10 分

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

  • 已列出工具15 分

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

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