taxtrace
Integrates with the macOS Keychain to store and retrieve passwords for encrypted PDFs, with per-read approval via Touch ID or Apple Watch through a compiled helper.
taxtrace
Prepare a US federal and state return from your own documents, on your own machine, with every number traced to the page it came from.
taxtrace does not file anything. It reads a folder of your documents (W-2s, 1099s, 1098s, property manager statements, receipts, foreign bank statements, last year's return) and produces an entry sheet you type into consumer tax software, an FBAR account list, and a list of questions for you: anything it couldn't settle from the documents alone.
If taxtrace helps with your return, a ⭐ on GitHub is how I know: taxtrace sends nothing home, so stars are the only count of who uses it. If it saved you a preparer's fee, a $1 coffee says so too.
Three rules hold everywhere:
Nothing is guessed. Every value carries the file, the page and the exact text it was read from, and a deterministic check rejects any value whose quote isn't on that page.
Your documents never leave your machine. Where a language model reads them (sorting documents, reading messy receipts), it is a local MLX model on your Mac. The chat can use a cloud model, which only ever sees masked text (see Chat).
Nothing is decided silently. A receipt that could be a repair or an improvement, a missing 1099, a rate choice: each is a question for you, with the evidence next to it.
Status
Early. What works today:
taxtrace redact: masks a document down to its layout, so a layout can be studied (by a person or an AI assistant helping write a parser) without its values, names or places. Every digit becomes#, and every word the IRS's own publications don't use in lowercase becomes*s, so names, streets and towns go without anyone listing them.taxtrace import,outlineandunlock: sort a preparer's export into the workspace, list the forms in each document, and check passwords kept in the Keychain.taxtrace prior: reads a filed return's carryforwards (see Carryforwards).taxtrace k1: reads each Schedule K-1 (Form 1065) face, every box and code, its statements, the Schedule K-3, and North Carolina's D-403 NC K-1 (see K-1s).taxtrace passive: each passive activity's year and the loss it carries in (see Passive activities).taxtrace 1098: reads each Form 1098 mortgage interest statement (see Form 1098).taxtrace 1099: reads the 1099-DIVs and 1099-INTs on brokers' consolidated 1099 statements (see Forms 1099-DIV and 1099-INT).taxtrace rental: puts the rental's Schedule E together, line by line (see Schedule E).taxtrace qbi: Form 8995, the qualified business income deduction (see Form 8995).taxtrace paystub: reads a pay stub's income tax withheld, and projects the year's (see Estimated tax).taxtrace estimates: the year's withholding and estimated payments against the safe harbor, quarter by quarter (see Estimated tax).taxtrace ira: each spouse's IRA deduction and the Roth IRA's limit at the year's projected income (see IRAs).taxtrace itr: reads India's income tax return (ITR-2): a property sale, the interest, the tax India kept (see India's return).taxtrace bank: reads a foreign bank's statement of account: interest, a tax refund, balances (see Bank statements).taxtrace ui: the workspace in the browser, with a chat (see Chat).
Planned, in order: the foreign side (interest at Treasury rates, foreign tax credit, FBAR, Form 8938 check), the entry sheet, and a year-over-year check.
Related MCP server: obsify
Install
pip install -e ".[dev,mcp]"taxtrace[chat] adds the chat's Claude SDK, taxtrace[local] the model that runs on your Mac and taxtrace[mcp]
an MCP server.
Use
taxtrace import ~/Downloads/portal-export.zip # filed returns to prior-returns/, the rest by tax year
taxtrace redact ~/TaxData/prior-returns # writes ~/TaxData/redacted/*.txtA path that isn't one from where you are is looked up in the workspace: taxtrace k1 2025 reads
<workspace>/2025.
taxtrace outline <files> lists each page's masked heading: which forms a document holds, in order (--match '1116|4562' lists only the pages naming them). taxtrace words <pdf> --page 2 --rows shows a page's words, masked,
with where each one sits (--x 300:450 and --top 70:240 narrow it to a region): the geometry of a printed form, for
writing a reader of it. --marks adds the drawn shapes, so a checkbox ticked with a drawn mark shows. --page 19-20,69, --page all and several files take one run, so files that share a Keychain entry cost one approval.
taxtrace show 'prior-returns/2025*' --page 19 opens a document in Chrome at a page, for you to read yourself.
A scanned PDF (pictures of pages, no text) is read through ocrmypdf
on your machine (brew install ocrmypdf), once: the searchable copy is kept under the original's sha256
in <workspace>/ocr-cache, so the text values are quoted from never shifts. A picture (.jpg, .png: a
phone's photo or screenshot of a statement, as one arrives in a chat) goes the same way. A chat app shrinks
what it sends, though, and a blurred table's rows may not read: bank then saves nothing for that account,
and a PDF from the bank (or the picture sent as a document, uncompressed) is the better copy.
import takes a folder or a zip, such as a tax preparer's portal export with one folder per year. It
skips identity documents (driver's licenses, Social Security cards, passports) and anything already
imported, and records every file it places, with its sha256, in imports.jsonl.
Your documents and everything derived from them live in a workspace folder, ~/TaxData or
$TAXTRACE_HOME, which taxtrace refuses to place inside a git repository. Names that are also
ordinary words (Will, Grace) go in <workspace>/redact-terms.txt, one per line.
A password-protected PDF is read with a password kept in the macOS Keychain, never in a file or on a
command line. One default entry covers every return that shares a password; an entry under a file's
name wins for that file, and one under the name before its first - covers a bank's monthly statements
(quillbank for quillbank-4410-2026-01.pdf, quillbank-4410-2026-02.pdf, ...). security asks for it
without echoing, and -T "" trusts no app, so macOS asks you (Allow / Deny) each time anything reads it,
taxtrace included:
security add-generic-password -s taxtrace -a default -T "" -w
security add-generic-password -s taxtrace -a "2024 Return.pdf" -T "" -w # just this file
security add-generic-password -s taxtrace -a quillbank -T "" -w # quillbank-*.pdfClick Allow, not Always Allow: that would add the reader to the trusted list and end the prompts.
That prompt only takes the typed password. To approve each read with Apple Watch or Touch ID instead:
taxtrace keychain setup # the default entry and the chat's key
taxtrace keychain setup --account "2024 Return.pdf" # and a file's own entrysetup builds a small helper (taxtrace/macos/keychain_gate.swift, compiled with the Xcode command line tools)
and moves each entry over to it. macOS asks for the entry the old way one last time, and the secret is never
retyped or shown. From then on the entry trusts only the helper, and the helper releases it only after macOS's
own approval: Apple Watch, Touch ID, or the password where neither is set up. Every read is still yours to allow.
Only the entries setup moved go through the helper; any other is read the classic way, with its one prompt. If
you deny the classic prompt during setup, it says so and leaves that entry as it was: run setup again.
Once the helper is built, store a new password with taxtrace keychain store <name> (a file's name, its issuer's
such as quillbank, or default): security asks for it unechoed, then the helper moves it over the way setup
does, with one classic prompt. An entry stored with a bare security add-generic-password -T <helper> stays
security's: macOS adds its own prompt to every read the helper makes, on top of Apple Watch or Touch ID.
taxtrace reads each entry at most once per command, so a folder of returns sharing the default password
costs one prompt. taxtrace unlock says which entry opens each locked file, and how each stored password
is shaped (aaaa99999), never what it is. A password pdfminer turns down is tried once more with qpdf
(brew install qpdf), which gets it on stdin and decrypts a copy into a temporary folder for the read: when qpdf
turns it down too, the stored password is wrong, not the reader.
Another tool can read an entry setup moved, such as an API key it shares with the chat, without the entry
trusting that tool too:
taxtrace keychain read anthropic --for jobwatch # the secret on stdout, once approvedThe prompt names the item and the tool. The secret goes to the program that runs the command, never to a
terminal, and an entry setup didn't move isn't read this way.
A tool that reads the key unattended (a background service after a restart) has nobody there to approve, and the
approval times out. For a key that guards little, such as one with a spend limit, taxtrace keychain release anthropic moves it back for good: one approval, then security saves it again (the secret on its stdin, never
in a command line) and it reads without any prompt, taxtrace's chat included. The document passwords can't be
released.
When a read asks for the password instead of Apple Watch or Touch ID (a Watch out of range, asleep or locked
falls back to it), taxtrace keychain check says which ways macOS offers right now and why not, and
check --try shows one Watch / Touch ID prompt and prints what came back. Neither rebuilds the helper or touches
an entry.
Besides PDFs and text files (.txt, .csv), Word documents (.docx: one line per paragraph and per table row) and Excel workbooks (.xlsx: a page per sheet, one line per row) are read, with Python's own zip and XML modules. A password-protected Office file isn't read: save an unprotected copy into the workspace.
redact, outline and unlock with no paths read the whole workspace, leaving out what taxtrace wrote
there itself (redacted/, ocr-cache/).
Carryforwards
taxtrace prior # every return in <workspace>/prior-returns
taxtrace prior --layout # also each line a value came from, masked
taxtrace prior --words 23-26,71 # also those pages' masked words, in the same run (one Keychain approval)Next year's return starts from last year's: each asset's depreciation so far, and each passive activity's loss
that couldn't be taken yet. prior reads them from the preparer's own printouts: the federal depreciation
schedule (Drake's "Federal Depreciation Schedule", or its "Depreciation Detail Listing", whose land rows carry
forward as cost with no depreciation) and Form 8582's Worksheets 5 and 6. Where the return prints "Next Year's
Depreciation Worksheet", each asset's deduction there is checked against a year rolled forward. The values go to <workspace>/carryforwards/<return>.json, each with its page and the exact
line it was read from. The terminal gets counts, failed checks and questions, masked like redact's output.
Nothing is taken on trust:
Every row is added up against the schedule's totals line, and each asset's depreciation so far against its basis.
On Form 8582, each activity's ratio is checked against its share of the loss, its unallowed loss against its ratio of the total, and Worksheet 6 against Worksheet 5.
Worksheets 1 and 3 list every passive activity, with income as well as loss: its net income, net loss, the unallowed loss it brought in, and the overall gain or loss. A row leaves the columns it doesn't use empty, so each amount is read by where it sits, under its column's heading. Each row has to come to its overall gain or loss, and the rows to the total line.
A schedule printed twice is kept once, and a second copy that disagrees is a question.
The alternative minimum tax and book copies are skipped. So is next year's projected schedule, which is checked to start from this year's accumulated depreciation.
A Section 179, bonus or salvage amount, or business use under 100%, is a question for you, not a guess.
Schedule B (Form 1116) gives the foreign tax carried to next year, per category of income (passive, general...) and again for the alternative minimum tax, by the year each tax arose: its columns are the ten years before this one, this year and a total. Each column's lines have to combine (line 3 is lines 1 and 2, line 8 is lines 3 through 7), the subtotal has to carry from page 1 to page 2, and the total has to add up its columns. The AMT copy's page 2 doesn't say it's the AMT copy, so it goes with the nearest page 1. A box left unchecked is a question. The terminal shows only the years a carryover arose in.
The Carryover Worksheet (Drake's "List of items that will carryover to the next tax return") is read row by row: state and local taxes paid, the QBI loss, the foreign tax credit (regular and AMT), last year's tax for the estimated-tax safe harbor, each spouse's IRA basis. Its foreign tax credit has to equal Schedule B's carryover, and its heading has to name the next year. Anything under Passive Activity or At Risk Limitations is a question, since no copy seen so far had rows there.
--layoutlists each row's label and which amounts it has (regular, AMT, taxpayer, spouse), never the amounts.Form 8995 (the simplified QBI deduction) gives each business's qualified business income (line 1, continued on its own page past five businesses) and lines 2 to 17. Its arithmetic is checked line by line, its QBI and REIT/PTP losses carried on (lines 16 and 17) have to be the Carryover Worksheet's and the next year's lines 3 and 7.
Form 1040 gives the lines next year's estimated tax goes by: the adjusted gross income, the total tax, the withholding, the estimated payments, the refundable credits, the total payments, the overpayment, what of it went to next year, and the estimated tax penalty. Each is found by its number and words of its label, and the payments have to add up.
Returns years apart are checked against each other. An asset's depreciation is rolled forward a full year at a time, and has to match the later return's prior depreciation. A miss says how far off it is in years of depreciation (0.46 years is a first, partial year left out), with no amounts. A year's foreign tax carried to the following year has to be the next year's carryover from the prior year, year by year.
taxtrace prior --project 2025 # roll the latest schedule forward, for returns not in hand yet--project writes <workspace>/carryforwards/projected-<year>.json: each asset's depreciation for every
year since the latest return, marked as projected until that year's return arrives to confirm it.
--layout also shows each asset's prior and current depreciation in years. That's enough to spot a
missing or partial year without seeing an amount.
K-1s
taxtrace k1 2025 # every K-1 face in <workspace>/2025
taxtrace k1 2025 --layout # also each value's text, masked, and where it sitsA K-1's face is a printed form: each amount sits in its box under the box's number, with its code beside it, so
read in order an amount lands next to whatever label is beside it on the page. k1 reads by position instead. It
finds Part III's two columns by their first boxes and gives each amount to the nearest box number above it in its
column. Parts I and II are read by their labels: the EIN, the partnership, the partner (last four digits of the
TIN), each check mark, profit, loss and capital shares, liabilities, and the capital account. The values go to
<workspace>/k1/<document>.json, each with its page, position and exact text. The terminal gets the boxes and
codes that hold amounts, with failed checks and questions, never an amount or a name.
Item L's capital account has to add up: beginning, plus contributions, this year's income and other changes, less withdrawals, is the ending balance. Each share has to be between 0% and 100%, and each amount in a box with codes has to have one.
A face printed twice for the same partner counts once. The same amounts for another partner, such as a spouse, are that partner's own K-1.
An IRA's K-1 (item I2 checked) is marked as one: its income stays in the account, off the return.
An amount the face sends to the partnership's statement ("STMT", "See statement") is read from the statement pages after the face. A "Line 20, Code Z" or "Box 19C" reference, or a code in a box's table, ties each amount line under it to its box and code, with its description; a label wrapped around its amount (Statement A's 199A grid) is put back together. Totals and item L's reconciliation, which repeat amounts given elsewhere, are left out. A statement amount the pages don't give is a question.
Each Section 199A amount is marked with its kind (qualified business income, W-2 wages, UBIA, REIT dividends, PTP income) and the trade or business it's for. An aggregated group's totals are kept but marked as the group's, so they're never added in a second time, and each one has to be its businesses' amounts added up.
A final K-1, or more than one activity (boxes 22, 23), is a question.
North Carolina's partner K-1 (Form D-403 NC K-1) is read the same way, by where things sit. Each of its eleven lines has its amount just above the line's label, and the partnership's FEIN, the partner's number (only its last four digits are kept) and the three Yes/No answers are read by their labels. It goes to the same folder, with where the D-400 takes each line.
Line 1 has to be the same partnership's federal K-1, Part III lines 1 to 11 added up, as the form says. The federal K-1 can be read in the same run or an earlier one; if none has been read, that's a question.
Lines 6 to 9 are for nonresident partners and 10 and 11 for taxed partnerships: an amount there on a K-1 whose answers say it isn't one fails.
Additions and deductions (lines 2, 3) and credits (line 4) are a question: the form doesn't say which Schedule S or D-400TC line each belongs on.
A Schedule K-3 (the partner's international items) is read for Form 1116: box E's marks for which parts apply, and every amount in Part II (gross income and deductions by source and category), each with its line, its country row and country code, and its column, from (a) U.S. source through the foreign categories to the total. Part III's foreign taxes are noted.
Each row's columns have to add up to its total, and each total line to the lines its label names ("combine lines 1 through 23", "subtract line 54 from line 24").
Part II's interest, dividends, gains and section 1231 lines have to match the same partnership's K-1 boxes, and a K-1 whose box 16 says a K-3 is attached has to have one read, now or on an earlier run.
The terminal says whether any of Part II is foreign source and in which categories. Foreign taxes are a question for now: their table isn't read yet.
Passive activities
taxtrace passive # the latest year of the K-1s read
taxtrace passive --year 2025Form 8582 limits passive losses, and what it doesn't allow carries to the next year activity by activity. passive
starts from the latest return's worksheets as prior saved them (every activity on Worksheets 1 and 3, and what
each carries on from Worksheet 5) and goes on with this year's K-1s as k1 saved them. It opens no document. Each
activity is matched to its partnership's K-1 by the words of its name, and its year is the K-1's boxes 1, 2, 3
and 10, less box 12. Interest, dividends and portfolio deductions aren't the activity's. A K-1 no activity matches
is a new activity. The ledger goes to <workspace>/passive/<year>.json; the terminal gets each activity's
masked name, where its year comes from and whether it's income or a loss, never an amount.
An activity with no K-1 this year is a question: sold, ended, or missing from the folder? A full disposition (a final K-1) frees the loss it carries.
What each activity carries in comes from last year's Worksheet 5. When last year's copy has none, its Carryover Worksheet's Passive Activity section printed empty means nothing carries in; failing that, last year's ledger (activities sold last year dropped) and the Form 8582
passiveworked out for it. Each is a note to confirm against the filed Worksheet 5, and with none of them what each carries in is left unknown.A return whose Worksheet 5 carries losses on, followed by a return whose Carryover Worksheet lists none, is a question: the next year's passive income allowed them all, or they were left off and lost to every later year.
The rental's year comes from Schedule E (
rental), and a box 13 deduction whose code doesn't settle whether it's the activity's is a question.
With every activity's year and carried loss known, passive works the year's Form 8582 out and saves it with the
ledger:
Each activity goes in Part IV (rental real estate with active participation: the latest return's Worksheet 1) or Part V (every other one, a partnership's new activity too). Line 3 adds them up; zero or more, and every loss is allowed.
Part II's special allowance (up to $25,000, phased out between $100,000 and $150,000 of modified adjusted gross income) needs
--confirm magi=AMOUNT; married filing separately also needslived_apart=yes|no. It goes to the Part IV activities first (Part VI).The rest of line 3's loss is unallowed, shared by the activities with an overall loss in proportion to their losses (Part VII), and carries to next year. An activity disposed of in full at an overall loss is off the form: its losses, carried ones too, are allowed in full.
Every filed return whose worksheets were read is worked out again from its own Worksheets 1 and 3 and checked against its Worksheet 5: the same arithmetic, rehearsed on the preparer's numbers.
The terminal says what each activity's loss does (allowed, part carried on, off the form), never how much.
Form 1098
taxtrace 1098 2025 # every Form 1098 in <workspace>/2025
taxtrace 1098 2025 --layout # also each value's text, masked, and where it sitsA lender's 1098 is a printed form too. Each box is found by its number and the first words of its label, matched
with the spaces taken out, since a lender's print can split a word ("M ortgage"). Its value is what sits under the
label, from the box's number to the next box's label beside it or below it. The lender's TIN, the borrower's (last
four digits only), the account number and the calendar year are read by their labels. The values go to
<workspace>/forms/<document>.json; the terminal gets which boxes hold amounts, the checks and the questions.
Box 1 has to be there, and has to be under 15% of box 2's principal: more is a misread, not a mortgage.
Box 10 (other) isn't interest. What the lender prints beside it (often taxes or insurance paid from escrow) decides where it goes, so it's a question, as are a refund of interest (box 4), insurance premiums (box 5), points (box 6), more than one property (box 9) and a corrected form.
Box 8's address, or box 7's mark, says which property the interest is for: a rental's goes on Schedule E line 12, a home's on Schedule A. A 1098 with neither is a question.
Forms 1099-DIV and 1099-INT
taxtrace 1099 2025 # every consolidated 1099 statement in <workspace>/2025
taxtrace 1099 2025 --layout # also the account and each box's label, with its value masked
taxtrace 1099 2025 --unlock # also ask the Keychain for locked documents' passwordsBrokers' statements are seldom locked, so a locked document is skipped without asking the Keychain unless
--unlock is given: the folder's other locked files (a W-2, a return) never prompt.
A broker's consolidated statement prints each form as a column of rows under its title ("1099-DIV Dividends and
Distributions - 2025 ..."), the forms side by side: a box's id, its label, and the amount well right of the label.
A box id printed a little under its label's line is on that row. The year is the title's; the payer's TIN and the
account number are read by their labels. A statement printed twice counts once. The values go to
<workspace>/forms/<document>.json; the terminal gets which boxes hold amounts, the checks and the questions.
The form's own arithmetic is checked: qualified dividends (1b), section 897 ordinary dividends (2e) and section 199A dividends (5) are each part of the ordinary dividends (1a), and 2b, 2c, 2d and 2f part of the capital gain distributions (2a).
Box 5 is Form 8995 line 6 (
qbi).The boxes that change where an amount goes are questions: foreign tax paid (with box 8's country), a return of capital (box 3), tax withheld, liquidation distributions, exempt-interest dividends, and on a 1099-INT the early withdrawal penalty, Treasury interest (North Carolina doesn't tax it), tax-exempt interest and bond premium. So is a corrected form.
Schedule E
taxtrace prior # also reads the filed returns' Schedule E: the property, lines 3 to 20, line 19's list
taxtrace 1098 2025
taxtrace rental --year 2025 # reads the answers and policies in <workspace>/2025, writes schedule-e/2025.jsonrental puts one property's Part I together from what's been read, each line with where it came from:
Rents and expenses from the answers typed into the preparer's questions ("Rental income = $1,200/month", "Repairs = $400/year"), under a rental heading. A month's amount is taken twelve times, and that's a question.
Mortgage interest (line 12) from the 1098 whose box 8 is the property's address, and its box 10, when the lender's description says taxes or insurance paid from escrow, on line 16 or 9.
Insurance (line 9) from the landlord policy's declarations page for the property: its annual premium. A policy new this year is a question: an earlier one's premium paid this year counts too.
Depreciation (line 18) from the latest filed depreciation schedule
priorsaved, rolled forward without opening the return again (orprior --project's, when it's newer). Rolled over more than one year, it's a question.
When the year's own return has been filed and read, each line is checked against its Schedule E: a rehearsal that shows which lines the build and the preparer part on, and whether taxtrace's is more or less, never by how much.
What only you know is settled with --confirm, kept in schedule-e/<year>-confirmed.json for every later run:
fair_rental_days=365, personal_use_days=0, rent_all_year=yes, rent=AMOUNT (the rent every tenant paid in the
year, together, in place of the answers' monthly figure), earlier_policy=AMOUNT (a premium paid this year for a
policy before the one read, 0 for none), dues=AMOUNT (association dues, line 19), and cleaning=AMOUNT and
repairs=AMOUNT (the year's receipts added up, lines 7 and 14, a repair a tenant paid out of the rent among them).
A document goes with the property when its address has the same house number and street. A line the latest filed Schedule E had that this year has nothing for (association dues on line 19) is a question, as are the fair rental and personal use days. Locked documents are skipped without a Keychain prompt: answers and policies aren't locked.
Form 8995
taxtrace qbi --year 2025 # writes qbi/2025.json from what k1, rental and prior saved
taxtrace qbi --year 2025 --layout # also each K-1's Section 199A statement rows: what each is and its sign
taxtrace qbi --year 2025 --confirm k1_4567=business --confirm rental=notqbi puts the simplified Form 8995 together from what's been saved, opening no document:
Each partnership's qualified business income is its K-1's passive income or loss (boxes 1, 2, 3 and 10, less 12), the figure a preparer's Form 8995 carries, rather than the Section 199A statement's own total.
The rental's is Schedule E's net from
rental.A passive loss Form 8582 doesn't allow isn't qualified business income until a later year allows it. Each activity's amount is what the year's Form 8582 let through (its overall gain or loss, less what it carried on): the filed one when the year's return is read, otherwise the one
passiveworked out. With neither, the amounts are before that limit, and that's a question.Which businesses count follows last year's filed Form 8995: a partnership it listed (by its EIN's last four digits) that has income or a loss this year, and the rental if it listed a Schedule E. One it didn't list is a question for
--confirm k1_<last four>=business|not(orrental=), kept inqbi/<year>-confirmed.json.Line 6 is the K-1s' REIT dividends and PTP income and the year's 1099-DIVs' section 199A dividends (box 5, from
taxtrace 1099). Last year's losses carried on (lines 16 and 17) come in on lines 3 and 7. Lines 11 to 15 need taxable income, so the deduction is known only when line 10 is zero. The threshold for the simplified form follows the filing statusforeign --confirm filing=kept; above it Form 8995-A applies, which taxtrace doesn't do yet.
When the year's own return has been filed and read, every line and business is checked against its Form 8995. A business whose amount disagrees names the other figure the filed one matches (the Section 199A statement's total, an aggregated group's, the filed Schedule E's net), so a rehearsal shows where a build and the preparer part ways. The terminal gets which businesses are income or a loss, the checks and the questions, never an amount.
Estimated tax
taxtrace prior # reads last year's Form 1040 and Carryover Worksheet
taxtrace paystub 2026/payslip.pdf # the latest pay stub: the year's withholding, projected
taxtrace estimates --year 2026 --confirm paid_2026-09-15=2500A pay stub's header row gives the pay period and the payment date, and its taxes table each tax's amount this paycheck and the year to date ("FIT Withheld", "SIT Withheld (NC)"). The year's withholding is the year to date's plus this paycheck's for each one left: the pay period's length says how often you're paid, and each later paycheck is paid as long after its period ends as this one was, so a December period paid in January counts next year. A bonus or a new W-4 changes that, so it's a question.
taxtrace paystub --rate 47 says what each paycheck left would take home at a 47% pre-tax 401(k) deferral: this
paycheck's net pay, less the deferral's change, plus the federal withholding it moves (the percentage method: the
year's brackets past the standard deduction, on the paycheck's taxable wages times the paychecks in a year) and the
state's at its flat rate, until the year's limit stops the deferrals. The terminal gets each paycheck's take-home as
a share of this one's and the cents of take-home a deferred dollar costs; the amounts go to the file, and --show
prints them in your own terminal only.
No underpayment penalty is due when the year's payments reach the smaller of 90% of the year's own tax and 100% of last year's (110% when last year's adjusted gross income was over $150,000, $75,000 married filing separately). Last year's tax is the Carryover Worksheet's federal tax liability, or Form 1040's line 24 less line 32.
A quarter of it is due by each of April 15, June 15, September 15 and January 15, and each quarter is checked on
its own. An estimated payment counts on the day it's made, so a January payment can't make up an earlier quarter.
Withholding counts as paid evenly through the year, however late, so more withholding before December 31 makes up
every quarter, and estimates says how much would. Last year's overpayment applied to this year counts in April.
What only you know is kept in estimates/<year>-confirmed.json: withheld=AMOUNT (the year's federal tax
withheld in all, in place of the pay stub's projection, when there's more than one job or a 1099 withholds too), paid_YYYY-MM-DD=AMOUNT (each estimated payment)
and tax=AMOUNT (the year's own tax, once it's worked out). The terminal says which quarters are met or short;
the amounts are in estimates/<year>.json.
IRAs
taxtrace ira --year 2026 # from the latest pay stub and last year's Form 1040
taxtrace ira --year 2026 --confirm other=9000 # the year's income besides the wages, once it's knownA married couple filing jointly can each put the year's limit into an IRA, a spouse without pay on the other's
pay. How much of it is deductible phases out with income: over a lower range for a spouse covered by a 401(k), a
higher one for a spouse who isn't but is married to one who is. A Roth IRA has its own range. ira projects the
year's income from the pay stub's federal taxable wages to December 31, as they are and with the 401(k)'s room
filled (which lowers it), plus last year's interest, dividends and Schedule 1 income, and gives each spouse's
deduction and the Roth IRA's limit in both cases. The income itself is masked.
To do
taxtrace todo # open items by due date, undated last
taxtrace todo --due # overdue, plus what's due in the next 7 days
taxtrace todo --due 14 # ... the next 14 daysThe list is todo.md at the root of the workspace, written by hand (taxtrace never reads it as a tax document).
The page shows it above the documents, in groups (overdue, today, this week, later, no date), and a card's checkbox
marks it done.
## Estimated tax
Any line that isn't an item, like this one, is ignored.
- [ ] **Thu 1/14/2027** Pay the fourth-quarter estimate, from `estimates`
- the amount is in estimates/2026.json
- [ ] check the account balance
- [ ] **Wed 10/28, time TBD** Call the plan administrator about the **rollover**
- [x] Order the tax softwareAn item is - [ ] text (open) or - [x] text (done) under a ## Section. A leading bold date is its due date, with
a year (**Fri 1/15/2027**) or without (**Wed 10/28**, the date from 2 months back to 10 months ahead); undated
items are fine. Indented - text lines are notes and indented - [ ] text lines sub-tasks. Marking an item done
changes that line's [ ] to [x] and nothing else, and refuses if the line was edited since the page loaded.
India's return
taxtrace itr 2025 # writes forms/itr-<assessment year>.jsonA US person who sells property in India files India's ITR-2 as a non-resident: the buyer withholds TDS at
registration and the return settles it, often with a refund. itr reads the printed return row by row (amounts in
Indian grouping, 12,34,567):
Schedule CG, each property: the purchase and sale dates, the consideration received, the stamp value, the cost, the improvements (with the financial year of each), transfer expenses, exemptions and the gain.
Schedule OS: the interest, and how much of it is savings-account interest.
Part B-TTI: the tax, the aggregate liability, the taxes paid (advance tax, TDS, TCS, self-assessment) and the refund. What India kept, the taxes paid less the refund, is what Form 1116 can credit.
India's year runs April to March (assessment year 2025-26 is April 2024 to March 2025), so a sale's own date says which US year it belongs to. The checks: the taxes paid add up, the refund is the taxes paid less the aggregate liability (to the nearest ten rupees, as India rounds it), the aggregate is the net liability plus interest and fees, the TDS schedules match Part B-TTI, each sale's balance and gain add up, and the sale falls in the return's year. Questions: an improvement's date (only its financial year is printed, and each converts to dollars at its own day's rate), a stamp value above the price received (the US return uses the price), and whether the person owned the whole property. Nothing that names the person is kept: no PAN, acknowledgement number or address.
Bank statements
taxtrace bank 2025 # writes forms/bank-<last four digits>-<period start>.json
taxtrace bank --rows 2025 # also each row's date, kind and description, maskedAn Indian branch's statement of account is a table: the date, the amount under Withdrawal or Deposit, the balance
("Cr." or "Dr."), then a description that can run onto several lines. bank places each word by where it sits: an
amount belongs to the column whose heading it starts under, and a description line goes with the dated row nearest
it (a description of several lines is centred on its row). Each row gets a kind: interest (its description names the
period it pays for), a tax refund, TDS withheld on interest, or a TDS refund. These give Schedule B and Form 1116 the
interest, Form 8938 and the FBAR the balances, and a cross-check that India's refund arrived.
A bank's monthly "Account Statement" is read too: a row number, dates as 05 Jan 2026, the description left of the
amounts, Indian commas (1,23,456.78), an Opening Balance row and an Account Summary. Its quarterly interest is
credited net of TDS (Int:1234.56 and TAX:123.45), and is read as two rows, the interest and then the TDS withheld.
When only a blurred picture of a statement has arrived (a chat app shrinks what it sends past what OCR reads), type it into a .csv next to it, each row as printed. Its balances are checked one by one like a printed statement's, and a mistyped one isn't saved:
account,000123459876
currency,INR
period,01-04-2026,27-10-2026
opening,"1,02,410.00"
date,withdrawal,deposit,balance,description
05-05-2026,,25000.00,127410.00,Cash Deposit
30-06-2026,,1234.56,128644.56,Int.Pd From:01-04-2026 to 30-06-2026
30-06-2026,123.45,,128521.11,TDS on interestSo is a bank's monthly e-statement of every account the customer holds there ("As on 31-01-26"): a balance summary,
the term deposits, and a page per savings account with the month's transactions (dates as 31-01-26, amounts
right-aligned under Credit, Debit and Balance) between its opening and closing balance lines. Each account is saved as
a statement of its own, with how it's held (single, "former or survivor"). A term deposit's balance is its principal
and the interest accrued on it, and the foreign side takes its interest for the year as what that value grew by:
interest added to a cumulative deposit is income in the year it's added, though none is paid out, and a renewal
carries it into the new principal. A value that falls (TDS taken from the interest at India's year end) is asked
about. The checks: each account's balances follow from its opening balance, its closing balance is its available
balance, and the accounts add up to the balance summary.
The bank's interest certificate on its deposits ("for the financial year ended 31st March 2026") goes through bank
too: per account, the balance on March 31, the interest paid and accrued, and the total tax deducted, checked against
its TOTAL row. It gives what a term deposit's statements don't: the TDS taken from its interest on March 31. The
foreign side takes it as tax withheld that day (Form 1116) and as interest the value never showed, but only when it
fits the fall in the deposit's value that day (at most the fall plus a quarter's interest). Tax taken earlier in
India's year belongs to an earlier US year, so a certificate that doesn't fit is asked about, never counted whole.
India's quarterly TDS certificate (Form 16A until March 2026, Form 131 from April 2026) goes through bank as well:
the quarter, the bank, each payment of interest, the tax deducted and the challans that paid it to the government,
checked against their totals. India's quarters fall inside calendar quarters, so the foreign side compares each 2026
quarter's tax with the TDS it found for that bank (the statements' TDS rows and the interest certificate's): a match is
noted, and tax left over is asked about, never counted on its own: it was taken where no statement shows it. Form
26AS gives each deduction's day and account. Tax on interest credited in an earlier year (a certificate can list such
credits) still counts in the year it was taken when Form 1116 claims taxes paid, its usual box. foreign --confirm tds_<the quarter's last day, YYYYMMDD>=taken then counts it once, as tax withheld on the quarter's last day and as
interest the deposit's value never showed, and =not leaves it out. Less tax than was found is asked about too. The
bank is matched by the name the certificate prints, from foreign --confirm bank_<the statements' file name's first word>=<name>; <address>.
taxtrace fbar --year 2026 turns the foreign side into each spouse's FBAR (FinCEN Form 114), item by item:
<workspace>/foreign/fbar-2026.txt is a checklist to type into the BSA e-filing form. Each account is in Part II
(held alone) or Part III (held with anyone else: its statement's mode of operation says, or foreign --confirm joint_<last four>=yes|no). Its maximum value is the highest balance at the Treasury's December 31 rate, rounded up to
the dollar, and marked an estimate until the year is covered. The bank comes from foreign --confirm bank_<the statements' file name's first word>=<name>; <address>. Account numbers stay out of it: they're typed in
from a statement.
The checks: each balance is the one before plus the deposit or less the withdrawal, and the rows add up to the statement's grand totals (or, on a monthly statement, its opening and closing balances are the summary's, and its net interest is the interest less the TDS). A statement runs on India's year (April to March), so a calendar year usually needs two. Of the account number only its last four digits are kept, and nothing that names the holders.
Foreign side
taxtrace foreign --year 2025 # writes foreign/2025.json
taxtrace foreign --year 2025 --confirm owner=spouseforeign puts a property sold abroad on Form 8949 in dollars, each amount at its own day's rate: the price at the
sale's, the cost at the purchase's, an improvement at the day it was paid. A currency that fell against the dollar
leaves a dollar gain far smaller than the gain taxed abroad (or a loss, which on a personal home isn't deductible).
The tax the other country kept, the taxes paid less the refund, goes to Form 1116 as passive category, at the rate
of the day it was withheld (a refund is translated at the rate the tax was).
Form 1116 claims taxes either paid or accrued, the box under its Part II, and last year's return shows which:
foreign --confirm method=paid|accrued. Accrued, once chosen, binds every later year. Paid counts each tax in the
year it was withheld, at that day's rate. Accrued counts India's tax for its year (April to March) in the US year that
year ends in, as its return for that year finally kept it, at the IRS's average rate for the US year (section 986(a)):
2026 claims the return for April 2025 to March 2026 (assessment year 2026-27). The TDS withheld during the US year is
then kept apart (tds in the output), since that return credits it, and the TDS from April on belongs to next year's
Form 1116.
Foreign tax the limit kept from being credited carries forward ten years. foreign takes it from last year's return
as prior read it: its Schedule B (Form 1116) line 8, per category and for the alternative minimum tax, by the year
each tax arose, is this year's Form 1116 line 10 (carryovers in the output). Without last year's return read, that's
a question (--confirm carryover=none when nothing carries). A tax in its tenth year is flagged: what this year can't
credit of it expires.
The rates are public and fetched once into <workspace>/fx, each kept with its source:
a day's rate: the Federal Reserve's noon buying rate (FRED,
DEXINUSfor the rupee), or the last business day's before a weekend or holiday;a year's average, for interest: the IRS's yearly average currency exchange rates;
December 31, for Form 8938 and the FBAR: the Treasury's Reporting Rates of Exchange.
What only you know is settled with --confirm, kept in foreign/<year>-confirmed.json: owner=self|spouse|joint|other
(on a joint return either spouse's sale counts in full), improvement_dates=YYYY-MM-DD,... (until then an
improvement is at its financial year's mean rate; average keeps that rate when the dates aren't known),
use=investment|personal|rental (asked when the dollar result is a loss), filing=joint|single|separate|head
(for Form 8938's thresholds) and account_<last four digits>=self|spouse|joint (whose an account is). Each spouse
files their own FBAR, of the accounts in their name, so the FBAR is tested per person; an account held with someone
outside the return (a parent) is yours in full. Form 8938 on a joint return takes both spouses' accounts together.
With the bank statements bank saved, foreign also gives each account's year:
the interest credited in the calendar year, at the IRS's yearly average (Schedule B; passive income on Form 1116), and the TDS withheld on it at each day's rate (a tax Form 1116 credits);
the highest balance and the balance on December 31, at the Treasury's December 31 rate, and from all accounts together whether the FBAR (over $10,000 at any time) and Form 8938 (by filing status, for someone living in the US) are due. A home held directly isn't a Form 8938 asset; the account its price was paid into is;
India's refund found among the deposits: what arrived beyond it is India's interest on the refund, Schedule B income in the year it arrived.
India's statements run April to March, so a calendar year needs two. Until both are read, the days not covered are asked for, and the FBAR and Form 8938 stay unsettled unless what's covered already crosses a threshold. While the year is still going, its average and December 31 rates don't exist yet: the latest day's rate stands in for both, and the FBAR and Form 8938 answers say they're estimates.
Chat
pip install -e ".[chat,local]" # Claude's SDK, and the model that runs on your Mac
taxtrace ui # opens http://taxtrace.localhost:8766/The page lists your workspace by folder and has a chat for tax rules, forms and what a document is for.
It answers with the first model in --models (default claude,agy,local) that can, and moves on to the
next when one can't (no key, quota used up, not installed):
claude: Anthropic's API, withANTHROPIC_API_KEYor a Keychain item holding the key (--keychain, defaultanthropic).agy: Google's Antigravity CLI, on its subscription, run in an empty folder.local: an MLX model on your Mac.
Claude and agy only ever see masked text. Every message, and the list of your documents, goes through the
same masking as taxtrace redact first: digits become #, and words outside the IRS's own vocabulary
become *s. A question with an amount in it, or a name from redact-terms.txt, isn't sent to them at all:
the model on your Mac answers it, and the page says why. Under each question a cloud model answered, the
page shows exactly what it was sent. The chat reads only the document list and what you type: it can't open
a document, change a file or file anything.
Always on (macOS)
An installed app (the page added to your Dock from the browser) only works while the server is running. To start it at login and restart it if it stops:
taxtrace service install # same --port, --models and --keychain as `ui`
taxtrace service status # running or not, the address, the log
taxtrace service uninstallRun install from the shell where TAXTRACE_HOME is set: a login item doesn't read your shell profile,
so install writes that workspace path, and a PATH that finds agy and Homebrew's ocrmypdf, into
~/Library/LaunchAgents/io.github.taxtrace.ui.plist. No key or password goes in it; Claude's key is still
read from the Keychain. The log is ~/Library/Logs/taxtrace-ui.log.
To install the page as an app, open http://taxtrace.localhost:8766/ in Chrome and click the install icon at
the right of the address bar (or ⋮ → Cast, save and share → Install page as app). It gets its own window and
Dock icon. python scripts/make_icons.py rebuilds the app's PNG icons after an SVG changes.
MCP server and agent skill
taxtrace-mcp lets Claude Code, Claude Desktop or any MCP client look at your workspace: what's in it, what
each document is, which forms read cleanly, and what's on your to-do list. It returns masked text only.
pipx install 'taxtrace[mcp]' # or: pip install 'taxtrace[mcp]'
claude mcp add taxtrace -s user -e TAXTRACE_HOME=~/TaxData -- taxtrace-mcpWithout installing first, uv can fetch and run it: claude mcp add taxtrace -s user -e TAXTRACE_HOME=~/TaxData -- uvx --with 'mcp>=2.2' taxtrace mcp. taxtrace mcp runs the same server as
taxtrace-mcp. For Claude Desktop, add this to claude_desktop_config.json (use the full path of taxtrace-mcp
if which taxtrace-mcp isn't on the app's PATH, such as ~/.local/bin/taxtrace-mcp for pipx):
{
"mcpServers": {
"taxtrace": {
"command": "taxtrace-mcp",
"env": { "TAXTRACE_HOME": "/Users/you/TaxData" }
}
}
}Tool | What it gives |
| Counts: tax years, folders, documents, how many were read, what was saved from them, whether a to-do list exists |
| Each document's id, folder and name (masked), kind, and whether it was read; filter to read or unread |
| A document's pages by masked heading: which forms it holds, in order (a locked PDF is reported, not opened) |
| Per Form 1098, 1099-DIV or 1099-INT, or K-1: checks passed of total, the ones that failed, the questions left |
|
|
Every tool is read-only. The masking is the one the chat uses for a cloud model and taxtrace redact writes:
digits become #, words outside the IRS's own vocabulary become *s, and names in redact-terms.txt go first.
No tool returns an amount, name, ID, account number or the workspace's path; dates, counts and pass/fail results
stay. A locked document is never opened and the Keychain is never asked, so a client can't cause a prompt or reach
a password. taxtrace reads your documents on your machine, as it always does; the client only gets what is left
after masking. A test builds a workspace full of made-up amounts and names and checks that none of them appear in
any tool's output.
skills/taxtrace/SKILL.md is an agent skill that tells an assistant when and how to use
these tools, and that it only ever sees masked text. Copy the folder into ~/.claude/skills/, or zip it to upload
to Claude Desktop (Settings, Capabilities, Skills): cd skills && python -m zipfile -c taxtrace-skill.zip taxtrace.
It's listed in the MCP Registry as io.github.vinayvobbili/taxtrace
once released.
Development
git config core.hooksPath .githooks # once: every push first runs scripts/check
scripts/check # ruff + pytest on Python 3.10 and 3.13, as CI does
python scripts/build_vocab.py # rebuild the redaction vocabulary from IRS publicationsSet PRE_PUSH_AUDIT to a command that scans a repository for personal data and exits non-zero on a
finding, and scripts/check runs it too, with the repository's path appended.
Every fixture is invented: no real person, place or amount appears in this repository.
Available Tools
5 toolsget_document_outlineARead-onlyIdempotent
A document's pages, each with its first lines masked: which forms and worksheets it holds, and in what order. A password-protected document is reported as locked, not opened (taxtrace's own commands read it with a password from the Keychain). To see how the forms in it checked out use get_form_checks; to find the id, list_documents.
| Name | Required | Description | Default |
|---|---|---|---|
| id | Yes | The document's id from list_documents (its place in the workspace, from 1). Ids change when documents are added or removed, so list again after an import. | |
| limit | No | How many pages to list from start (default 40, at most 200). | |
| lines | No | How many of each page's first lines to show, masked (default 2: on a tax form they name the form). | |
| start | No | The first page to list (default 1). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint and destructiveHint=false, so safety is covered. The description adds real behavioral context beyond that: content is masked, and a password-protected document comes back as 'locked' rather than opened, with a note about Keychain-supplied passwords.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, no filler, and the return shape is stated first followed by behavioral caveats and sibling routing. The first sentence is slightly convoluted in phrasing but still earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description carries the burden of describing the return value, and it does so reasonably (pages, masked first lines, forms/worksheets, order). Pagination semantics are left to the schema parameters, which is acceptable given full coverage.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, with limit, lines, start and id all documented in the schema including defaults and maxima. The description adds only indirect color (why lines are masked on tax forms) and no syntax or format detail beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a concrete resource and return shape: a document's pages with masked first lines, listing which forms and worksheets it holds and in what order. It also distinguishes itself from siblings by routing form-check results to get_form_checks and id lookup to list_documents.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly names two alternatives with the condition that selects each ('to see how the forms in it checked out use get_form_checks; to find the id, list_documents'). It also flags the locked-document case so the agent knows why a document may not open.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_form_checksARead-onlyIdempotent
Read the forms of one kind in the workspace's PDFs and report how their checks came out, never a value: per form its document id, page, tax year, how many checks passed of how many, the ones that failed and the questions it leaves the person (box names and masked quotes). Every value taxtrace reads is traced to its page and rejected if its text isn't there, so a failed check means a value to look at by hand. Locked documents are skipped and counted. For a document's other pages use get_document_outline.
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | Only this document (its id from list_documents). Omit to read every PDF in the workspace, which can take a while. | |
| form | Yes | Which form to read: 1098 (mortgage interest), 1099 (the 1099-DIVs and 1099-INTs on a broker's statement) or k1 (the federal Schedule K-1 face). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover readOnly/idempotent/non-destructive, yet the description adds substantial context beyond them: it never returns a value, quotes are masked, failed checks signal a value needing manual review, locked documents are skipped and counted, and every value is traced to its page. This is genuinely useful behavioral disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core purpose and packs a lot of useful detail into a few sentences. The middle sentence is long and dense, but every clause earns its place; no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, yet the description specifies the return shape (document id, page, tax year, pass counts, failures, masked questions) and edge cases (locked docs skipped/counted). An agent has what it needs to call and interpret results.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both parameters are already documented, and the description largely repeats the schema's guidance about omitting id to read every PDF. It does not add syntax or format detail beyond the schema, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb+resource: reads one form kind in the workspace's PDFs and reports check outcomes. It also explicitly names the sibling for the complementary task ('For a document's other pages use get_document_outline'), so an agent can distinguish the two tools without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clear context for use, and it names the alternative (get_document_outline) plus the when-to-omit-id behavior. It lacks an explicit 'do not use this when...' statement, but the routing to the sibling is stated.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_statusARead-onlyIdempotent
What the person's workspace holds, as counts: tax years (folders named for one), each folder's number of documents, how many documents taxtrace has read, how many files it saved per kind of reading, and whether a to-do list exists. Names are masked. Start here; for the documents themselves use list_documents, for the to-dos use get_todo.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, non-destructive and closed-world, so the safety profile is covered. The description adds genuinely new context: results are aggregate counts only and 'Names are masked', which is a data-privacy behavior an agent could not infer from structured fields. It stops short of describing shape or size of the payload, so a 4 rather than 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The payload contents are front-loaded into the first sentence and the routing instruction closes the description, so the structure is sound. The middle enumeration is somewhat run-on ('how many documents taxtrace has read, how many files it saved per kind of reading'), which could be tightened into a short list without losing information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, so the description carries the burden of explaining return values – and it does, itemizing every count returned plus the name-masking caveat. Combined with zero params and full annotation coverage, an agent has everything needed to call this correctly and interpret the result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Zero parameters, so there is nothing for the description to disambiguate; baseline is 4. The description correctly implies a no-argument, whole-workspace call rather than a filtered one, which matches the empty schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific framing ('What the person's workspace holds, as counts') and enumerates exactly which counts are returned: tax-year folders, documents per folder, documents read, files saved per reading kind, and to-do list existence. This is clearly distinguishable from siblings that return documents (list_documents) or to-dos (get_todo).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says 'Start here', establishing it as the entry-point tool, and routes the agent to the correct alternative for each adjacent need – 'for the documents themselves use list_documents, for the to-dos use get_todo'. That is the when-to-use plus named-alternatives pattern in two clauses.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_todoARead-onlyIdempotent
The person's open money and tax to-dos from todo.md in their workspace, soonest first: section, due date, days left, group (overdue, today, week, later, undated), and the text, notes and sub-tasks masked (the dates are real; the wording is masked, so ask the person what an item says if it matters). Nothing is changed: the person ticks items on the taxtrace page. For the documents use list_documents.
| Name | Required | Description | Default |
|---|---|---|---|
| days | No | Only the overdue items and those due within this many days from today. Omit for every open item, soonest first, undated last. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (readOnly, idempotent, non-destructive), but the description adds a genuinely non-obvious behavior: the wording, notes and sub-tasks are masked while dates are real, and the agent should ask the person what an item says. It also clarifies that mutation happens elsewhere (the person ticks on the taxtrace page), which prevents a false write attempt.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but front-loaded, with the resource and ordering stated first and the masking caveat second. Every clause carries information; the single long parenthetical is slightly heavy but not padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description covers the returned fields, ordering and group taxonomy, plus the masking limitation the agent must not misreport. Nothing an agent needs to call and interpret it is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the days parameter is fully described there (overdue plus items due within N days, omit for all, undated last). The description adds no syntax or edge-case detail for days, so the schema does the work — baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States the exact resource (open money and tax to-dos from todo.md), the ordering (soonest first), and enumerates the returned fields including the group buckets. It also names the sibling boundary (list_documents for documents), so an agent can select it without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clear usage context: open items only, ordering, and an explicit redirect for documents to list_documents. It does not state what to do for closed/completed to-dos or a direction to tick items, but the 'open only' scope plus the sibling routing is enough guidance for correct selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_documentsARead-onlyIdempotent
The workspace's documents, each with an id, its folder and file name (masked), its kind (pdf, csv, ...) and whether it was read. Nothing is opened, so no password is asked for. The id is what get_document_outline and get_form_checks take. For counts only use get_status.
| Name | Required | Description | Default |
|---|---|---|---|
| only | No | all (default), read (taxtrace has saved something from it) or unread (nothing saved yet: the ones still to read). | all |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations already declare read-only, idempotent, non-destructive, closed-world behavior. The description adds useful behavioral context beyond annotations by stating that nothing is opened and no password is asked for; it does not cover pagination or result limits, but the safety/authentication note is meaningful.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is compact and front-loaded: resource and returned fields come first, followed by the no-password note, downstream id usage, and the get_status alternative. Every sentence adds operational value without filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple one-parameter read tool with annotations covering safety and no output schema, the description supplies the missing return-field context and routing information. Nothing critical an agent needs in order to call it correctly is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, and the single enum parameter is already fully documented in the input schema. The description does not mention the 'only' filter or add meaning beyond the schema, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names the exact resource (the workspace's documents) and enumerates the returned metadata (id, folder/file name, kind, read status), making the operation immediately clear. It also distinguishes the tool from get_status for counts and explains how its returned id is used by get_document_outline and get_form_checks.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly says 'For counts only use get_status,' giving a clear alternative for a common adjacent need. It also explains that the returned id is what get_document_outline and get_form_checks take, routing the agent to downstream tools rather than leaving that inference implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
5 tool updates
v0.1.0- First observed
get_document_outline - First observed
get_form_checks - First observed
get_status - First observed
get_todo - First observed
list_documents
TDQS
Scored across 5 tools
Each tool has a distinct resource and action: workspace counts, document inventory, per-document outline, form-check results, and to-dos. Descriptions explicitly point to sibling tools for adjacent needs, so an agent can tell which tool to use.
All tool names are snake_case verb_noun forms: list_documents, get_status, get_todo, get_document_outline, get_form_checks. The get_/list_ distinction is used consistently for retrieval style.
Five tools are well-scoped for a read-only tax workspace inspection server. Each tool covers a distinct layer of the workspace and none appears redundant.
The surface covers workspace overview, document listing, per-document outline, form-check results, and to-do retrieval. The read-only design is intentional, though there is no direct document-content or search tool for deeper inspection.
Maintenance
Related MCP Connectors
Read-only US tax reference for notices, deadlines, filing screens, and clearly labeled estimates.
Human-owned review for agent plans, rendered documents, Forms, research, and live previews.
Read-only finance and operations controls for AI agents with evidence and safe next actions.
Identity verification, document checks, sanctions/PEP screening and case review as agent tools.
Related MCP Servers
- FlicenseNot gradedqualityNot gradedmaintenanceStreamlines tax professional workflows by automating conversational client intake and generating personalized document checklists based on specific tax situations. It includes tools for calculating case complexity and routing clients to appropriate specialists to optimize appointment throughput.-
- AlicenseAqualityAmaintenanceLets AI assistants work on sensitive files by reasoning over schemas and masked output while deterministic local code handles raw data, never exposing real values to the model.525 PyPIMIT
- AlicenseNot gradedqualityBmaintenanceChecks a US company's federal and state filing obligations and searches the user's own documents for evidence, sending only structured facts so files never leave the machine.3MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to browse and read files from allowlisted folders while names, IDs, contacts, and secrets are replaced with consistent tokens locally before the agent ever sees them. The agent's final answer can then be restored to the original values locally, with no write tools and no file contents leaving the machine.26 npmMIT