Glassbook is a budgeting app that stays in your browser. You import a statement, sort spend, set budgets, and teach a few rules. None of that leaves this tab. There is no account and no server.
The usual deal with "AI budgeting" is the other way around. You upload a CSV, connect Plaid, or paste a statement into a chat so a model can help. I built this because I will not send a year of swipes to someone else's server so a model can lecture me about dining.
Instead, the agent comes here. WebMCP tools run in the page, against the same localStorage ledger you see. Every call shows up in the activity log. If a write goes wrong, you can undo the last one or undo everything the agent did.
Once you connect an agent, tool results can enter its context. Tools still send summaries and cleaned merchant names, not your whole statement, unless you ask. You can see what went out. You can rewind what got written.
People already refuse to paste a bank statement into a chat box. They should not have to upload one to a SaaS either.
WebMCP is what lets an agent work on data the app never uploads. The tools live in this page. The page decides what they can read, how little they return, and how undo works. There is no remote MCP server sitting on a copy of the ledger.
The screen is shared too. The agent can open Transactions, filter to Dining, and ring the Delta row while it talks about June. A server-side tool cannot point at a row on your screen.
Load the demo and you get about 320 uncategorized rows (May through August 2026). Clicking chips by hand takes forever, and it is easy to miss Scribd at $11.99 or that June 18 was Delta and Airbnb. Ask the agent to categorize everything, then make a rule so a correction sticks. After that, look for forgotten subscriptions, ask why June was expensive, and set a dining budget. If the streak was too aggressive, Undo all agent changes walks it back.
- Dashboard, transactions, budgets, import, and rules. The demo is a fixed four-month sample. June 18 is the trip (Delta −$420, Airbnb −$380).
- Import stays in this tab. CSV and TSV go through PapaParse. OFX / QFX have a local parser. Excel and text PDFs go through AnyDoc WASM in a worker. Scanned PDFs fail here. Nothing is uploaded.
- The agent can categorize many rows in one go. Chips update on screen when that happens.
- Correct one merchant, save a rule, and optionally apply it to the other uncategorized matches.
- The agent can change views, set filters, highlight rows (clears after 10 seconds), and pin a note on the dashboard.
- The activity log records every tool call. Undo last and Undo all agent changes sit at the top.
- Appearance follows your system unless you pick light or dark.
No login. Data stays in this browser and is still there after a reload.
Live: glassbook.vercel.app
-
Open Import and click Load demo. If you already have rows, confirm replace. You should land on Transactions with about 320 uncategorized rows.
-
Connect an agent:
- ChatGPT desktop (easiest). Open the live URL in the built-in browser, not a Codex preview. Use GPT-5.6 Sol or GPT-5.6 Terra. Site tools are off on Luna and Light. Check Site tools in the address bar. The sidebar should say 18 tools live.
- Chrome 149+. Turn on
chrome://flags/#enable-webmcp-testing, relaunch, then open the live URL.
-
Click Connect agent for copy-paste prompts. Open Activity log. Then ask, in this order:
Categorize all my transactionsMake a rule so that sticks(fix one chip by hand first)Find subscriptions I might have forgotten about(look for Scribd at $11.99)Why was June so expensive?(June 18: Delta −$420, Airbnb −$380)Set a $300 monthly dining budget
-
In the activity log, click Undo all agent changes. Categories, rules, and that dining budget should rewind.
Open DevTools on the live URL. The page always installs window.__glassbookTools:
window.__glassbookTools.list() // 18 tools
await window.__glassbookTools.execute("get_overview", {})
await window.__glassbookTools.execute("list_transactions", { month: "2026-06", limit: 20 })That path uses the same wrapper as a live agent: activity entries, Error: strings, and MCP text results.
After the store loads, tools register on document.modelContext, with fallbacks to navigator.modelContext and window.modelContext. Each tool is defined once in src/webmcp/tools/ with defineTool, then registered from src/webmcp/register.ts. If the host injects modelContext after the first paint, registration retries.
await document.modelContext.registerTool({
name: "get_overview",
description: "Call this first to understand the user's ledger.",
inputSchema: { type: "object", properties: {}, additionalProperties: false },
annotations: { readOnlyHint: true },
execute: async (input) => {
const text = await executeRegisteredTool("get_overview", input);
return { content: [{ type: "text", text }] };
},
});All 18 tools use that shape: name, description, inputSchema, execute. Read tools set readOnlyHint: true. One AbortController is shared across registerTool(..., { signal }). Re-init cancels the previous set.
Results go back as MCP { content: [{ type: "text", text }] }. Handlers do not throw to the host. A failure is an Error: … string so the model can try again (unknown category, bad month, missing id). Amounts in the tool API are dollars, and expenses are negative. Categories accept an id or a name. "uncategorized" is a valid filter.
executeRegisteredTool logs every call as the agent. A write saves a snapshot of the ledger (up to 50). undoLast pops one snapshot. undoAll walks back a run of agent writes. Reads are logged and do not create undo snapshots.
What tools return on purpose:
get_spending_summarysends grouped totals, not hundreds of rows.list_transactionssends cleaned merchant names, 50 by default and 100 at most, and neverrawDescription.find_recurringfigures cadence locally and returns a short list (merchant, amount, cadence, yearly cost).- There are no account numbers or balances in the data or in tool output.
| Name | Type | What it does |
|---|---|---|
get_overview |
read | First look: months, totals, categories, budgets, rules, current view. |
list_transactions |
read | Page of ids and cleaned merchants. Filter by month, category, or search. |
get_spending_summary |
read | Expenses grouped by category, merchant, or month. |
find_recurring |
read | Subscriptions and rent: 3+ hits, similar amounts, weekly or monthly. |
get_budget_status |
read | Limit, spent, remaining, and over/under for a month. |
get_dashboard_state |
read | What is on screen: view, month, filters, highlights, pinned titles. |
categorize_transactions |
write | Categorize many rows at once. One undo for the whole batch. |
create_category |
write | Add a category when the defaults are not enough. |
create_rule |
write | Substring rule on merchant or description. Can apply it now. |
apply_rules |
write | Run saved rules on the remaining uncategorized rows. |
set_budget |
write | Set or clear a monthly category limit (null / 0 clears it). |
add_transaction |
write | Add one cash or correction row. |
delete_transactions |
write | Delete rows by id. One undo for the batch. |
annotate_transaction |
write | Set or clear a note without changing the category. |
pin_insight |
write | Pin a finding on the dashboard so it stays. |
highlight_transactions |
ui | Ring rows, scroll the first into view, clear after 10 seconds. |
set_view_filters |
ui | Set the transaction filters and open that view. |
navigate_to_view |
ui | Switch dashboard, transactions, budgets, import, or rules. |
git clone https://github.com/BlinkZ404/GlassBook.git
cd GlassBook
npm install
npm run devnpm test
npm run buildVite, React 19, TypeScript, Tailwind, and Zustand. Node 20+. No extra services to stand up.
- The ledger is stored in
localStorageunderglassbook-store. The app never sends it to a server. - Import (CSV, Excel, OFX, PDF) runs in this tab. Spreadsheet and PDF conversion uses AnyDoc WASM in a worker. Scanned PDFs are rejected here. There is no remote OCR.
- Once you connect an agent, tool results can enter its context. Those calls are logged. Writes can be rewound.
- No accounts, analytics, or telemetry.
src/
├── App.tsx # Hydrate store, init WebMCP, route views
├── components/ # Shell, sidebar, activity log, import UI
├── views/ # Dashboard, transactions, budgets, import, rules
├── store/ # Zustand persist + undo snapshots
├── lib/
│ ├── csv.ts # PapaParse + column mapping
│ ├── import/ # OFX, Excel, PDF / AnyDoc worker
│ ├── activity.ts # Log copy and icons
│ └── rules.ts # Merchant substring rules
└── webmcp/
├── register.ts # document.modelContext.registerTool
├── registry.ts # defineTool
├── wrapper.ts # Activity + undo around every call
└── tools/ # read.ts, write.ts, ui.ts
public/
├── logo.png
├── favicon.ico
├── favicon-16.png
├── favicon-32.png
├── apple-touch-icon.png
├── icon-512.png
├── og.png # 16:9 social
└── devpost-glassbook.png # 3:2 gallery
LICENSE # MIT
MIT. See LICENSE. © 2026 Arifur Rahman.
Built for the OpenAI WebMCP Challenge.
