Skip to content

Releases: yuens1002/bookie

v0.8.13 — release-pipeline and test-diagnosability fixes

Choose a tag to compare

@yuens1002 yuens1002 released this 07 Aug 02:03
7636a77

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 identical parse() helper called JSON.parse on it directly — turning every server-side error into SyntaxError: Unexpected token 'I' and discarding the actual cause. An intermittent report.test.ts failure 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 null from prisma.posting.findMany() in src/tools/reports.ts, i.e. a posting whose parent entry was deleted mid-query by a different test file's afterAll. generate_report reads 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/inspector crossed v1 → v2 (#56). The inspector does not give the server it spawns its own environment — the -e default is {} — so the published server started with no BOOKIE_DB_URL and died in bootstrapLedger. 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 unpinned npx silently 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 guard scripts/setup.ts uses) so its helpers can be unit-tested without provisioning a real Neon project; scripts/verify-published-install.test.ts covers the flag building and shell escaping. The entry-point guard itself moved to a shared isMainModule() in scripts/setup.ts (both scripts had the same raw process.argv[1] === fileURLToPath(...) compare) and now resolves both sides, since process.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

Choose a tag to compare

@yuens1002 yuens1002 released this 07 Aug 01:15
cff9e2c

Added

Changed

  • manage_receipts: tool description now explicitly directs Claude.ai (mobile/web) clients to never pass fileContent, 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 that hasFile:false from a structured-only attach is the correct, complete outcome, not a fallback. docs/TOOLS.md regenerated (also picked up pre-existing drift from PR #30's add_transaction/import_transactions describe clarifications, which had never been regenerated).

Fixed

  • test/setup.ts: integration tests no longer run against whatever BOOKIE_DB_URL happens to point at. They now require BOOKIE_TEST_DB_URL (a separate Neon branch) and refuse to start if it is unset or resolves to the same host+database as BOOKIE_DB_URL. Cleanup only runs in afterAll, so an interrupted run against a real ledger left rows behind — and the comments in test/setup.ts, test/oauth.test.ts, test/receipts.test.ts and test/reconcile.test.ts all 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.ts asserts 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.example and CONTRIBUTING.md document the new variable.
  • src/lib/oauth.ts: removed the 60-second setInterval that purged expired oauth_tokens rows. 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 in issueAuthCode instead of on a clock. The purge is awaited but error-swallowed so housekeeping can never fail an auth exchange. rotateRefreshToken now delegates token creation to issueRefreshToken rather than duplicating it inline.
  • src/transports/http.ts: /.well-known/oauth-authorization-server advertised token_endpoint_auth_methods_supported: ["none"] while /token rejects any exchange without a matching client_secret — a spec-compliant client that trusted the metadata would send no credentials and fail with invalid_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, return hasFile:false + a fileWarning) instead of hard-failing when fileContent is 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 /release skill's own version bump (0.8.4 → 0.8.5) never propagated to server.json's version/package-entry version, caught by test/server-json.test.ts failing on main immediately after the v0.8.5 release merged.

v0.8.5 — Dockerfile port/expose guard + MCP Registry submission

Choose a tag to compare

@yuens1002 yuens1002 released this 03 Jul 11:25
7e6e428

Added

  • test/dockerfile.test.ts: asserts the Dockerfile's ENV PORT and EXPOSE values 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's mcpName field: 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_KEY was 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

Choose a tag to compare

@yuens1002 yuens1002 released this 03 Jul 08:46
0840329

[0.8.2] — 2026-07-03

Added

  • verify-npm-install job in npm-publish.yml — after each publish, installs bookie-mcp from 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 — a try/finally). Requires a NEON_API_KEY repo secret.
  • verify-npm-install now files (or comments on an existing open) a GitHub issue when it fails, titled verify-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 via gh issue list --search, so repeated failures for the same version comment on one issue instead of spamming duplicates. Needs issues: write permission, scoped to just this job.

Changed

  • README.md: document the npm/npx install path alongside the source npm run setup flow, 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 via npm run setup's browser auth step
  • README.md: add a "Publish to npm" GitHub Actions status badge next to the npm version badge — reflects the pass/fail of the release pipeline, including verify-npm-install
  • vitest.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 of test/**

Fixed

  • npm audit fix: bumped hono (4.12.23 → 4.12.27) and esbuild (0.28.0 → 0.28.1, transitive via tsx/vitest) to patched versions. Neither vulnerability was reachable in bookie's actual usage (no cors()/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 create requires an --org-id; without one it drops into an interactive org picker that hangs indefinitely once stdout isn't a real TTY (always true under execSync) — every brand-new Neon account hit this on first-ever npm run setup, since a fresh account has no cached "current org." Now resolves the org via neonctl orgs list first (auto-selects when there's exactly one, errors with guidance to set NEON_ORG_ID when there's more than one) and passes --org-id explicitly. Same fix applied to scripts/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 — running npm run setup a second time (deliberately, for a second independent ledger, or by accident after losing .env) silently created a second, empty, identically-named bookie project 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 set NEON_PROJECT_NAME to deliberately create a second ledger) instead of silently duplicating.
  • scripts/setup.ts: npm run setup never actually worked on Windows. buildEnvContent split its .env.example template on "\n" only; git on Windows checks that file out with CRLF line endings by default, leaving a trailing \r on every line. Since . doesn't match \r and $ requires true end-of-string, the per-line substitution regex silently failed to match every line — .env was written back out as the raw, unmodified template (literal USER/PASSWORD/ep-xxxx.REGION placeholder text, empty secrets), with no error. The only symptom was a downstream prisma db push failure (P1001: Can't reach database server) pointing at a placeholder hostname — nothing in the failure indicated the real cause. Fixed by normalizing \r\n\n before splitting. Added a retry (scripts/setup.ts's new shared retry() helper, also used by verify-published-install.ts) around the db push step regardless, since genuine Neon compute cold-start on a just-created project is a real, separate, already-documented characteristic (see vitest.config.ts's testTimeout comment) — but note that specific failure, when it happens, is not what this CRLF fix addresses.
  • scripts/verify-published-install.ts: replaced a spawnSync(cmd, argsArray, { shell: true }) call (Node's DEP0190: unescaped args concatenated into a shell command) with a plain execSync string, matching the run() 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 no EXPOSE) bound whatever PORT Railway 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 pinning ENV PORT=8080 + EXPOSE 8080 in the Dockerfile so Railway's target-port auto-detection has something to key off. docs/DEPLOYING.md updated with the new default and a troubleshooting note.
  • README.md: "Quick start (remote, HTTP — for Claude.ai mobile)" opened with a npm run build && npm start snippet binding to localhost: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

Choose a tag to compare

@yuens1002 yuens1002 released this 01 Jul 18:28
f091d25

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.md and the /release skill now document that npm publish is automatic (triggered by the version-tag push, via npm-publish.yml) — previously said "not published to npm", stale since bookie-mcp@0.7.11 shipped.

Included in this phase (P6, shipped across 0.7.x)

  • npm publishing — bookie-mcp published automatically via CI on every version-tag push; also runnable via npx 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

Choose a tag to compare

@yuens1002 yuens1002 released this 01 Jul 17:38
e26b950
  • fix(workflow): move npm publish to its own workflow, untangled from the GHCR job's paths filter so it always runs on version-tag pushes; skip on tag deletion; verify the tag matches package.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 } when BOOKIE_API_KEY is 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 least BOOKIE_API_KEY or JWT_SECRET is configured — prevents accidentally deploying an unauthenticated financial endpoint.
  • src/transports/http.ts: /authorize now returns 500 server_error when OAUTH_CLIENT_SECRET is unset — prevents any visitor from completing the OAuth flow on an unconfigured server.
  • Renamed npm package from bookie to bookie-mcp; set "private": false to enable npm publishing; updated bin key to bookie-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

Choose a tag to compare

@yuens1002 yuens1002 released this 14 Jun 10:35
d6b80a0

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 Bucketmanage_receipts action='attach' now accepts optional fileContent (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. New action='get_url' refreshes the URL on demand. action='delete' removes the stored file alongside the DB row. action='list' surfaces hasFile and mimeType per receipt.
  • src/domain/blob.ts — thin S3 client wrapper using @aws-sdk/client-s3 + @aws-sdk/s3-request-presigner. Storage uses Railway's injected AWS_ENDPOINT_URL, AWS_S3_BUCKET_NAME, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION env 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; added BOOKIE_DB_DIRECT_URL to 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_report tool — run any report and email it as Markdown via Resend

v0.6.0 — MCP resources + prompts (2026-06-13)

  • bookie://accounts resource (JSON list of all accounts with balances) and bookie://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_report via Resend

v0.4.0 — Tax reports (2026-06-13)

  • generate_report type='schedule-c' and type='schedule-e' — fiscal-year P&L for sole-proprietor and rental real estate segments
  • export_report tool — 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 entry
  • generate_report type='monthly-reconciliation' — opening/closing balance, income/expense by segment, uncategorized entries, uncleared discrepancies
  • reconcile — 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 flagging
  • manage_rules — description-substring categorization rules; auto-suggestion from past postings
  • split_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.json for one-click deploy

v0.6.0 — P5 Resources & prompts

Choose a tag to compare

@yuens1002 yuens1002 released this 13 Jun 13:43
26be652

Added

  • MCP resources: bookie://accounts (JSON list of all accounts with current balances) and bookie://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_rules gains action='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. Rejects accountId, pattern, id, and priority when action='suggest'.

v0.5.0 — P4 Remote hardening & delivery

Choose a tag to compare

@yuens1002 yuens1002 released this 13 Jun 12:55
783459e

Added

  • Per-request rate limiting on the HTTP transport: fixed-window per-IP, configurable via RATE_LIMIT_RPM env 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 /mcp request logs {ts, ip, method, toolName} to stderr in JSON. Captured by Railway deploy logs.
  • send_report tool — run any report (monthly-reconciliation, schedule-c, schedule-e) and email it as markdown via Resend. Requires RESEND_API_KEY + RESEND_FROM env vars; fails fast with a clear message if either is missing. Same parameter contract as generate_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

Choose a tag to compare

@yuens1002 yuens1002 released this 13 Jun 12:26
e2150cc

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 by taxLine; Schedule E groups by property then by taxLine. Null taxLine → "Unclassified" bucket; entries without propertyId on Schedule E accounts → "Unassigned" property bucket. Pure domain engines in src/domain/tax.ts. Completes P3 tax reports.
  • export_report tool: render any report (monthly-reconciliation, schedule-c, schedule-e) as markdown (human-readable) or csv (spreadsheet-ready). Re-fetches data fresh; no need to call generate_report first.

Details

See CHANGELOG for the full entry.