statetakehome-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE +21 -0
- package/README.md +163 -0
- package/dist/data/ca-tax-data.json +285 -0
- package/dist/data/state-tax-data.json +2431 -0
- package/dist/index.js +180 -0
- package/dist/lib/ca-data.js +18 -0
- package/dist/lib/state-data.js +22 -0
- package/dist/lib/tax-calc.js +510 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { calculate, selfEmploymentTax, capitalGains } from "./lib/tax-calc.js";
|
|
6
|
+
import { FEDERAL, STATE_ENTRIES } from "./lib/state-data.js";
|
|
7
|
+
const STATE_CODES = STATE_ENTRIES.map((s) => s.abbr);
|
|
8
|
+
const FILING_STATUS = z
|
|
9
|
+
.enum([
|
|
10
|
+
"single",
|
|
11
|
+
"married_filing_jointly",
|
|
12
|
+
"married_filing_separately",
|
|
13
|
+
"head_of_household",
|
|
14
|
+
])
|
|
15
|
+
.describe("IRS filing status.");
|
|
16
|
+
const SOURCE_NOTE = {
|
|
17
|
+
tax_year: FEDERAL.year,
|
|
18
|
+
federal_source: FEDERAL.source,
|
|
19
|
+
federal_verify_url: FEDERAL.verify_url,
|
|
20
|
+
state_source: "Tax Foundation — State Individual Income Tax Rates and Brackets, as of January 1, 2026, cross-checked against state DOR publications.",
|
|
21
|
+
disclaimer: "2026 tax year — these are estimates for planning purposes, not tax advice. Consult a licensed tax professional for your specific situation.",
|
|
22
|
+
};
|
|
23
|
+
const server = new McpServer({
|
|
24
|
+
name: "statetakehome-mcp",
|
|
25
|
+
version: "0.1.0",
|
|
26
|
+
});
|
|
27
|
+
server.registerTool("us_take_home_pay", {
|
|
28
|
+
title: "US Take-Home Pay Calculator",
|
|
29
|
+
description: "Computes net take-home pay for a US W-2 employee: federal income tax, state income tax (all 50 states + DC), FICA (Social Security + Medicare + Additional Medicare), and state-specific extras (e.g. CA SDI, OR transit tax). " +
|
|
30
|
+
"Supports pre-tax 401(k) and health-insurance deductions and per-paycheck breakdowns. " +
|
|
31
|
+
'Example input: { "gross": 75000, "state": "CA", "filingStatus": "single" }. ' +
|
|
32
|
+
"2026 tax year — estimates, not tax advice; brackets from Tax Foundation (Jan 1, 2026) & IRS.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
gross: z.number().positive().describe("Annual gross income in USD, e.g. 75000."),
|
|
35
|
+
state: z
|
|
36
|
+
.enum(STATE_CODES)
|
|
37
|
+
.describe("Two-letter US state or DC postal code, e.g. \"CA\", \"TX\", \"NY\"."),
|
|
38
|
+
filingStatus: FILING_STATUS,
|
|
39
|
+
dependents: z
|
|
40
|
+
.number()
|
|
41
|
+
.int()
|
|
42
|
+
.min(0)
|
|
43
|
+
.optional()
|
|
44
|
+
.describe("Number of dependents claimed (informational; not currently used in the federal/state bracket math)."),
|
|
45
|
+
pretax401kPct: z
|
|
46
|
+
.number()
|
|
47
|
+
.min(0)
|
|
48
|
+
.max(100)
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Pre-tax 401(k) contribution as a percent of gross pay (0-100), capped at the 2026 employee deferral limit ($23,500)."),
|
|
51
|
+
pretaxHealthAnnual: z
|
|
52
|
+
.number()
|
|
53
|
+
.min(0)
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Annual pre-tax health insurance / HSA premiums in USD, excluded from both income tax and FICA wages."),
|
|
56
|
+
payFrequency: z
|
|
57
|
+
.enum(["weekly", "biweekly", "semimonthly", "monthly", "annual"])
|
|
58
|
+
.optional()
|
|
59
|
+
.describe("Pay frequency; the result always includes a full per-paycheck breakdown for all frequencies regardless of this value."),
|
|
60
|
+
},
|
|
61
|
+
}, async (input) => {
|
|
62
|
+
const result = calculate({
|
|
63
|
+
grossIncome: input.gross,
|
|
64
|
+
state: input.state,
|
|
65
|
+
filingStatus: input.filingStatus,
|
|
66
|
+
dependents: input.dependents,
|
|
67
|
+
pretax401kPct: input.pretax401kPct,
|
|
68
|
+
pretaxHealthAnnual: input.pretaxHealthAnnual,
|
|
69
|
+
payFrequency: input.payFrequency,
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
content: [
|
|
73
|
+
{
|
|
74
|
+
type: "text",
|
|
75
|
+
text: JSON.stringify({
|
|
76
|
+
input,
|
|
77
|
+
result,
|
|
78
|
+
source: SOURCE_NOTE,
|
|
79
|
+
reference_url: "https://statetakehome.com/",
|
|
80
|
+
}, null, 2),
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
};
|
|
84
|
+
});
|
|
85
|
+
server.registerTool("self_employment_tax", {
|
|
86
|
+
title: "Self-Employment (1099) Tax Calculator",
|
|
87
|
+
description: "Computes Schedule SE self-employment tax (15.3% = 12.4% Social Security up to the wage base + 2.9% Medicare on 92.35% of net profit), the Additional Medicare surtax, federal income tax, and an optional state income tax, plus a quarterly estimated-payment figure. " +
|
|
88
|
+
'Example input: { "netProfit": 90000, "filingStatus": "single", "state": "TX" }. ' +
|
|
89
|
+
"2026 tax year — estimates, not tax advice; brackets from Tax Foundation (Jan 1, 2026) & IRS.",
|
|
90
|
+
inputSchema: {
|
|
91
|
+
netProfit: z
|
|
92
|
+
.number()
|
|
93
|
+
.positive()
|
|
94
|
+
.describe("Schedule C net profit in USD (1099 income minus business expenses), e.g. 90000."),
|
|
95
|
+
filingStatus: FILING_STATUS,
|
|
96
|
+
state: z
|
|
97
|
+
.enum(STATE_CODES)
|
|
98
|
+
.optional()
|
|
99
|
+
.describe("Optional two-letter US state or DC postal code to also estimate state income tax."),
|
|
100
|
+
otherIncome: z
|
|
101
|
+
.number()
|
|
102
|
+
.min(0)
|
|
103
|
+
.optional()
|
|
104
|
+
.describe("Other ordinary income (e.g. a W-2 job) that stacks on top for bracket and wage-base purposes."),
|
|
105
|
+
},
|
|
106
|
+
}, async (input) => {
|
|
107
|
+
const result = selfEmploymentTax({
|
|
108
|
+
netProfit: input.netProfit,
|
|
109
|
+
filingStatus: input.filingStatus,
|
|
110
|
+
state: input.state,
|
|
111
|
+
otherIncome: input.otherIncome,
|
|
112
|
+
});
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: JSON.stringify({
|
|
118
|
+
input,
|
|
119
|
+
result,
|
|
120
|
+
source: SOURCE_NOTE,
|
|
121
|
+
reference_url: "https://statetakehome.com/self-employment-tax-calculator",
|
|
122
|
+
}, null, 2),
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
server.registerTool("capital_gains_tax", {
|
|
128
|
+
title: "Capital Gains Tax Calculator",
|
|
129
|
+
description: "Computes federal capital gains tax (long-term 0%/15%/20% stacked bands or short-term ordinary rates), the 3.8% Net Investment Income Tax (NIIT) above the MAGI threshold, and an optional state income tax on the gain (states generally tax capital gains as ordinary income). " +
|
|
130
|
+
'Example input: { "gain": 50000, "ordinaryTaxableIncome": 80000, "filingStatus": "single", "term": "long" }. ' +
|
|
131
|
+
"2026 tax year — estimates, not tax advice; thresholds from Rev. Proc. 2025-32, cross-checked against Tax Foundation & Schwab.",
|
|
132
|
+
inputSchema: {
|
|
133
|
+
gain: z.number().positive().describe("Gross capital gain in USD (sale price minus cost basis), e.g. 50000."),
|
|
134
|
+
ordinaryTaxableIncome: z
|
|
135
|
+
.number()
|
|
136
|
+
.min(0)
|
|
137
|
+
.describe("Other taxable income (excluding the gain), which sets the long-term capital gains bracket and NIIT threshold."),
|
|
138
|
+
filingStatus: FILING_STATUS,
|
|
139
|
+
term: z.enum(["long", "short"]).describe("\"long\" for assets held > 1 year (preferential rates), \"short\" for ordinary rates."),
|
|
140
|
+
state: z
|
|
141
|
+
.enum(STATE_CODES)
|
|
142
|
+
.optional()
|
|
143
|
+
.describe("Optional two-letter US state or DC postal code; most states tax the gain as ordinary income."),
|
|
144
|
+
exclusion: z
|
|
145
|
+
.number()
|
|
146
|
+
.min(0)
|
|
147
|
+
.optional()
|
|
148
|
+
.describe("Amount excluded from the gain before tax (e.g. the §121 primary-residence sale exclusion)."),
|
|
149
|
+
},
|
|
150
|
+
}, async (input) => {
|
|
151
|
+
const result = capitalGains({
|
|
152
|
+
gain: input.gain,
|
|
153
|
+
ordinaryTaxableIncome: input.ordinaryTaxableIncome,
|
|
154
|
+
filingStatus: input.filingStatus,
|
|
155
|
+
term: input.term,
|
|
156
|
+
state: input.state,
|
|
157
|
+
exclusion: input.exclusion,
|
|
158
|
+
});
|
|
159
|
+
return {
|
|
160
|
+
content: [
|
|
161
|
+
{
|
|
162
|
+
type: "text",
|
|
163
|
+
text: JSON.stringify({
|
|
164
|
+
input,
|
|
165
|
+
result,
|
|
166
|
+
source: SOURCE_NOTE,
|
|
167
|
+
reference_url: "https://statetakehome.com/capital-gains-tax-calculator",
|
|
168
|
+
}, null, 2),
|
|
169
|
+
},
|
|
170
|
+
],
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
async function main() {
|
|
174
|
+
const transport = new StdioServerTransport();
|
|
175
|
+
await server.connect(transport);
|
|
176
|
+
}
|
|
177
|
+
main().catch((err) => {
|
|
178
|
+
console.error("statetakehome-mcp fatal error:", err);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
// See state-data.ts for why createRequire is used instead of a static ESM
|
|
3
|
+
// JSON import (Node >=18 compatibility, no import-attribute version gate).
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const raw = require("../data/ca-tax-data.json");
|
|
6
|
+
export const caData = raw;
|
|
7
|
+
export const CA_FEDERAL = caData.federal;
|
|
8
|
+
export const PROVINCES = caData.provinces;
|
|
9
|
+
// Display order for the calculator dropdown: by population/relevance, not alphabetical.
|
|
10
|
+
export const PROVINCE_ORDER = [
|
|
11
|
+
"ON", "QC", "BC", "AB", "MB", "SK", "NS", "NB", "NL", "PE", "NT", "YT", "NU",
|
|
12
|
+
];
|
|
13
|
+
export const PROVINCE_ENTRIES = PROVINCE_ORDER
|
|
14
|
+
.map((code) => caData.provinces[code])
|
|
15
|
+
.filter(Boolean);
|
|
16
|
+
export function getProvince(abbr) {
|
|
17
|
+
return caData.provinces[abbr.toUpperCase()];
|
|
18
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
// Loaded via createRequire (not a static ESM JSON import) so this works
|
|
3
|
+
// unmodified on Node >=18 without relying on version-gated `with { type: "json" }`
|
|
4
|
+
// import-attribute support (only stable since Node 20.10 / 18.20).
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const raw = require("../data/state-tax-data.json");
|
|
7
|
+
export const data = raw;
|
|
8
|
+
export const FEDERAL = data.federal;
|
|
9
|
+
export const STATES = data.states;
|
|
10
|
+
export const STATE_ENTRIES = Object.values(data.states).sort((a, b) => a.name.localeCompare(b.name));
|
|
11
|
+
export const REGIONS = {
|
|
12
|
+
Northeast: ["CT", "ME", "MA", "NH", "NJ", "NY", "PA", "RI", "VT"],
|
|
13
|
+
South: [
|
|
14
|
+
"AL", "AR", "DE", "DC", "FL", "GA", "KY", "LA", "MD", "MS",
|
|
15
|
+
"NC", "OK", "SC", "TN", "TX", "VA", "WV",
|
|
16
|
+
],
|
|
17
|
+
Midwest: ["IL", "IN", "IA", "KS", "MI", "MN", "MO", "NE", "ND", "OH", "SD", "WI"],
|
|
18
|
+
West: ["AK", "AZ", "CA", "CO", "HI", "ID", "MT", "NV", "NM", "OR", "UT", "WA", "WY"],
|
|
19
|
+
};
|
|
20
|
+
export function getState(abbr) {
|
|
21
|
+
return data.states[abbr.toUpperCase()];
|
|
22
|
+
}
|