Releases: yuens1002/bookie
Releases · yuens1002/bookie
Release list
v0.8.13 — release-pipeline and test-diagnosability fixes
Added
Changed
Fixed
test/tool-result.ts: when a tool fails, the server returns its error as plain text, and the eight test files that each carried an identicalparse()helper calledJSON.parseon it directly — turning every server-side error intoSyntaxError: Unexpected token 'I'and discarding the actual cause. An intermittentreport.test.tsfailure had survived several sessions for exactly this reason: three clean re-runs could not reproduce it and nothing was left to diagnose. The helper is now shared and includes the response text on a parse failure, which identified the cause on its first recurrence —Inconsistent query result: Field entry is required to return data, got nullfromprisma.posting.findMany()insrc/tools/reports.ts, i.e. a posting whose parent entry was deleted mid-query by a different test file'safterAll.generate_reportreads every posting in a month, so parallel test files sharing one database inevitably see each other's rows. The underlying concurrency issue is not fixed here; it is now diagnosable.scripts/verify-published-install.ts: the post-publish smoke test failed on every release after@modelcontextprotocol/inspectorcrossed v1 → v2 (#56). The inspector does not give the server it spawns its own environment — the-edefault is{}— so the published server started with noBOOKIE_DB_URLand died inbootstrapLedger. The DB and API-key variables are now passed explicitly via-e, single-quoted because Neon connection strings contain?and&. The inspector is pinned to 2.1.0: an unpinnednpxsilently upgraded across a major version between releases, and the failure surfaced as a misconfigured-environment error that looked nothing like dependency drift.main()now only runs when the script is invoked directly (the same guardscripts/setup.tsuses) so its helpers can be unit-tested without provisioning a real Neon project;scripts/verify-published-install.test.tscovers the flag building and shell escaping. The entry-point guard itself moved to a sharedisMainModule()inscripts/setup.ts(both scripts had the same rawprocess.argv[1] === fileURLToPath(...)compare) and now resolves both sides, sinceprocess.argv[1]reflects how the script was invoked and need not be absolute — and a wrong answer there fails silently, exiting 0 with the smoke test never having run.
v0.8.12 — OAuth compute & test-isolation fixes
Added
Changed
manage_receipts: tool description now explicitly directs Claude.ai (mobile/web) clients to never passfileContent, explains why no client-side upload workaround exists (raw file bytes are unreachable; a signed-URL upload path was tried and removed because Claude.ai's sandboxed runtime blocks outbound HTTP requests — see PR #28), and clarifies thathasFile:falsefrom a structured-only attach is the correct, complete outcome, not a fallback.docs/TOOLS.mdregenerated (also picked up pre-existing drift from PR #30'sadd_transaction/import_transactionsdescribe clarifications, which had never been regenerated).
Fixed
test/setup.ts: integration tests no longer run against whateverBOOKIE_DB_URLhappens to point at. They now requireBOOKIE_TEST_DB_URL(a separate Neon branch) and refuse to start if it is unset or resolves to the same host+database asBOOKIE_DB_URL. Cleanup only runs inafterAll, so an interrupted run against a real ledger left rows behind — and the comments intest/setup.ts,test/oauth.test.ts,test/receipts.test.tsandtest/reconcile.test.tsall claimed the suite ran against a dev branch when it followed.env. The same-database guard normalizes Neon's pooled/direct hostname pair, which address one database — comparing raw hosts would have let the live ledger through as a "test" target (caught by Copilot review on PR #54).test/db-target.test.tsasserts the substitution actually took effect and covers the comparison directly, so a refactor that drops either fails loudly instead of silently writing to the live ledger..env.exampleandCONTRIBUTING.mddocument the new variable.src/lib/oauth.ts: removed the 60-secondsetIntervalthat purged expiredoauth_tokensrows. Querying the database every minute meant the serverless compute never accumulated the 5 idle minutes it needs to scale to zero, so it ran continuously and consumed roughly 94% of the monthly compute allowance while completely idle. Expired refresh tokens are now purged opportunistically when a new one is issued (the only event that adds rows), and expired in-memory auth codes are swept inissueAuthCodeinstead of on a clock. The purge is awaited but error-swallowed so housekeeping can never fail an auth exchange.rotateRefreshTokennow delegates token creation toissueRefreshTokenrather than duplicating it inline.src/transports/http.ts:/.well-known/oauth-authorization-serveradvertisedtoken_endpoint_auth_methods_supported: ["none"]while/tokenrejects any exchange without a matchingclient_secret— a spec-compliant client that trusted the metadata would send no credentials and fail withinvalid_client. Now correctly advertises["client_secret_post"](RFC 6749 §2.3.1). The contradiction was latent because the deployed secret sat under a pre-0.6.3 variable name (OAUTH_AUTH_SECRET), so the enforcement branch never executed.manage_receipts: gracefully degrade (save structured data, returnhasFile:false+ afileWarning) instead of hard-failing whenfileContentis provided but Railway Bucket isn't configured — matches the mobile client flow (Claude.ai vision-extracts fields, can't base64-encode raw file bytes).server.json: version drift — the/releaseskill's own version bump (0.8.4 → 0.8.5) never propagated toserver.json'sversion/package-entryversion, caught bytest/server-json.test.tsfailing onmainimmediately after the v0.8.5 release merged.
v0.8.5 — Dockerfile port/expose guard + MCP Registry submission
Added
test/dockerfile.test.ts: asserts the Dockerfile'sENV PORTandEXPOSEvalues stay in sync — regression guard for the target-port mismatch that caused the v0.8.2 Railway 502 ("Application failed to respond").server.json+package.json'smcpNamefield: publishes bookie's metadata to the official MCP Registry (registry.modelcontextprotocol.io), so directories like PulseMCP can auto-discover it instead of requiring a separate manual submission per directory.
Docs
docs/RELEASING.md: fixed stale drift —NEON_API_KEYwas documented as "Not yet set" but has been set since 2026-07-02, before the last release even ran.
v0.8.2 — npm install verification + Railway deploy port fix
[0.8.2] — 2026-07-03
Added
verify-npm-installjob innpm-publish.yml— after each publish, installsbookie-mcpfrom the registry into a disposable Neon project, pushes the bundled schema, and confirms the server responds over stdio via the MCP inspector CLI; the project is always deleted after (pass or fail — atry/finally). Requires aNEON_API_KEYrepo secret.verify-npm-installnow files (or comments on an existing open) a GitHub issue when it fails, titledverify-npm-install failed for <tag>— previously a failure was only visible as a red check in the Actions tab, easy to miss. Dedups by title viagh issue list --search, so repeated failures for the same version comment on one issue instead of spamming duplicates. Needsissues: writepermission, scoped to just this job.
Changed
README.md: document the npm/npx install path alongside the sourcenpm run setupflow, including adding another machine to an existing ledger without cloning; add npm version badge; tip to use OAuth (not email+password) when signing up for Neon for the first time vianpm run setup's browser auth stepREADME.md: add a "Publish to npm" GitHub Actions status badge next to the npm version badge — reflects the pass/fail of the release pipeline, includingverify-npm-installvitest.config.ts: exclude.claude/**from test discovery — a stray agent worktree under.claude/worktrees/was silently double-running the entire suite via its own nested copy oftest/**
Fixed
npm audit fix: bumpedhono(4.12.23 → 4.12.27) andesbuild(0.28.0 → 0.28.1, transitive viatsx/vitest) to patched versions. Neither vulnerability was reachable in bookie's actual usage (nocors()/serveStatic()/Lambda adapter imports; esbuild's dev server never runs in this project), but both were safe in-range bumps.scripts/setup.ts:neonctl projects createrequires an--org-id; without one it drops into an interactive org picker that hangs indefinitely once stdout isn't a real TTY (always true underexecSync) — every brand-new Neon account hit this on first-evernpm run setup, since a fresh account has no cached "current org." Now resolves the org vianeonctl orgs listfirst (auto-selects when there's exactly one, errors with guidance to setNEON_ORG_IDwhen there's more than one) and passes--org-idexplicitly. Same fix applied toscripts/verify-published-install.ts, which had the identical bug and would have hung on every GitHub Actions run (no TTY at all there).scripts/setup.ts: Neon allows multiple projects with the same name in one org — runningnpm run setupa second time (deliberately, for a second independent ledger, or by accident after losing.env) silently created a second, empty, identically-namedbookieproject with no warning. Now checks for an existing project with the target name first and aborts with guidance (reuse the existing one's connection strings, or setNEON_PROJECT_NAMEto deliberately create a second ledger) instead of silently duplicating.scripts/setup.ts:npm run setupnever actually worked on Windows.buildEnvContentsplit its.env.exampletemplate on"\n"only; git on Windows checks that file out with CRLF line endings by default, leaving a trailing\ron every line. Since.doesn't match\rand$requires true end-of-string, the per-line substitution regex silently failed to match every line —.envwas written back out as the raw, unmodified template (literalUSER/PASSWORD/ep-xxxx.REGIONplaceholder text, empty secrets), with no error. The only symptom was a downstreamprisma db pushfailure (P1001: Can't reach database server) pointing at a placeholder hostname — nothing in the failure indicated the real cause. Fixed by normalizing\r\n→\nbefore splitting. Added a retry (scripts/setup.ts's new sharedretry()helper, also used byverify-published-install.ts) around thedb pushstep regardless, since genuine Neon compute cold-start on a just-created project is a real, separate, already-documented characteristic (seevitest.config.ts'stestTimeoutcomment) — but note that specific failure, when it happens, is not what this CRLF fix addresses.scripts/verify-published-install.ts: replaced aspawnSync(cmd, argsArray, { shell: true })call (Node'sDEP0190: unescaped args concatenated into a shell command) with a plainexecSyncstring, matching therun()helper already used for every other external command in the file.Dockerfile: the README's "Deploy on Railway" button produced a container that never actually served traffic. Railway's public domain for the one-click template defaulted its target port to 3000, but the container (with noEXPOSE) bound whateverPORTRailway happened to inject (8080) — every request 502'd ("Application failed to respond") even though the deploy log showed the server starting cleanly and the container stayed healthy. Confirmed via a real deploy: fixed by pinningENV PORT=8080+EXPOSE 8080in the Dockerfile so Railway's target-port auto-detection has something to key off.docs/DEPLOYING.mdupdated with the new default and a troubleshooting note.README.md: "Quick start (remote, HTTP — for Claude.ai mobile)" opened with anpm run build && npm startsnippet binding tolocalhost:3000— leftover from before the Railway one-click badge existed (present since the very first commit), never revisited when the badge was added. It was actively misleading (localhost isn't reachable by Claude.ai mobile; no tunnel is documented anywhere) and unnecessary for either deploy path (the badge pulls a pre-built image; "from source" has Railway build it). Removed — the section now opens directly with the badge.
v0.8.0 — P6: deployment & distribution
Minor bump, not patch — retroactively marking the npm/Railway/GHCR distribution work already shipped across the 0.7.x patch series culminating in v0.7.11 as its own roadmap-phase increment (P6: deployment & distribution), consistent with this project's versioning policy (minor = phase increment). No new user-facing behavior beyond v0.7.11 other than the docs fix below.
Changed
docs/RELEASING.mdand the/releaseskill now document that npm publish is automatic (triggered by the version-tag push, vianpm-publish.yml) — previously said "not published to npm", stale sincebookie-mcp@0.7.11shipped.
Included in this phase (P6, shipped across 0.7.x)
- npm publishing —
bookie-mcppublished automatically via CI on every version-tag push; also runnable vianpx bookie-mcp - Railway one-click deploy — README button now points to a real Railway Template
npm run setup— bootstraps a new deployment (Neon DB, secrets,.env,db:push)- GHCR image publish workflow
Full Changelog: v0.7.11...v0.8.0
v0.7.11 — npm publish + Railway template deploy fix
- fix(workflow): move npm publish to its own workflow, untangled from the GHCR job's
pathsfilter so it always runs on version-tag pushes; skip on tag deletion; verify the tag matchespackage.json's version before publishing - fix(deploy): point README's Railway deploy badge at a real Railway Template link; the prior
railway.app/new?image=...URL isn't a supported Railway deploy mechanism and just opened the generic new-project picker - fix(workflow): switch railway redeploy from webhook to railway cli
- feat(workflow): add npm publish job; publishes to npm on version tag push
- fix(workflow): remove railway redeploy step; railway auto-deploys from GitHub source
- feat(p6): add npm run setup, ghcr publish workflow, railway one-click deploy badge
Changed
src/lib/auth.ts:requireAuth()now returns{ ok: false }whenBOOKIE_API_KEYis unset rather than{ ok: true }, so OAuth JWT callers are correctly validated instead of being silently passed through.src/transports/http.ts: HTTP transport now refuses to start unless at leastBOOKIE_API_KEYorJWT_SECRETis configured — prevents accidentally deploying an unauthenticated financial endpoint.src/transports/http.ts:/authorizenow returns500 server_errorwhenOAUTH_CLIENT_SECRETis unset — prevents any visitor from completing the OAuth flow on an unconfigured server.- Renamed npm package from
bookietobookie-mcp; set"private": falseto enable npm publishing; updated bin key tobookie-mcp.
Fixed
import_transactions: CC/card (liability) accounts now produce correct double-entry postings.- Receipt file upload "Region is missing" error fixed for Railway Bucket.
Full Changelog: v0.7.0...v0.7.11
v0.7.0 — receipt file storage
bookie is an MCP server for personal and business double-entry bookkeeping. You talk to it through any MCP-capable environment (Claude Desktop, Cursor, VS Code, or any host supporting stdio MCP). It stores your ledger in Neon Postgres and runs on Railway.
What's in v0.7.0
Added
- Receipt file storage via Railway Bucket —
manage_receipts action='attach'now accepts optionalfileContent(base64) +mimeType(JPEG, PNG, WEBP, HEIC, or PDF) to upload the original receipt image or PDF to S3-compatible object storage. A signed download URL (1-hour TTL) is returned on attach. Newaction='get_url'refreshes the URL on demand.action='delete'removes the stored file alongside the DB row.action='list'surfaceshasFileandmimeTypeper receipt. src/domain/blob.ts— thin S3 client wrapper using@aws-sdk/client-s3+@aws-sdk/s3-request-presigner. Storage uses Railway's injectedAWS_ENDPOINT_URL,AWS_S3_BUCKET_NAME,AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY,AWS_DEFAULT_REGIONenv vars (AWS SDK Generic style). Structured receipt data continues to save even when no bucket is configured.
Changed
README.md— stdio quickstart now says "any MCP-capable environment" rather than Claude Desktop only; addedBOOKIE_DB_DIRECT_URLto the example config block.
Full feature history
v0.6.x — OAuth connector + remote hardening (2026-06-13)
- OAuth 2.0 authorization server (PKCE S256, JWT access tokens, refresh token rotation) so bookie can be added as a Claude.ai MCP connector
- Per-request rate limiting (fixed-window per-IP,
RATE_LIMIT_RPM), audit logging to Railway logs send_reporttool — run any report and email it as Markdown via Resend
v0.6.0 — MCP resources + prompts (2026-06-13)
bookie://accountsresource (JSON list of all accounts with balances) andbookie://reports/{year}(annual fiscal snapshot: Schedule C, Schedule E, month-by-month summary)- MCP prompts:
monthly-close,categorize-uncategorized,prepare-tax-summary manage_rules action='suggest'— scans past categorizations and surfaces candidate automation rules
v0.5.0 — HTTP hardening + email reports (2026-06-13)
- Rate limiting, audit logging,
send_reportvia Resend
v0.4.0 — Tax reports (2026-06-13)
generate_report type='schedule-c'andtype='schedule-e'— fiscal-year P&L for sole-proprietor and rental real estate segmentsexport_reporttool — render any report as Markdown or CSV
v0.3.0 — Receipts + reconciliation (2026-06-13)
manage_receipts— attach, list, delete structured receipt data (merchant, date, total, line items) against a journal entrygenerate_report type='monthly-reconciliation'— opening/closing balance, income/expense by segment, uncategorized entries, uncleared discrepanciesreconcile— two-step (preview → commit): match a bank/card statement CSV against payment-leg postings and mark them cleared
v0.2.0 — CSV import + categorization rules (2026-06-10)
import_transactions— two-step (preview → commit) bank/card statement CSV import with RFC-4180 parsing, content-hash dedup, possible-match flaggingmanage_rules— description-substring categorization rules; auto-suggestion from past postingssplit_transaction— one payment leg + N category legs (receipt splits)delete_transaction— correct mistakes / duplicate imports- Segment model (
manage_segments) + Property model (manage_properties) for per-entity P&L and Schedule E tracking - Vitest integration test suite
v0.1.0 — Core ledger scaffold (2026-06-03)
- MCP server with dual transport: stdio (local) + Streamable HTTP over Hono (Railway deploy)
- Neon Postgres via Prisma; seeded US sole-proprietor chart of accounts
manage_accounts,add_transaction,query_transactions,account_balances- Integer-cent money math; bearer-token auth for HTTP transport
- Dockerfile +
railway.jsonfor one-click deploy
v0.6.0 — P5 Resources & prompts
Added
- MCP resources:
bookie://accounts(JSON list of all accounts with current balances) andbookie://reports/{year}(annual fiscal snapshot in markdown: Schedule C, Schedule E, and a one-row-per-month summary table). - MCP prompts:
monthly-close(guided month-end close workflow),categorize-uncategorized(find and categorize entries lacking an income/expense leg),prepare-tax-summary(generate + export annual Schedule C and E). manage_rulesgainsaction='suggest': scans past income/expense categorizations, groups by normalized description, and returns candidate rules for descriptions appearing 2+ times with no existing rule matching them. RejectsaccountId,pattern,id, andprioritywhenaction='suggest'.
v0.5.0 — P4 Remote hardening & delivery
Added
- Per-request rate limiting on the HTTP transport: fixed-window per-IP, configurable via
RATE_LIMIT_RPMenv var (default 60 rpm), returns 429 on excess. Exempt:/health. Rate limit applied before auth to guard against unauthenticated floods. - Audit logging on the HTTP transport: each
/mcprequest logs{ts, ip, method, toolName}to stderr in JSON. Captured by Railway deploy logs. send_reporttool — run any report (monthly-reconciliation,schedule-c,schedule-e) and email it as markdown via Resend. RequiresRESEND_API_KEY+RESEND_FROMenv vars; fails fast with a clear message if either is missing. Same parameter contract asgenerate_report. Completes P4 delivery.docs/DEPLOYING.md— step-by-step Railway + Neon production-branch + Resend setup guide; env var reference table.
v0.4.0 — Tax & export
What's new
generate_report— Schedule C & E modes: fiscal-year P&L for sole-proprietor (Schedule C) and rental real estate (Schedule E) segments. Schedule C groups income/expense accounts bytaxLine; Schedule E groups by property then bytaxLine. NulltaxLine→ "Unclassified" bucket; entries withoutpropertyIdon Schedule E accounts → "Unassigned" property bucket. Pure domain engines insrc/domain/tax.ts. Completes P3 tax reports.export_reporttool: render any report (monthly-reconciliation,schedule-c,schedule-e) asmarkdown(human-readable) orcsv(spreadsheet-ready). Re-fetches data fresh; no need to callgenerate_reportfirst.
Details
See CHANGELOG for the full entry.