Skip to main content
Glama
dma9527

irs-taxpayer-mcp

by dma9527

analyze_paycheck

Verify paycheck withholding accuracy by comparing your pay stub numbers to expected tax calculations. Input your earnings and deductions to check if your employer is withholding the correct federal and state tax amounts.

Instructions

Analyze a paycheck to verify withholding accuracy. Input your pay stub numbers and see if your employer is withholding the right amount.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
taxYearYesTax year
filingStatusYes
payFrequencyYes
grossPayYesGross pay this period
federalWithheldYesFederal tax withheld this period
stateWithheldNoState tax withheld this period
socialSecurityWithheldNoSocial Security withheld
medicareWithheldNoMedicare withheld
retirement401kNo401k/403b pre-tax contribution this period
hsaContributionNoHSA contribution this period
stateCodeNo

Implementation Reference

  • The complete implementation of the analyze_paycheck tool. It takes paycheck input (pay frequency, gross pay, withholding amounts, 401k/HSA contributions), annualizes the values, calculates expected federal tax using calculateTax(), computes expected FICA (Social Security and Medicare), and generates a detailed analysis report comparing actual vs expected withholding with recommendations for adjustments.
    server.tool(
      "analyze_paycheck",
      "Analyze a paycheck to verify withholding accuracy. " +
      "Input your pay stub numbers and see if your employer is withholding the right amount.",
      {
        taxYear: z.number().describe("Tax year"),
        filingStatus: FilingStatusEnum,
        payFrequency: z.enum(["weekly", "biweekly", "semimonthly", "monthly"]),
        grossPay: z.number().min(0).describe("Gross pay this period"),
        federalWithheld: z.number().min(0).describe("Federal tax withheld this period"),
        stateWithheld: z.number().min(0).optional().describe("State tax withheld this period"),
        socialSecurityWithheld: z.number().min(0).optional().describe("Social Security withheld"),
        medicareWithheld: z.number().min(0).optional().describe("Medicare withheld"),
        retirement401k: z.number().min(0).optional().describe("401k/403b pre-tax contribution this period"),
        hsaContribution: z.number().min(0).optional().describe("HSA contribution this period"),
        stateCode: z.string().length(2).optional(),
      },
      async (params) => {
        const periods: Record<string, number> = { weekly: 52, biweekly: 26, semimonthly: 24, monthly: 12 };
        const periodsPerYear = periods[params.payFrequency];
        const annualGross = params.grossPay * periodsPerYear;
        const annual401k = (params.retirement401k ?? 0) * periodsPerYear;
        const annualHSA = (params.hsaContribution ?? 0) * periodsPerYear;
        const annualTaxableIncome = annualGross - annual401k - annualHSA;
    
        // Expected federal tax
        const expected = calculateTax({
          taxYear: params.taxYear,
          filingStatus: params.filingStatus,
          grossIncome: annualTaxableIncome,
          w2Income: annualTaxableIncome,
        });
    
        const expectedPerPeriod = Math.round(expected.totalFederalTax / periodsPerYear);
        const actualFederal = params.federalWithheld;
        const federalDiff = actualFederal - expectedPerPeriod;
    
        // Expected FICA
        const expectedSS = Math.round(Math.min(annualGross, 168600) * 0.062 / periodsPerYear);
        const expectedMedicare = Math.round(annualGross * 0.0145 / periodsPerYear);
        const actualSS = params.socialSecurityWithheld ?? 0;
        const actualMedicare = params.medicareWithheld ?? 0;
    
        const lines = [
          `## 💰 Paycheck Analysis — TY${params.taxYear}`,
          `**${params.payFrequency}** pay (${periodsPerYear} periods/year) | **${params.filingStatus.replace(/_/g, " ")}**`,
          "",
          `### This Paycheck`,
          `| Item | Actual | Expected | Diff |`,
          `|------|--------|----------|------|`,
          `| Gross Pay | $${fmt(params.grossPay)} | — | — |`,
          `| Federal Tax | $${fmt(actualFederal)} | ~$${fmt(expectedPerPeriod)} | ${federalDiff >= 0 ? "+" : ""}$${fmt(federalDiff)} |`,
          actualSS > 0 ? `| Social Security | $${fmt(actualSS)} | ~$${fmt(expectedSS)} | ${actualSS - expectedSS >= 0 ? "+" : ""}$${fmt(actualSS - expectedSS)} |` : "",
          actualMedicare > 0 ? `| Medicare | $${fmt(actualMedicare)} | ~$${fmt(expectedMedicare)} | ${actualMedicare - expectedMedicare >= 0 ? "+" : ""}$${fmt(actualMedicare - expectedMedicare)} |` : "",
          params.stateWithheld ? `| State Tax | $${fmt(params.stateWithheld)} | — | — |` : "",
          params.retirement401k ? `| 401k/403b | $${fmt(params.retirement401k)} | — | pre-tax |` : "",
          params.hsaContribution ? `| HSA | $${fmt(params.hsaContribution)} | — | pre-tax |` : "",
          "",
          `### Annual Projection`,
          `| Item | Amount |`,
          `|------|--------|`,
          `| Annual Gross | $${fmt(annualGross)} |`,
          annual401k > 0 ? `| 401k Contributions | -$${fmt(annual401k)} |` : "",
          annualHSA > 0 ? `| HSA Contributions | -$${fmt(annualHSA)} |` : "",
          `| Taxable Income | $${fmt(annualTaxableIncome)} |`,
          `| Expected Annual Federal Tax | $${fmt(expected.totalFederalTax)} |`,
          `| Annual Federal Withheld (projected) | $${fmt(actualFederal * periodsPerYear)} |`,
          "",
        ];
    
        const annualFederalWithheld = actualFederal * periodsPerYear;
        const annualDiff = annualFederalWithheld - expected.totalFederalTax;
    
        if (Math.abs(annualDiff) < 500) {
          lines.push("✅ **Withholding looks accurate.** You're within $500 of your expected tax.");
        } else if (annualDiff > 0) {
          lines.push(
            `⚠️ **Over-withholding by ~$${fmt(Math.round(annualDiff))}/year.** You'll likely get a refund.`,
            `Consider reducing W-4 withholding to keep ~$${fmt(Math.round(annualDiff / periodsPerYear))} more per paycheck.`,
          );
        } else {
          lines.push(
            `⚠️ **Under-withholding by ~$${fmt(Math.round(Math.abs(annualDiff)))}/year.** You may owe at tax time.`,
            `Consider increasing W-4 withholding by ~$${fmt(Math.round(Math.abs(annualDiff) / periodsPerYear))} per paycheck.`,
          );
        }
    
        lines.push("", `> ⚠️ Simplified estimate. Actual withholding depends on W-4 settings, pre-tax benefits, and other factors.`);
    
        return { content: [{ type: "text", text: lines.filter(Boolean).join("\n") }] };
      }
    );
  • Zod schema defining the input validation for analyze_paycheck. Includes taxYear, filingStatus (enum), payFrequency (enum: weekly/biweekly/semimonthly/monthly), grossPay, federalWithheld, optional stateWithheld, socialSecurityWithheld, medicareWithheld, retirement401k, hsaContribution, and stateCode.
    {
      taxYear: z.number().describe("Tax year"),
      filingStatus: FilingStatusEnum,
      payFrequency: z.enum(["weekly", "biweekly", "semimonthly", "monthly"]),
      grossPay: z.number().min(0).describe("Gross pay this period"),
      federalWithheld: z.number().min(0).describe("Federal tax withheld this period"),
      stateWithheld: z.number().min(0).optional().describe("State tax withheld this period"),
      socialSecurityWithheld: z.number().min(0).optional().describe("Social Security withheld"),
      medicareWithheld: z.number().min(0).optional().describe("Medicare withheld"),
      retirement401k: z.number().min(0).optional().describe("401k/403b pre-tax contribution this period"),
      hsaContribution: z.number().min(0).optional().describe("HSA contribution this period"),
      stateCode: z.string().length(2).optional(),
    },
  • src/index.ts:28-49 (registration)
    Registration of the comprehensive tools module which includes analyze_paycheck. Line 28 imports registerComprehensiveTools from ./tools/comprehensive-tools.js, and line 49 calls it to register 6 tools including 'paycheck' (analyze_paycheck).
    import { registerComprehensiveTools } from "./tools/comprehensive-tools.js";
    import { registerAdvancedTools } from "./tools/advanced-tools.js";
    import { registerSmartTools } from "./tools/smart-tools.js";
    import http from "node:http";
    
    const server = new McpServer({
      name: "irs-taxpayer-mcp",
      version: "0.5.3",
      description:
        "Tax calculation, credits, deductions, state taxes, and retirement strategy tools for individual US taxpayers. " +
        "All financial calculations run locally — your income data never leaves your machine.",
    });
    
    // Register all tool groups
    registerTaxCalculationTools(server);   // 6 tools: calculate, brackets, compare, quarterly, total, w4
    registerDeductionTools(server);        // 2 tools: list deductions, standard vs itemized
    registerIrsLookupTools(server);        // 3 tools: deadlines, refund status, form info
    registerCreditTools(server);           // 5 tools: list credits, eligibility, retirement accounts, strategies, EITC
    registerStateTaxTools(server);         // 4 tools: state info, estimate, compare states, no-tax states
    registerPlanningTools(server);         // 6 tools: planning tips, year compare, SE tax, mortgage, education, MFJ vs MFS
    registerObbbTools(server);             // 2 tools: OBBB deductions calculator, what changed between years
    registerComprehensiveTools(server);    // 6 tools: report, 1099, calendar, paycheck, scenario, audit
  • FilingStatusEnum - a reusable Zod enum defining the valid filing statuses (single, married_filing_jointly, married_filing_separately, head_of_household) used by analyze_paycheck and other tools.
    export const FilingStatusEnum = z.enum([
      "single",
      "married_filing_jointly",
      "married_filing_separately",
      "head_of_household",
    ]);
  • The calculateTax function used by analyze_paycheck to compute expected federal tax. Takes TaxInput including taxYear, filingStatus, and gross income, returns TaxBreakdown with totalFederalTax and other details. This is the core tax calculation engine that the paycheck analyzer depends on.
    export function calculateTax(input: TaxInput): TaxBreakdown {
      // Input validation
      const errors = validate(
        validateTaxYear(input.taxYear),
        validateIncome(input.grossIncome, "grossIncome"),
      );
      if (errors.length > 0) {
        throw new Error(formatValidationErrors(errors));
      }
    
      const taxData = getTaxYearData(input.taxYear);
      if (!taxData) {
        throw new Error(`Tax year ${input.taxYear} is not supported. Supported years: 2024, 2025`);
      }
    
      // Step 1: Calculate AGI
      const aboveTheLine = input.aboveTheLineDeductions ?? 0;
      const w2 = input.w2Income ?? 0;
      const seDeduction = input.selfEmploymentIncome
        ? calculateSelfEmploymentTax(input.selfEmploymentIncome, taxData, w2) * 0.5
        : 0;
      const agi = input.grossIncome - aboveTheLine - seDeduction;
    
      // Step 2: Determine deduction (standard vs itemized)
      let standardDeduction = taxData.standardDeduction[input.filingStatus];
    
      // Additional deduction for age 65+ or blind
      const additionalAmount = taxData.additionalDeduction.age65OrBlind[input.filingStatus];
      if (input.age65OrOlder) standardDeduction += additionalAmount;
      if (input.blind) standardDeduction += additionalAmount;
      if (input.spouseAge65OrOlder) standardDeduction += additionalAmount;
      if (input.spouseBlind) standardDeduction += additionalAmount;
    
      const itemized = input.itemizedDeductions ?? 0;
      const useItemized = itemized > standardDeduction;
      const deductionAmount = useItemized ? itemized : standardDeduction;
    
      // Step 3: Calculate taxable income
      const longTermGains = (input.capitalGainsLongTerm !== false ? (input.capitalGains ?? 0) : 0);
      const shortTermGains = input.shortTermCapitalGains ?? (input.capitalGainsLongTerm === false ? (input.capitalGains ?? 0) : 0);
      const ordinaryIncome = input.grossIncome - longTermGains;
      const taxableOrdinaryIncome = Math.max(0, ordinaryIncome - aboveTheLine - seDeduction - deductionAmount);
    
      // Step 4: QBI deduction
      const qbi = input.qualifiedBusinessIncome ?? 0;
      const taxableBeforeQBI = taxableOrdinaryIncome + longTermGains;
      const qbiDeduction = calculateQBIDeduction(qbi, taxableBeforeQBI, taxData);
    
      const adjustedTaxableOrdinary = Math.max(0, taxableOrdinaryIncome - qbiDeduction);
    
      // Step 5: Calculate ordinary income tax
      const { breakdown, total: ordinaryTax, marginalRate } = calculateBracketTax(
        adjustedTaxableOrdinary,
        taxData.brackets[input.filingStatus]
      );
    
      // Step 6: Capital gains tax (long-term only)
      const cgTax = longTermGains > 0
        ? calculateCapitalGainsTax(longTermGains, adjustedTaxableOrdinary, input.filingStatus, taxData)
        : 0;
    
      // Step 7: Self-employment tax
      const seTax = input.selfEmploymentIncome
        ? calculateSelfEmploymentTax(input.selfEmploymentIncome, taxData, w2)
        : 0;
    
      // Step 8: NIIT (3.8% on investment income above threshold)
      const investmentIncome = longTermGains + shortTermGains;
      const niit = calculateNIIT(agi, investmentIncome, input.filingStatus);
    
      // Step 9: Additional Medicare Tax (0.9% on earned income above threshold)
      const earnedIncome = (input.w2Income ?? 0) + (input.selfEmploymentIncome ?? 0);
      const additionalMedicareTax = calculateAdditionalMedicareTax(earnedIncome, input.filingStatus, taxData);
    
      // Step 10: Child Tax Credit
      const dependents = input.dependents ?? 0;
      let childCredit = dependents * taxData.childTaxCredit.amount;
      const phaseoutStart = taxData.childTaxCredit.phaseoutStart[input.filingStatus];
      if (agi > phaseoutStart) {
        const excess = Math.ceil((agi - phaseoutStart) / 1000) * taxData.childTaxCredit.phaseoutRate;
        childCredit = Math.max(0, childCredit - excess);
      }
    
      const totalTaxBeforeAMT = Math.max(0, ordinaryTax + cgTax + seTax + niit + additionalMedicareTax - childCredit);
    
      // Step 11: AMT
      const isoSpread = input.isoExerciseSpread ?? 0;
      const saltDeducted = useItemized ? (input.stateTaxDeducted ?? 0) : 0;
      const regularIncomeTax = ordinaryTax + cgTax;
      const amt = calculateAMT(regularIncomeTax, taxableOrdinaryIncome + longTermGains, input.filingStatus, taxData, isoSpread, saltDeducted);
    
      const totalTax = totalTaxBeforeAMT + amt;
      const taxableIncome = adjustedTaxableOrdinary + longTermGains;
    
      return {
        taxYear: input.taxYear,
        filingStatus: input.filingStatus,
        grossIncome: input.grossIncome,
        adjustedGrossIncome: agi,
        deductionType: useItemized ? "itemized" : "standard",
        deductionAmount,
        taxableIncome,
        bracketBreakdown: breakdown,
        ordinaryIncomeTax: ordinaryTax,
        capitalGainsTax: cgTax,
        selfEmploymentTax: seTax,
        niit,
        additionalMedicareTax,
        qbiDeduction,
        amt,
        totalFederalTax: totalTax,
        effectiveRate: input.grossIncome > 0 ? totalTax / input.grossIncome : 0,
        marginalRate,
        childTaxCredit: childCredit,
        estimatedQuarterlyPayment: Math.ceil(totalTax / 4),
      };
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions the tool analyzes for accuracy but doesn't specify whether it performs calculations, returns a boolean result, provides explanations, or handles edge cases like multiple states. This leaves significant gaps in understanding how the tool behaves beyond its basic purpose.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two concise sentences that front-load the core purpose ('analyze a paycheck to verify withholding accuracy') and follow with input/output context. Every word contributes directly to understanding the tool's function without redundancy or fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 11 parameters, no annotations, and no output schema, the description is minimally adequate. It covers the purpose and input context but lacks details on behavioral traits, output format, error handling, or limitations given the complexity. It meets basic requirements but leaves gaps in fully contextualizing the tool's operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 73%, providing good documentation for most parameters. The description adds minimal value beyond the schema by implying that inputs are 'pay stub numbers' and the tool verifies 'withholding accuracy,' but it doesn't clarify parameter relationships or usage nuances. This meets the baseline for adequate coverage without significant enhancement.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the specific action ('analyze a paycheck to verify withholding accuracy') and the resource ('paycheck'/'pay stub numbers'), distinguishing it from sibling tools that focus on tax calculations, benefits, or planning rather than paycheck verification. It precisely communicates the tool's function without being tautological.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives like 'calculate_w4_withholding' or 'calculate_federal_tax', nor does it mention prerequisites or exclusions. It implies usage for paycheck verification but lacks explicit context for tool selection among the many tax-related siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dma9527/irs-taxpayer-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server