npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

pepiros-mcp

v0.1.1

Published

MCP server for Pepiros: search, verify, and cite claims against a paper's actual text over stdio -- quote-located grounding, not vector similarity.

Downloads

378

Readme

Turn a research PDF into a knowledge graph where every generated claim stays bound to a located quote, and hand that grounding to Claude as a callable MCP service.

pepiros.vercel.app

CI License: MIT Node Next.js 16 TypeScript

Quick start · What works today · Architecture · MCP · Design system · Configuration · Contributing


What it does

Upload a paper, or paste an arXiv / PMC / DOI link. Pepiros extracts its structure, generates a summary and thematic "pillars," and backs every generated claim with a quote it can point at in the source PDF: page, rect, and match score included.

The verification is deterministic. No LLM judges another LLM:

for each claimed {ref, quote}:
  chunk = resolve(ref)                                  # stable citation id, not a vector lookup
  if !chunk                        -> drop, log hallucinated_ref
  score = token_set_ratio(quote, chunk.text)
  if   score >= 0.92               -> quote located
  elif score >= 0.75               -> paraphrase   (badged, kept)
  else                             -> drop the anchor, strip [^eN] from the prose

On top of that sits an entailment overlap floor: every number, unit, and comparator a claim asserts must also appear in the anchored chunk's numerics. That is what catches the real failure mode, a genuine quote attached to a reversed or overstated conclusion, which fuzzy matching alone scores 1.0.

No claim is ever labelled "verified." The badge reads quote located, because a fuzzy-matched quote proves quotation provenance, not entailment. Claim and quote render side by side so the reader adjudicates. This is a deliberate, load-bearing constraint, not hedging.

docs/PLAN-V1.md is the canonical spec: architecture, data model, locked decisions, and the list of things deliberately not built. This README is the entry point.


What works today

Early build, moving fast. Honest status:

Working

| Area | State | | --- | --- | | Grounding spine (lib/grounding/*) | Quote verification, entailment floor, reverse audit, citation-ref resolution. Deterministic, no model calls. | | Data model (lib/db/schema.ts) | All 18 tables from docs/PLAN-V1.md §5, in Drizzle, with real migrations applied and CI running against a real ephemeral Postgres on every push. | | UI (components/*, app/(app)/*) | React Flow canvas (5 node types, ghost citation nodes, kind-coloured edges), doc-reader, outline, audit, learn, and share views -- all four reader subpages share one nav/spacing shell and a consistent guest banner. Renders the bundled fixture or any real ingested workspace alike. | | Citation APIs (lib/services/related.ts, lib/services/citationExpand.ts) | Real Semantic Scholar + OpenAlex calls (free, no key) for the related-papers rail and canvas citation expansion. Typed ok/no_match/rate_limited/error status, never fabricated fallback data. | | LLM layer (lib/agents/*) | Archetype classifier, archetype-conditioned pillar planner, and 19 of the 22 node generators, fanned out via p-queue with per-node failure isolation. Runs on Groq (primary) + Featherless (fallback), not Anthropic: see Configuration. Every claimed quote is re-verified through the grounding spine before it becomes a real evidence row. Verified end to end against both live APIs, not just mocked. | | MCP server (mcp/*) | All 12 tools in lib/mcp/registry.ts are live over stdio (search_paper, verify_claim, get_outline, get_node, create_node, find_contradictions, paper_facts, list_papers, list_workspaces, create_workspace, add_paper, get_job), plus 3 resource templates for @-mentioning a workspace/paper/node and the 4 docs/PLAN-V1.md §13.3 prompts. create_node re-verifies submitted evidence server-side, so a client cannot assert quote_located. A token requires a live session (or is refused outright in a production-configured process with none) rather than defaulting to unrestricted access. Verified over the real protocol with the SDK's own client. The remote streamable-HTTP transport (app/api/mcp/route.ts) is real too, with a full RFC 7591/6749 OAuth chain (dynamic client registration, PKCE, single-use authorization codes) for a hosted connector rather than a locally-run stdio process -- both the stdio and remote surfaces share the same token-pin/rate-limit checks, verified live (a workspace-pinned token can't reach another workspace, and hammering a tool or the OAuth endpoints past their limit gets a real 429). | | Grounded chat (lib/services/chat.ts, POST /api/chat) | Query rewrite → route classifier → stable-id context block → answer → citation re-verification. Refusal path with an explicit "answer without sources" opt-in; ungrounded answers render visually distinct. Verified against the live Groq API. | | Canvas layout (lib/layout/*) | Deterministic server-computed positions: radial for one paper, layered columns for several. Verified to produce no overlapping cards at 1, 2, or 3 papers. Pillars open collapsed, edges render only when both endpoints do, cards shed detail as you zoom out (lib/graph/lod.ts), and a closed-by-default legend explains every colour and line style the graph actually uses. | | Auth (lib/auth/*, app/auth/callback) | Google sign-in via Supabase as the OAuth broker, plus real username/password accounts (a required real email, so password recovery has somewhere to send to, and email confirmation is real -- Supabase sends it, sign-in gates on it). Password sessions are server-side revocable (logout kills that one session; a "sign out everywhere" action kills every session for the account), translated into the app's own signed cookie so there is one answer to "who is signed in". Sign-in buys persistence, not access: the reader and upload are open to guests, with a banner saying plainly that guest work is not kept. Every workspace-scoped mutation route requires a session for anything other than the public demo workspace. | | Upload validation + ingest (lib/services/upload.ts, lib/services/ingest.ts, POST /api/ingest) | Size (50MB), magic bytes, page cap (120), text-layer check, duplicate detection (DOI then fuzzy title), and arXiv/PMC/direct-PDF URL resolution (DOI resolution still needs an Unpaywall-style resolver). GET /api/jobs/[id] streams the 9-stage progress list over SSE. A 202 kicks off the real pipeline: scripts/parse.py (PyMuPDF) extracts sections/chunks/numerics/authors/publication year/archetype, then lib/agents/orchestrator.ts classifies, plans pillars, and fans out generators, all re-verified against the source before the graph updates. A scanned/image-only PDF fails loudly with a clear diagnosis instead of silently ingesting nothing. The parsed PDF itself is stored locally and served back for the reader's real page view (react-pdf), not a styled mock. | | Cross-paper synthesis (lib/services/synthesis.ts, POST /api/compare) | Pairwise comparison writes real agrees/contradicts/extends/shares_method/relates edges (two-sided evidence required) and 4 of the 6 docs/PLAN-V1.md §10 synthesis node types: Consensus, Contradictions (LLM-classified), Timeline of Findings, and Methodological Divergence (both deterministic, from real per-paper year/archetype). Dataset Overlap and Open Questions still need signals nothing in the pipeline extracts yet. | | Node mutation (lib/services/nodes.ts, PATCH/DELETE /api/nodes/[id]) | Editing a node's body re-verifies every citation against the edited text (downgrading/stripping one that no longer matches) and records a version history row; deleting cascades edges/evidence and marks referencing nodes stale. | | Export & promote (GET /api/export, POST /api/chat/promote) | Markdown and BibTeX export; promoting a chat answer that draws on more than one paper into a cross-paper thread node. | | API | POST /api/verify, POST /api/audit, POST /api/chat, POST /api/ingest, POST /api/compare, POST /api/share, GET /api/jobs/[id], GET /api/graph/[workspaceId], GET /api/related, GET /api/expand, GET /api/export, full nodes/nodes/[id] CRUD, GET /api/papers/[paperId]/pdf | | Tooling | Typecheck, lint, test, and build all gated in CI on every push and PR, against a real ephemeral Postgres service container. 318 Vitest cases across 36 files, including the LLM and chat layers tested against a hand-rolled mock model (no API key or network call needed). |

Not built yet

Each of these is a one-line TODO at the top of its file, describing what belongs there.

| Area | Missing | | --- | --- | | OCR fallback + seed script | scripts/ocr_fallback.py (PaddleOCR-VL, for scanned/table-heavy pages) and scripts/seed.ts (bulk-loading a corpus into Postgres once one exists) are still stubs -- a scanned/image-only PDF now fails the job with a clear message instead of silently ingesting nothing, but there's no OCR pass yet to actually recover text from one. scripts/parse.py (PyMuPDF) and lib/services/ingest.ts are otherwise real -- pip install pymupdf is the one setup step, since it's a local script per docs/PLAN-V1.md §2, never a deployed service. | | Remaining generators | 19 of the ~22 real types in docs/PLAN-V1.md §8 are implemented (lib/agents/generators/), including equations -- scripts/parse.py detects display-equation regions and converts them to LaTeX via Pix2Text (free, open-source, runs entirely locally), bbox-anchored like any other chunk -- and figures, which needed the same Pix2Text pass to also crop each detected figure region and pair it with its nearby caption (a real figure_caption-kind chunk), plus a third model tier: visionModel() in lib/ai/client.ts (OpenRouter, a free :free-suffixed model, since Groq has no vision model at all and Featherless's returned capacity_exhausted on every attempt checked live). Left: concept_links (needs cross-paper context a single-paper GeneratorContext doesn't carry, plus a way to write real relates edges from a generator's output -- already solved a different way by lib/services/synthesis.ts's relates classification, so this isn't a real gap so much as a pointer to where the equivalent already lives). quiz/flashcards are solved a different way -- lib/services/quiz.ts and components/learn/FlashcardDeck.tsx derive them from already-verified leaf nodes rather than being their own generator. | | Synthesis, remaining node types | Dataset Overlap and Open Questions (docs/PLAN-V1.md §10) need signals nothing in the pipeline extracts yet (dataset identifiers, a genuine gap-in-the-literature judgment) -- not a metadata gap like the two that shipped, new extraction work. | | Session refresh | A signed-in session simply expires at its 7-day lifetime with no silent renewal. Revocation exists (see Auth, above); refresh doesn't yet. | | Measurement | evals/, scripts/measure-drop-rate.ts |

Chat and the generators need at least one of GROQ_API_KEY or FEATHERLESS_API_KEY; everything else runs without a key. PDF ingest itself only runs locally (npm run dev) -- Vercel's Node runtime has no Python interpreter, so the hosted deployment serves the demo workspace's already-ingested content but can't parse a new upload.


Quick start

git clone https://github.com/StudentSuite/pepiros.git
cd pepiros
npm install
npm run dev

Open http://localhost:3000/w/ws-1. ws-1 is the only workspace id the fixture defines.

No environment file, database, or API key is needed to run it: the app is fully functional against fixtures/workspace.json, which ships 3 papers, a contradiction pair, a cross-paper cites link, and one planted misattribution that the verifier correctly demotes to unsupported.

Requires Node 20 or newer. Uploading a real PDF additionally needs Python 3 with PyMuPDF (pip install -r scripts/requirements.txt) and a GROQ_API_KEY (or FEATHERLESS_API_KEY) in .env -- neither is needed just to browse the fixture workspace.

Scripts

| Command | Does | | --- | --- | | npm run dev | Next.js dev server | | npm run build / npm start | Production build / serve | | npm run typecheck | tsc --noEmit across the repo | | npm run lint | ESLint | | npm test / npm run test:watch | Vitest over lib/**/*.test.ts | | npm run db:generate / db:migrate / db:studio | Drizzle Kit against DATABASE_URL | | npm run seed | scripts/seed.ts (still a stub) | | npm run mcp:stdio | MCP server over stdio, see Using the MCP server |


Architecture

Next.js 15 (App Router, TS, React 19)          -> Vercel
  app/api/*         HTTP surface for the UI
  mcp/server.ts     MCP surface for Claude (stdio today, remote HTTP later)
  lib/services/*    <- BOTH of the above call only this
  lib/grounding/*   deterministic verification, no model calls
  lib/agents/*      archetype classifier, pillar planner, node generators -> Groq, falling back to Featherless (lib/ai/client.ts)
  lib/layout/*      deterministic server-computed node positions (radial | layered)
  lib/chat/*        citation-marker parsing, shared by the chat server and client

Supabase          Postgres (no vector column), Storage, Auth, Realtime (job status)
scripts/parse.py  local PyMuPDF run: sections, chunks, figures, equations, refs, numerics
scripts/ocr_fallback.py   local PaddleOCR-VL, for scanned or table-heavy pages

Three things that look like omissions and are not. There are no embeddings and no vector column: a paper is 8-20k tokens, so the whole thing goes in context behind a prompt cache, addressed by stable citation ids (search_paper scores keyword coverage rather than cosine distance). There is no deployed Python service: PyMuPDF and PaddleOCR run as local scripts, so there is no second deploy target. There is no force-directed layout: positions come from lib/layout/* as a pure function of the graph's shape, so the same graph always lands identically. See docs/PLAN-V1.md §2 and §20 before proposing any of them.

Working in this repo with an AI coding agent? Read CLAUDE.md first.


Using the MCP server

Point Claude Code (or Desktop) at the stdio server to call the grounding layer from a conversation. Published on npm, no local clone needed:

{
  "mcpServers": {
    "pepiros": {
      "command": "npx",
      "args": ["-y", "pepiros-mcp"]
    }
  }
}

Working from a clone instead (e.g. to test a local change to mcp/* before it's published):

{
  "mcpServers": {
    "pepiros": {
      "command": "npx",
      "args": ["tsx", "mcp/stdio.ts"],
      "cwd": "/absolute/path/to/pepiros"
    }
  }
}

Then the docs/PLAN-V1.md §13.5 beat works for real: ask Claude to summarize the fixture's RCT, then "now verify every claim you just made." It calls verify_claim on its own sentences and reports back which ones the source actually supports.

Worth knowing what the tiers mean, because the distinction is the product: quote located means the quote was found in the source. It does not mean the claim follows from the quote. Nothing here judges entailment: that is deliberately left to the reader, and there is no tool that will do it for you.


Configuration

Nothing below is required to run against the fixture. Copy .env.example to .env once a real backend exists.

LLM provider: Groq primary, Featherless fallback, not Anthropic. Either works alone:

| Variable | For | | --- | --- | | GROQ_API_KEY | Primary. Defaults to openai/gpt-oss-20b/-120b, the only two Groq models confirmed to support the structured outputs every call here needs; see lib/ai/client.ts's comment before changing either id | | FEATHERLESS_API_KEY | Fallback, used only when Groq 401/402/403/429s or marks its own error retryable (lib/ai/fallbackModel.ts). Featherless is OpenAI-compatible with no first-party AI SDK provider: lib/ai/client.ts goes through @ai-sdk/openai-compatible. Its $25/mo flat-rate plan is contractually for human-driven use in Featherless's own UI, not the programmatic API traffic this is; the metered Developer plan is the one its terms describe as intended for that | | FEATHERLESS_MODEL_FAST, FEATHERLESS_MODEL_STRONG | Default to two Qwen2.5 models verified live against a real account (meta-llama's repos there are gated behind a HuggingFace org connection and 403 without it) |

Free account:

| Variable | For | | --- | --- | | NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY | Postgres, Storage, Auth, Realtime | | DATABASE_URL | Drizzle's direct Postgres connection | | SESSION_SECRET | Signs the app's own session cookie. Any long random string; falls back to a known constant in development and refuses to start without one in production |

Enabling Google sign-in

The app is wired for it, but Google itself is enabled per-Supabase-project, not per-env-var, so this is two dashboard steps, not a code change:

  1. Google Cloud Console → create an OAuth 2.0 client (Web application), and set the authorised redirect URI to https://<your-project>.supabase.co/auth/v1/callback.
  2. Supabase dashboard → Authentication → Providers → Google → paste the client ID and secret, and add your app's origin (http://localhost:3000 in development) to Authentication → URL Configuration → Redirect URLs.

Until that is done the button still renders and fails honestly: the callback bounces back to /login with a message rather than dead-ending on a provider error page. Email/password sign-in and guest browsing are unaffected either way.

Free, no key required:

| Variable | For | | --- | --- | | NEXT_PUBLIC_APP_URL | MCP results carry deep links back into the canvas, so the app needs to know its own origin | | OPENALEX_MAILTO, CROSSREF_MAILTO | Courtesy identifiers that move requests into the polite rate-limit pool | | SEMANTIC_SCHOLAR_API_KEY | Optional. Unauthenticated access is enough unless the related-papers rail starts returning 429 | | MCP_TOKEN_SECRET | Self-generated, any long random string |


Design system

Direction: Editorial Paper (Are.na × Instapaper/NYT Reader), on top of the "lab notebook at night" thesis: dark chrome, a warm paper-white reading surface, pillar colour as a structural system. Full brief and the canonical palette: design/DIRECTIONS.md. Live token reference: /dev/tokens.

| Layer | Where | | --- | --- | | Tokens (color, spacing, radius, elevation, motion, layout dims) | app/globals.css, tailwind.config.ts | | Fonts (Source Serif 4, Inter, JetBrains Mono via next/font) | app/layout.tsx | | Primitives (Button, Input, Dialog, Drawer, Tooltip, Popover, Tabs, Menu, Toast, Skeleton, ErrorBanner, Badge, Icon, Logo, ...) | components/ui/ | | Icons | Lucide, only through components/ui/Icon.tsx (never a raw lucide-react import in a feature component) | | Brand assets (favicon, app icon, OG image) | app/favicon.ico, app/icon.png, app/apple-icon.png, app/opengraph-image.png, app/twitter-image.png | | Image-gen prompts (brand kit + all 8 app surfaces) | design/prompts/, zipped at design/pepiros-editorial-paper-prompts.zip | | Platform-vision scope (accounts, publish, discovery, discussion) | docs/PLAN-V1.md §22 |

Two conventions worth knowing before adding a component: pillar hues have two accessors in components/ui/PillarChip.tsx, pillarColor() for borders/dots/edge strokes (canonical hex, 3:1 threshold) and pillarTextColor() for anywhere a pillar hue is literal text colour (lightened for WCAG AA's 4.5:1, three of the seven canonical hues fail it unmixed). And motion reaches for lib/motion.ts's named helpers or the keyframes in globals.css, never a hand-picked duration: Editorial Paper is ease-out only, never spring.


Project docs

| File | What | | --- | --- | | docs/PLAN-V1.md | Canonical spec: product, architecture, locked decisions, cut list, cited by the TODO comments throughout the codebase | | CLAUDE.md | Conventions and invariants for AI coding agents | | CONTRIBUTING.md | Setup, PR gates, code conventions | | CHANGELOG.md | Keep a Changelog format | | SECURITY.md | Private vulnerability reporting |


License

MIT © Anay Dhawan and Yash Kewlani