Invoice-MCP

Invoice-MCP

MCP server that gives AI agents read access to subscription/SaaS invoice history, ingested from Gmail and stored in Postgres and MongoDB, with tools for listing invoices, retrieving PDFs, and summarizing spend.

Category
Visit Server

README

Rayshio

Rayshio is an invoice aggregator software for businesses that gives your AI agents MCP access to your expenses.

How it Works

Rayshio aggregates invoices directly from a company's billing email address:

  1. User signs in with whatever provider their work email lives on
  2. Pulls a sample of invoices from the email to find the recipient email. Sometimes, billing emails are aliases that forward emails to the user's email address.
  3. Poll through all emails to the billed email address(es) to find all invoices
  4. Invoice PDF is stored in MongoDB
  5. Invoice data is stored in PostgreSQL

See SPEC.md for the full design.

Setup

  1. cp .env.example .env and fill in values (scripts/ has key generators noted inline)
  2. docker compose up -d
  3. pnpm install
  4. pnpm migrate up — applies migrations/ to Postgres
  5. pnpm cli seed-org --name "Your Org"
  6. pnpm cli auth — browser OAuth consent, stores encrypted refresh token
  7. pnpm cli check-connections — sanity check all three databases

Running

Command What
pnpm worker ingestion worker (BullMQ; registers the monthly sync cron)
pnpm mcp MCP server + dashboard API on :3000; also serves web/dist when built
pnpm dev:web Vite dev server on :5173, proxying /api → :3000
pnpm cli discover whole-mailbox billing-sender discovery → auto-backfill
pnpm cli backfill --service <id> full history for one sender
pnpm cli sync incremental sync now (otherwise monthly via SYNC_CRON)
pnpm cli categorize backfill usage categories onto invoice line items

just

A root justfile wraps the common loops so nothing depends on remembering which pnpm script maps to which process. Recipe names cannot contain a colon — just rejects it at parse time — hence dev-web rather than dev:web.

Command What
just list recipes
just dev-mcp pnpm mcp — API + MCP on :3000
just dev-web pnpm dev:web — Vite on :5173
just dev-all both of the above in one terminal; Ctrl-C stops both
just dev alias for dev-all
just build pnpm build (backend dist/ + web/dist)
just test pnpm test — full vitest run
just clean pnpm lint:fix — Biome format + lint with autofix
just kill 3000 kill whatever holds a port, when a dev server outlives its terminal

dev-all runs each server in its own process group so Ctrl-C reaches the tsx/vite child, not just the pnpm wrapper it was launched through.

Dashboard

A React SPA at /, served by the same process as the MCP server. Pages: Dashboard (spend, budget, top vendors, recent invoices), Breakdown (usage categories rolled up across vendors), Invoices (paginated, searchable), Reports (the same views over fiscal quarters and years), Calendar (received + projected), and MCP (connection recipes).

Fiscal periods derive from client.org.fiscal_year_start_month (default January, changeable on the Reports page). Fiscal years are named for the year they end in — an April start makes Apr 2025 – Mar 2026 into "FY2026" — and every label carries its date range, since that convention is not universal.

Sign in with Google (Better Auth). Signing in creates a user but grants no access: membership in an org is a deliberate act, so the first person through the door does not inherit a tenant. Grant it with

pnpm cli grant-membership --org 1 --email you@example.com --role owner

Sign-up is allowlisted by ALLOWED_SIGNUP_EMAILS; an address outside it can still join by holding a pending invitation (pnpm cli invite).

MCP keys live in client.api_key — pnpm cli create-api-key --org 1 mints one and prints it once. No key is ever sent to the browser.

Two behaviours worth knowing:

  • Currency conversion is query-time only. Invoices are stored in the currency the vendor billed; the dashboard's currency selector is a display target, and every invoice is converted to it at the ECB rate on that invoice's own date, so a past month's total does not move when today's rate does. Converted values are never written back (SPEC.md:190-196), and converted figures are labeled with the rate date. The ECB publishes ~30 currencies — hard-pegged currencies outside that set (AED) use an explicit peg table in src/fx/rates.ts. The MCP tools are unaffected and still sum per-currency with no conversion, which is the spec's documented default.
  • Categories live on line items. billing.invoice_line_items.category is assigned by an LLM from a fixed five-value set, so a single vendor invoice can split across categories. Classification never fails an invoice — anything unclassified reads as Other until pnpm cli categorize retries it.

Local development runs two processes: pnpm mcp and pnpm dev:web (or just dev-all for both), then open http://localhost:5173. In production only pnpm start:mcp is needed, provided pnpm build has produced web/dist.

MCP tools

list_services, list_invoices, get_invoice, get_invoice_pdf, spend_summary — all scoped to the org the presented API key belongs to; money in minor units, per-currency aggregation, no FX conversion.

Claude Desktop's connector UI expects OAuth, which this MVP doesn't implement — bridge with mcp-remote in claude_desktop_config.json instead:

{
  "mcpServers": {
    "invoice-mcp": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://<host>/mcp",
        "--header",
        "Authorization: Bearer <MCP_API_KEY>"
      ]
    }
  }
}

Development

  • pnpm test / pnpm typecheck / pnpm lint (or just test / just clean)
  • pnpm codegen — regenerate src/db/types.ts after schema changes
  • tsx scripts/smoke-extract.ts [pdf] — live LLM extraction smoke test
  • tsx scripts/make-fixture-pdf.ts — regenerate the multi-page fixture PDF
  • tsx scripts/probe-mailbox.ts [query] — read-only audit of discovery coverage: replays sender attribution over the live mailbox and lists which vendors would be found, which arrive via a billing alias, and which are already ingested

Billing aliases and shared inboxes

Vendor invoices frequently arrive through a shared billing alias or Google Group (billing@, techteam@, …) rather than direct to the connected mailbox. The group re-sends the mail with itself as the RFC From: address, leaving the vendor only in the display name ("'OpenRouter, Inc' via Tech Team"). Keying on From alone therefore collapses every vendor behind an alias into one pseudo-sender that no classifier will accept as a vendor.

resolveSender (src/gmail/messages.ts) prefers X-Original-Sender — which Google sets on exactly these rewrites — and records the alias as ParsedMessage.deliveredVia. Discovery additionally refuses to treat the org's own addresses as vendors, so invoices you forward to yourself or to a bookkeeping tool don't get ingested a second time.

Deploy (Railway)

One repo, two services:

  • worker — start pnpm start:worker
  • mcp — start pnpm start:mcp (health check /healthz); serves the MCP endpoint, the dashboard API, and the built SPA from one port

Both services are connected to this repository on main, so a push to main deploys them. railway up still works and deploys the working tree instead, which is the escape hatch for testing a change that is not committed — but it is no longer how a release happens.

Both build with pnpm install && pnpm build (which also builds web/dist). Migrations run automatically at boot for both roles — the web service can otherwise start before the worker has applied one and serve sign-ins against a missing table — so no pre-deploy command is needed. A simultaneous deploy is safe: node-pg-migrate locks with pg_try_advisory_lock, which does not wait, so the loser is retried by src/main.ts rather than crashing.

Set env vars per .env.example (Railway injects database URLs for linked Postgres/Mongo/Redis services). Both services need BETTER_AUTH_SECRET, AUTH_GOOGLE_CLIENT_ID, AUTH_GOOGLE_CLIENT_SECRET, PUBLIC_APP_URL and ALLOWED_SIGNUP_EMAILS; the mcp service additionally needs PUBLIC_MCP_URL set to its public https://<host>/mcp. Build the SPA with VITE_PUBLIC_ORIGIN=https://<host> so canonical, OG and sitemap URLs are absolute and correct.

Sign-in uses a separate Google OAuth client from Gmail ingestion. The ingestion client carries gmail.readonly, a restricted scope, so sharing it would show a mailbox-access consent screen for a plain login. Register https://<host>/api/auth/callback/google on the sign-in client.

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured