finflow-mcp

finflow-mcp

Automatic personal finance tracker MCP server that reads transaction notification emails from Gmail, extracts transaction data, and records them to Google Sheets, enabling AI clients to query spending and drive syncs.

Category
访问服务器

README

finflow-mcp

Automatic personal finance tracker. It reads transaction notification emails from Gmail, extracts the numbers, and appends them to a Google Sheets ledger — on a schedule, without you opening anything.

finflow setup            # locale, timezone, base currency
finflow auth             # connect your own Google OAuth client
finflow sync             # creates the ledger, writes the first rows
finflow daemon install   # and then you can stop thinking about it

finflow status tells you whether it is still working.

Status: early development. The core is built and tested; it has not yet run for weeks against a real mailbox. See Honest limitations.


Two ways to run it

Autopilot (the point of the project). A per-OS scheduled job runs finflow daemon run-once and exits — launchd on macOS, a systemd user timer on Linux, Task Scheduler on Windows. There is no long-lived background process, which removes every memory-leak and stale-token class of bug and survives reboots for free.

MCP server. Connect it to an AI client (Claude Desktop, Claude Code) and ask about your spending, or drive a sync interactively. In this mode the server never calls an LLM — the connected client does the extraction and posts results back through the same validation and de-duplication the daemon uses.

// Claude Desktop config — check the current docs for the file location
{
  "mcpServers": {
    "finflow": { "command": "finflow-mcp" },
  },
}

How an email becomes a row

Gmail search (short, language-agnostic query)
   └─ client-side scoring against every keyword pack   ← language support is free here
        └─ sanitise: mask cards/accounts, strip OTPs, truncate
             └─ extract
                  ├─ sender template        free, instant, offline, reproducible
                  ├─ Claude (optional)      opt-in, your own API key, cost-capped
                  └─ neither                parked in needsExtraction — never guessed
                       └─ validate (Zod) → categorise → de-duplicate → Sheets

Extraction is a strategy, so there is exactly one pipeline. The daemon and the MCP server differ only in which extractor is plugged in — "validation is identical on both paths" is a structural fact rather than something to keep re-checking.


Design commitments

Read-only against Gmail. Scopes are gmail.readonly and drive.file. No modify, send or delete scope is ever requested, and drive.file cannot see any file FinFlow did not create.

Integer money. Amounts are stored as integer minor units. There is no floating-point arithmetic anywhere on the ledger path, currency exponents are an explicit table (JPY is 0, KWD is 3), and an unknown currency is an error rather than an assumption of 2.

Ambiguity is refused, never guessed. Rp1.234 means different things in different locales. When the text and the configured locale cannot settle it, FinFlow reports PARSE_AMBIGUOUS instead of picking the likelier reading. In a ledger, a wrong number recorded silently is worse than an email that fails loudly.

Dates are Temporal, not milliseconds. Month boundaries are computed as PlainDate → startOfDay(timezone). March 2026 is 744 hours in Jakarta, 743 in New York and 745 in Berlin, and the tests assert exactly that.

Language support is free where it can be. Month names come from CLDR via a build-time codegen (72 languages, committed so runtime never depends on the user's ICU build); number separators come from Intl. Keyword scoring runs client-side, so adding a language costs nothing in API quota or query length.

Failures are visible. The daemon writes a heartbeat on every run — success or failure — and raises an OS notification once per failure streak, not once per failure. Silent failure is the worst possible outcome for an autopilot: you go on believing the ledger is complete while it quietly stops being so.

Nothing leaves the machine uninvited. Network egress is googleapis.com only, plus api.anthropic.com if you explicitly enable the Claude extractor. In that case only the sanitised body is sent — account numbers masked, one-time codes removed, raw text never.


Idempotency

The scheduler runs hourly over a 48-hour overlap window, so every email is seen many times. Identity is deliberately split:

  • transactionId = hash of (source, sourceRef, occurrenceIndex) — which email, and it never changes
  • contentHash = hash of the extracted values — what we read, and it may
Situation What happens
Same id, same hash Already recorded → skip
Same id, new hash Re-read produced better values → update the row
New id, known hash within ±3 days Possibly the same payment via a second email → record and flag for review
New id, new hash Insert

The second row is why the two hashes are separate. Combined, a re-read that produced any difference would get a new id, miss the lookup, and append a duplicate — which with an hourly daemon is not a rare edge case.

Deleting a row in the sheet by hand is respected: a short ring of processed email ids stops the next sync from helpfully putting it back.


Configuration

~/.finflow/config.json, validated on every load.

Key Notes
locale, timezone, dateOrder Detected from Intl but always confirmed — a wrong timezone files every transaction a day out, silently
baseCurrency Totals are in this; other currencies are reported beside it, never converted
numberFormat Optional override for banks that ignore your locale
gmail.senderAllowlist The most effective filter there is, and the only one that behaves the same in every language
extractor.enabled Claude extraction. Off by default, with an explicit consent step
extractor.monthlyTokenBudget Hard cap. Exceeding it stops the extractor, not the sync — templates keep working
daemon.intervalMinutes Default 60, with ±5 minutes of jitter

Secrets live in ~/.finflow/.env (0600), never in config.json.


Commands

Command
finflow setup Configure locale, timezone, currency, extraction
finflow auth Connect Google (loopback + PKCE)
finflow sync [--dry-run] Read new emails and record them. A dry run writes nothing, not even the cursor
finflow daemon install | uninstall | status | run-once | preview The scheduled job. run-once is exactly what the scheduler invokes
finflow status Is it still working?
finflow doctor Diagnose everything, including the 7-day OAuth trap

Honest limitations

  • Parsing quality depends on sender templates, not on language. A BCA template does not help with Mandiri. The i18n work makes amounts and dates language-independent; it cannot make a bank's HTML layout universal. Expect the first week to lean on the Claude extractor if you enable it.
  • Not supported, by choice: non-positional CJK numerals (二万五千), Japanese-era and Hijri calendars, currency conversion, and month names that are ambiguous across languages without a narrowing locale (listopad is November in Polish and October in Croatian). Each is reported, never guessed.
  • You will occasionally need to re-authenticate — realistically only when you change your Google password, which revokes Gmail-scoped tokens and cannot be worked around.
  • Accuracy is not 100%. The needs_review column and extraction_confidence exist so you can audit rather than trust blindly.

Threat model, briefly

Where secrets live ~/.finflow/tokens/google.json and ~/.finflow/.env, mode 0600 in a 0700 directory. finflow doctor verifies and repairs the permissions
What reaches logs Everything passes through a redaction layer with two independent rules — by field name and by value shape. A test suite scans logger output for tokens, PANs and email bodies
What reaches an external API Nothing, unless you enable the Claude extractor. Then: the sanitised body only
Blast radius if a token leaks Read access to your Gmail and to the one spreadsheet FinFlow created. Revoke at myaccount.google.com/permissions
What FinFlow cannot do Send, modify or delete mail; read any other Drive file; move money

Requirements

  • Node.js >= 20
  • Your own Google Cloud project — see docs/google-setup.md
  • Optional: an Anthropic API key, only if you enable the Claude extractor

Development

npm install
npm run typecheck && npm run lint && npm test
npm run gen:month-names   # regenerate the CLDR month table (output is committed)

License

MIT

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选