Safety Herbarium MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Safety Herbarium MCP ServerCreate an AI-safety reading volume from arXiv and show the coverage gaps."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Safety Herbarium
Mount the AI-safety literature. See what you are actually missing.
Live app · GitHub · API health · Agent console · Issues
AI-safety literature arrives faster than anyone can read it. The result is a shelf that looks impressive and covers almost nothing. This app takes a different view: it measures what a reading volume actually covers against ten AI-safety risk classes, and tells you which ones are still open.
Pull live arXiv papers and open-access textbooks off the discovery rail, mount them into a reading volume, and a deterministic engine grades the result. Every factor is itemised. Every decision is sealed into a SHA-384 chain you can replay later.
All screenshots are captured from the running deployment with real seeded rows, a real
chain seal and a real engine score — node scripts/capture-screenshots.mjs.
No account. No API keys. No Map pretending to be a database.
✨ Features
Live discovery, honestly labelled. The arXiv Atom API across the field's load-bearing phrases, with real citation counts joined from OpenAlex. If arXiv is unreachable, a dated sealed snapshot answers and every record says
fallback— never dressed up as current.Twelve open-access books, verified. Full-text AI-safety textbooks, standards and primers that publishers placed in the public, each with its licence recorded and each PDF checkable live. Books with no free edition are deliberately absent rather than mirrored.
A coverage engine with its arithmetic exposed.
herbarium-grade/1.0.0measures ten risk classes from lexical affinity, evidence tier, reading status and citations. Itemised factors, a documented formula, and a pinned input digest. No clock, no randomness, no network.A full reading loop. Mount → inspect → annotate in the margin → decide → export → retire. Every control calls a real route and reports what came back.
Take it with you. A Markdown syllabus with the coverage plate, BibTeX with unique keys, CSV, and sealed JSON with a digest. Attribution and timestamps on all four.
A replayable audit chain. Every mutation appends a sealed event. Replay recomputes every seal and names the first broken link, not just that one exists.
An agent interface that cannot drift. Eight MCP tools over JSON-RPC 2.0. Mutating tools call the same service functions the interface uses, so an agent and a reader see the same state.
Delete that keeps the evidence. Removal writes a tombstone, so a seal you handed someone stays verifiable.
Related MCP server: Clearon Content Archive MCP
🚀 Quickstart
git clone https://github.com/aniruddhaadak80/safety-herbarium
cd safety-herbarium
npm install
npm run devThat is the whole setup. Zero required environment variables and zero API keys. Local development and the test suite run on an embedded Postgres (PGlite), so the schema, indexes, constraints and every SQL statement are the ones production runs.
npm run dev # http://localhost:3000
npm run typecheck # tsc --noEmit
npm run lint # eslint
npm run test # 89 deterministic unit and integration tests
npm run build # production build
npm run check # all four, in order
npm run test:e2e # the primary journey in a real browser
npm run verify:live # 81 live assertions against a running deploymentProduction environment
One variable, documented in .env.example:
Variable | Required | Purpose |
| in production | Pooled Postgres connection string. On Vercel, install the Neon integration. Without it a production build refuses to start rather than using a store that vanishes on the next cold start. |
| no | Public production alias. Used for canonical URLs, OpenGraph metadata, the sitemap and |
| no | Embedded store location for local development. |
| no | Local verification only. Never set on a deployment. |
Deploying to Vercel.
DATABASE_URLhas to exist on the project, not on one deployment, or the next build — including the automatic one agit pushtriggers — boots without a store and refuses to serve. Two ways to do it: install the Neon marketplace integration, or set the variable in Project → Settings → Environment Variables for Production. If a deployment 404s on writes or/api/healthreports the embedded store,/api/healthalso lists the keys that actually arrived understore.configKeys, which is the fastest way to tell a missing variable from a malformed one. Pass--envon the CLI only as a one-off for a single deploy; it leaves the project itself unconfigured, and the next push will replace your working deployment.
📁 Project map
User routes
Route | What it is for |
| Entry sheet: live feed, engine readout, and a form that creates a volume. |
| Your reading volumes, filterable by active or retired. |
| Dynamic. The volume: determination plate, sheet list with decisions, next-read recommendation, export, share, verify, retire. Filters live in the URL. |
| Dynamic. One mounted sheet: abstract, the full PDF embedded from the publisher's host, the determination with the lexicon terms that fired, and the marginalia rail. |
| Live arXiv index, search, and the open-access bookshelf with live link verification. |
| Analysis. The coverage lab: run the engine over any volume, read itemised factors, tilt the class priors. |
| Agent console. A live MCP client with one-click calls and full request/response transcripts. |
| The four export formats, described per volume. |
| Every volume's chain, replayed. |
| Dynamic. Event-by-event replay, naming the first broken link. |
| Engine constants, class priors, live store health, session fingerprint. |
| Read-only public view of a shared volume. |
API routes
Endpoint | Methods |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Domain
File | Responsibility |
| The ten risk classes and their weighted lexicons. |
|
|
| Canonical JSON and the SHA-384 seal chain. |
| Every read and mutation, with the audit event inside the same transaction. |
| The adapter boundary, schema and row mapping. |
| arXiv, OpenAlex, the bookshelf, the sealed snapshot. |
🔌 API
Create a volume, mount a real record, read it back:
BASE=https://safety-herbarium.vercel.app
# 1. Open a volume
VOLUME=$(curl -s -X POST $BASE/api/volumes \
-H 'content-type: application/json' \
-d '{"name":"Frontier safety primer","intent":"Six weeks.","role":"student"}' \
| jq -r .volume.id)
# 2. Mount a paper. The server resolves the record from arXiv itself.
curl -s -X POST $BASE/api/volumes/$VOLUME/sheets \
-H 'content-type: application/json' \
-d '{"sourceKind":"arxiv","sourceId":"2412.14093","mountedClass":"deceptive_alignment"}' \
| jq '{accession: .sheet.accession, provenance: .sheet.provenance, created}'
# 3. Read it back
curl -s $BASE/api/volumes/$VOLUME/sheets | jq '.sheets[].title'
# 4. Decide, and seal it
SHEET=$(curl -s $BASE/api/volumes/$VOLUME/sheets | jq -r '.sheets[0].id')
curl -s -X PATCH $BASE/api/sheets/$SHEET \
-H 'content-type: application/json' \
-d '{"status":"read","decision":"admitted","decisionNote":"Core evidence."}' \
| jq '.sheet | {status, decision, version}'
# 5. Run the engine
curl -s -X POST $BASE/api/volumes/$VOLUME/coverage \
-H 'content-type: application/json' -d '{}' \
| jq '{engine: .report.engine, score: .report.score,
gap: .report.recommendation.primaryGap,
seal: .report.referenceSeal,
factor: (.report.factors[] | select(.sheets|length>0)
| {classId, coverage, load})}'
# 6. Replay the chain
curl -s $BASE/api/volumes/$VOLUME/verify | jq '{ok, events, brokenAtSeq, headMatches}'Error responses share one envelope and honest status codes:
{ "error": { "code": "conflict", "message": "This sheet changed since you loaded it.",
"details": { "expectedVersion": 1, "currentVersion": 4 } } }Agent configuration
/mcp.json is served from the deployment itself, so the endpoint is always the real alias.
{
"mcpServers": {
"safetyHerbarium": {
"type": "http",
"url": "https://safety-herbarium.vercel.app/api/mcp",
"headers": { "x-herbarium-scope": "replace-with-your-own-random-token" }
}
}
}There are no accounts. Generate any random token and send it in x-herbarium-scope; it sees only
what it creates. A browser uses the hb_scope HTTP-only cookie instead.
curl -s -X POST $BASE/api/mcp -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'
curl -s -X POST $BASE/api/mcp -H 'content-type: application/json' \
-d "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",
\"params\":{\"name\":\"coverage_report\",
\"arguments\":{\"volumeId\":\"$VOLUME\"}}}" \
| jq '.result.structuredContent.report.score'Architecture
flowchart TB
classDef live fill:#cffafe,stroke:#22d3ee,color:#0b3d47
classDef ai fill:#ede9fe,stroke:#a78bfa,color:#3b2f6b
classDef agent fill:#d1fae5,stroke:#34d399,color:#0d4b32
classDef risk fill:#ffe4e6,stroke:#fb7185,color:#6b1220
classDef infra fill:#e2e8f0,stroke:#94a3b8,color:#1e293b
classDef ext fill:#fef3c7,stroke:#fbbf24,color:#5b3d05
Reader["Visitor · anonymous session"]:::infra
Reader --> UI["Next.js App Router"]:::infra
Agent["MCP client · JSON-RPC"]:::agent
Agent --> MCP["/api/mcp"]:::agent
UI --> REST["/api/* routes"]
MCP --> SVC["Service layer"]:::ai
REST --> SVC
SVC --> PG[("Hosted Postgres")]:::infra
SVC --> ENGINE["herbarium-grade engine"]:::ai
SVC --> SEAL["SHA-384 seal chain"]:::risk
ENGINE --> SEAL
SVC --> ARXIV["arXiv Atom API"]:::live
SVC --> OA["OpenAlex API"]:::live
SVC --> SHELF["Open-access catalogue"]:::ext
SVC -.-> SNAP["Sealed snapshot"]:::ext
MCP --> MCPData pipeline and honest fallback
Every external payload is normalised into src/lib/types.ts and keeps its provenance. There are
three, and they are never blurred:
flowchart LR
classDef live fill:#cffafe,stroke:#22d3ee,color:#0b3d47
classDef snap fill:#ede9fe,stroke:#a78bfa,color:#3b2f6b
classDef ok fill:#d1fae5,stroke:#34d399,color:#0d4b32
A["arXiv + OpenAlex"]:::live -->|"reachable"| B["provenance: live"]:::live
A -->|"timeout / error"| C["Sealed snapshot dated at build"]:::snap
C --> D["provenance: fallback · not current"]:::snap
E["Checked-in bookshelf"]:::ok --> F["provenance: curated · licences recorded"]:::ok
B --> G["Normalised SourceRecord"]:::ok
D --> G
F --> G
G --> H["UI, engine, export"]:::oknpm run snapshot:build regenerates the snapshot and its seal. The response envelope always states
live or fallback, and user-created rows are never produced by a source.
The engine
flowchart TB
classDef ai fill:#ede9fe,stroke:#a78bfa,color:#3b2f6b
classDef ok fill:#d1fae5,stroke:#34d399,color:#0d4b32
classDef risk fill:#ffe4e6,stroke:#fb7185,color:#6b1220
S["Sheet: title + abstract"]:::ai --> AFF["affinity = 1 − e^−(lexicon weight matched)"]:::ai
S --> EV["evidence = tier × status × citationFactor"]:::ai
AFF --> LOAD["load(class) = Σ affinity × evidence"]:::ai
EV --> LOAD
LOAD --> COV["coverage = 1 − e^−(load / 1.2)"]:::ai
COV --> GAP{"coverage < 35%?"}:::ok
GAP -->|yes| OPEN["open gap → recommend next read"]:::risk
GAP -->|no| DONE["covered"]:::ok
COV --> SCORE["score = Σ weight × coverage"]:::ai
SCORE --> SEAL["report carries the volume's chain head"]:::riskSums saturate on purpose: a tenth survey on one topic cannot dominate a volume, and unread material
opens a gap without closing one (queued counts 0.2 of a read sheet; rejected counts zero).
Ties break on declared taxonomy order, then on ids, so output is stable.
Integrity and replay
flowchart LR
classDef risk fill:#ffe4e6,stroke:#fb7185,color:#6b1220
classDef ok fill:#d1fae5,stroke:#34d399,color:#0d4b32
classDef infra fill:#e2e8f0,stroke:#94a3b8,color:#1e293b
W["Write inside a transaction"]:::infra --> EV["Append sealed event"]:::risk
EV --> CAN["canonicalJson · keys sorted at every depth"]:::infra
CAN --> SEAL["seal = SHA-384(prevSeal ‖ canonicalJson)"]:::risk
SEAL --> STORE[("hb_audit")]
STORE --> REPLAY["Replay recomputes every seal"]:::ok
REPLAY --> CMP{"matches stored?"}:::ok
CMP -->|all| GOOD["chain intact"]:::ok
CMP -->|first mismatch| BAD["report that sequence number"]:::risk
STORE --> TOMB["Deletion writes a tombstone"]:::risk
TOMB --> REPLAYCanonical JSON sorts object keys recursively, preserves array order, normalises -0 and refuses
non-finite numbers rather than dropping them silently. A deleted event surfaces as a sequence gap, not
a pass.
Agent sequence
sequenceDiagram
participant C as MCP client
participant M as /api/mcp
participant S as Service layer
participant D as Postgres
participant U as Interface
C->>M: initialize
M-->>C: protocolVersion, serverInfo
C->>M: tools/list
M-->>C: 8 tools with JSON schemas
C->>M: tools/call mount_sheet
M->>S: mountSheet(scope, …)
S->>D: INSERT sheet + audit event, one transaction
S-->>M: sheet, accession, volumeSeal
M-->>C: content blocks + structuredContent
Note over U,D: the same call from the UI writes the same rows
U->>D: read volume
U-->>U: sees the sheet the agent mountedScope comes from the transport, never from tool arguments, so a tool call cannot address another session's records.
User journey
flowchart LR
classDef ok fill:#d1fae5,stroke:#34d399,color:#0d4b32
classDef risk fill:#ffe4e6,stroke:#fb7185,color:#6b1220
classDef infra fill:#e2e8f0,stroke:#94a3b8,color:#1e293b
A["Open a volume"]:::infra --> B["Mount from live index"]:::ok
B --> C["Read the sheet, PDF inline"]:::ok
C --> D["Annotate the margin"]:::ok
D --> E["Decide: admit / defer / reject"]:::ok
E --> F["Run the coverage engine"]:::risk
F --> G{"gap found?"}
G -->|yes| H["recommend a next read"]:::risk
G -->|no| I["export syllabus, BibTeX, CSV, JSON"]:::ok
H --> I
I --> J["Retire, keeping the chain replayable"]:::infraDeployment
flowchart LR
classDef infra fill:#e2e8f0,stroke:#94a3b8,color:#1e293b
classDef ok fill:#d1fae5,stroke:#34d399,color:#0d4b32
classDef risk fill:#ffe4e6,stroke:#fb7185,color:#6b1220
A["git push main"]:::infra --> B["GitHub Actions"]:::infra
B --> C["npm ci"]:::infra
C --> D["typecheck · lint · test"]:::infra
D --> E["build"]:::infra
E --> F["browser smoke"]:::infra
F --> G["Vercel production"]:::ok
G --> H[("Neon Postgres")]:::infra
G --> I["Live alias"]:::ok
I --> J["verify-live.mjs · 81 assertions"]:::risk
J -->|failure| K["fix · redeploy · rerun"]:::riskA production runtime without DATABASE_URL refuses to start rather than selecting the embedded
store. /api/health reports which adapter is actually in use, and the health panel on /settings
shows it to a reader.
Security model
Ownership. An unguessable 128-bit token in an HTTP-only cookie, or an
x-herbarium-scopeheader for agents. Every read and write is filtered by it.Input. All external input is length-bounded and shape-checked before it reaches SQL, a URL or a page. Only
httpsURLs on a fixed host allowlist are ingested or embedded.Upstream. Time-bounded fetches with one retry. A visitor's search string is stripped to letters, digits and spaces, so the discovery route cannot become an open proxy.
SQL. Parameterised throughout. No user input is interpolated into a statement.
Errors. A single envelope, correct status codes, and never a stack trace, SQL string or environment value.
Rate limits. 40 writes, 20 mounts and 12 volume changes per window, per session, counted in the database so a cold start cannot reset them. Best-effort by design; see SECURITY.md.
Third-party links carry
target="_blank" rel="noopener noreferrer".
A note on the licence
The application is MIT. The literature is not, and nothing here is mirrored:
arXiv preprints belong to their authors and are licensed by them; PDFs stream from
arxiv.org.NIST publications are works of the U.S. Government and in the public domain.
Each bookshelf entry carries its own licence. A book with no free edition is deliberately absent.
This is a reading aid, not a safety assessment. Coverage describes what a volume has read, not what is true about AI systems. Nothing here is advice about building or deploying them.
🗺️ Roadmap
Now
✅ Mount live arXiv papers and twelve open-access books, with licence and reachability recorded.
✅
herbarium-grade/1.0.0: ten risk classes, itemised factors, sealed reports.✅ MCP JSON-RPC 2.0 with eight tools, idempotent mounts and session scoping.
✅ SHA-384 audit chain with replay and known-answer tests.
✅ Four export formats, a read-only share route, and tombstones on deletion.
flowchart LR
classDef ok fill:#d1fae5,stroke:#34d399,color:#0d4b32
A["Live sources"]:::ok --> B["Coverage engine"]:::ok --> C["Sealed state"]:::ok
C --> D["MCP agent"]:::okNext
Per-class reading plans. The engine names an open class; the next step is generating an ordered plan across the live index to close it, with the projected coverage change.
Volume diffing. Compare two volumes side by side and show exactly which classes a change moved, with the factor arithmetic for each.
Watchlists. A reader names a risk class and gets a live feed of new work matching its lexicon, so the gap stays closed rather than being re-opened by the literature.
Citation-graph coverage. Weight a sheet by how its citations cluster, so mounting a landmark survey moves several classes at once.
flowchart LR
classDef ok fill:#d1fae5,stroke:#34d399,color:#0d4b32
A["Open class"]:::ok --> B["Ordered plan"]:::ok --> C["Projected coverage"]:::ok --> D["Watchlist"]:::okLater
Coverage for a whole field. Grade an organisation's public safety research against the same ten classes, published as a signed report anyone can replay.
Evaluation notebooks. Export a coverage report plus its reading programme as a reproducible notebook, so a review is auditable end to end.
Local-first mode. A sync protocol so a reader keeps a full offline copy and can replay chains without the deployment.
flowchart LR
classDef ok fill:#d1fae5,stroke:#34d399,color:#0d4b32
A["Field report"]:::ok --> B["Notebooks"]:::ok --> C["Offline replay"]:::ok📄 Attribution
arXiv, arxiv.org — a Cornell University-operated preprint server. Content is licensed by its authors.
OpenAlex, openalex.org — an open catalogue of scholarly works, CC0.
Dan Hendrycks, Introduction to AI Safety, Ethics, and Society — CC BY-NC-ND.
NIST AI 100-1 and AI 600-1 — U.S. Government works, public domain.
🤝 Contributing
Read CONTRIBUTING.md. Security issues: SECURITY.md.
📄 License
MIT © aniruddhaadak80
This server cannot be deployed
Maintenance
Related MCP Connectors
Signed Buildability Oracle for AI-for-science papers. ed25519 receipts, Wave proofs, divergence.
Machine-native research commons for agent evidence, discovery, rooms, and bounded research quests.
Shared, versioned context that humans and AI agents can publish, review, annotate, and continue.
Issue & verify signed (ed25519), hash-chained, timestamped provenance receipts for agent actions.
Related MCP Servers
- AlicenseAqualityBmaintenanceProvides cryptographic truth infrastructure for AI agents, enabling them to seal content with SHA-256 and Ed25519, verify receipts, anchor them to Bitcoin via OpenTimestamps, generate citations, and audit chains of receipts.537 npm1MIT
- AlicenseBqualityCmaintenanceEnables users to maintain append-only content history with verified publication and social follow-through outcomes, search for overlaps, and run read-only integrity verification of stored snapshots.16MIT
- AlicenseAqualityBmaintenanceEnables local-first document versioning with content-addressable storage, word-level AST diffs, and AI-compliance auditing for LLM agents.6MIT
- FlicenseNot gradedqualityAmaintenanceEnables deterministic, harness-neutral research workflows by unifying research object identity, evidence receipts, claim-evidence graphs, and append-only decision logs, along with specialized scholarly skills for literature analysis, reproducibility audits, and multi-agent review.29 PyPI1-