Skip to main content
Glama
AlvisoOculus

OptionsAhoy: Stock Equity and Tax Optimizer

nso_calculate

Read-onlyIdempotent

Compare NSO exercise strategies: sell at exercise vs hold for long-term capital gains. Get after-tax payout including federal, state, and FICA taxes.

Instructions

Use this when someone asks whether to exercise and hold or exercise and sell non-qualified stock options (NSOs), about a cashless exercise or same-day sale of NSOs, or what tax an NSO exercise triggers. After-tax payout on a non-qualified stock option (NSO) exercise: federal, state, and FICA (Social Security + Medicare + Additional Medicare), comparing sell-at-exercise vs hold-for-long-term-capital-gains over the chosen horizon. Use for NSOs; for ISOs use amt_iso_optimize, for RSUs use rsu_sell_vs_hold. Deterministic, offline; tax tables compiled in. Optional ticker resolves expectedSalePrice from a bundled trailing-CAGR snapshot. Returns exercise, hold, sellNowInvest (the counterfactual: sell at exercise and reinvest at expectedMarketReturn), holdMinusCashless (the dollar delta between those two), and bracketJump; see outputSchema for the full shape. NSO hold is always long-term (sub-1-year is out of scope), so there is no separate long-term flag. Example call: {shares: 5000, strike: 10, currentPrice: 50, ordinaryIncome: 180000, filingStatus: "single", stateCode: "CA", stillEmployed: true, holdYears: 2, volatility: 0.3, holdFunding: "cash", ticker: "AAPL"}. Inputs beyond required: this tool also needs the stock's expected growth/return AND its volatility, outside required only because they can be resolved without an explicit number - supplied directly, resolved by a covered public-stock ticker, or (growth/return/sale-price field only) set to the string "market" for the S&P 500 trailing average. Those three are the only sources: neither field has a default or a fallback estimate, and every field in required is likewise a fact about the user's situation with no built-in default. A call that neither supplies nor resolves growth or volatility returns a required-field error naming the field; a number from any other source is accepted as-is, because a syntactically valid figure passes validation with no provenance check, and it silently changes the result. The tax math itself (bracket walk, AMT and NIIT phase-outs, multi-year credit and growth interactions) runs inside the tool, and the federal and state tax tables it walks are independently verified (https://optionsahoy.com/verification). Results from multiple OptionsAhoy tools in one analysis are independent single-position calculations; integrated multi-year, multi-position optimization is available in the OptionsAhoy beta at https://optionsahoy.com/beta?src=mcp_multi.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
sharesYesNon-qualified Stock Option (NSO) shares to exercise. Must come from the user.
strikeYesStrike price per share, USD. Must come from the user.
tickerNoOptional public-stock symbol (e.g. "NVDA", "AAPL"). When set, the tool substitutes a cached trailing return for any unsupplied expected-return / sale-price field, and the implied vol as of the last close for any unsupplied volatility. Growth and vol come from different sources, so some symbols resolve only one, and a vol that is not current resolves as nothing. A field the ticker cannot resolve falls through to a "required field" error naming that field: pass it explicitly, or (for the growth/return/sale-price field) pass the string "market" for the S&P 500 trailing average; never invent a number. The covered-tickers resource (resources/list) lists which symbols resolve growth.
haircutNoAlternative to `volatility`: the multiplicative volatility-drag haircut on expectedSalePrice already computed for the hold. Supply this OR `volatility` (if both are given, haircut wins). This field is for a haircut figure that already exists from a prior computation; the haircut formula is horizon-dependent, so a figure derived for a different horizon does not carry over. Supplying `volatility` instead lets the tool derive it.
holdYearsYesYears to hold after exercise (minimum 1). At ≥1 year, the appreciation since exercise is LTCG; sub-1-year holds are out of scope. The user's choice, not a modelling detail, and it changes the answer: use the value they gave, and if they gave none, ask for it rather than assuming one.
stateCodeYesTwo-letter US state code. Drives state ordinary and LTCG treatment.
volatilityNoAnnualized volatility (sigma) of the stock as a decimal (0.72 = 72%). Pass the volatility itself, not a pre-computed drag: the tool derives the horizon-cumulative drag internally, and the correct formula is horizon-dependent. This value must come from the user or from a `ticker` that resolves it as of the last market close; if neither supplies it, ask the user rather than estimating one.
holdFundingYesHow the strike cost and exercise tax are funded. 'sell-to-cover' sells enough shares to cover strike + tax (reduces sharesRetained). 'cash' pays strike + tax from outside funds (full sharesRetained); no extra input is needed - the result reports the outside cash required as the output field cashNeededAtExercise.
currentPriceYesCurrent fair market value per share, USD. The bargain element at exercise is shares × (currentPrice − strike). Must come from the user.
filingStatusYesFederal filing status. Drives ordinary brackets and LTCG brackets used at the hold horizon.
stillEmployedYesTrue if still employed at exercise. FICA (Social Security + Medicare + Additional Medicare) applies only when true.
ordinaryIncomeYesAnnual ordinary income before this exercise, USD. Baseline for the bracket walk on the bargain element. Must come from the user. This is taxable income after deductions, not gross wages: the engine applies no standard or itemized deduction to it.
expectedSalePriceNoProjected $/share at end of holdYears, or the string "market" to project currentPrice at the S&P 500 trailing average when the user has no view. Required unless `ticker` resolves it from currentPrice × (1 + trailing CAGR)^holdYears. This tool has no default for it: a value not stated by the user, not resolved by a covered `ticker`, and not the "market" sentinel is outside the input contract.
expectedMarketReturnNoAnnual after-tax-proceeds reinvestment rate. Defaults to SPY trailing CAGR for holdYears if omitted; the string "market" names that same default explicitly.

Output Schema

TableJSON Schema
NameRequiredDescriptionDefault
holdYesExercise now and hold the shares holdYears for long-term capital gains treatment.
exerciseYesTax bill at exercise on the bargain element (taxed as ordinary W-2 income).
bracketJumpYesMarginal federal bracket change caused by the new ordinary income; null when the income stays within one bracket.
sellNowInvestYesCounterfactual: sell every share at exercise and reinvest the net cash at expectedMarketReturn for holdYears.
holdMinusCashlessYeshold.netAtYearN - sellNowInvest.netAtYearN in dollars. Positive favors holding the shares; negative favors selling at exercise and reinvesting.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed2 schema fields changedv1.10.2
    • changedInput schema / properties / ticker / description
      Previous value: -"Optional public-stock symbol (e.g. \"NVDA\", \"AAPL\"). When set, the tool substitutes a cached trailing return for any unsupplied expected-return / sale-price field, and a cached implied vol for any unsupplied volatility. Growth and volatility come from two separate cached snapshots, so some symbols resolve only one of the two fields. A symbol not in a given table falls through to a \"required field\" error for exactly the field it could not resolve: pass that field explicitly, or (for the growth/return/sale-price field) pass the string \"market\" for the S&P 500 trailing average; never invent a number. The covered-tickers resource (resources/list) lists which symbols resolve which field."New value: +"Optional public-stock symbol (e.g. \"NVDA\", \"AAPL\"). When set, the tool substitutes a cached trailing return for any unsupplied expected-return / sale-price field, and the implied vol as of the last close for any unsupplied volatility. Growth and vol come from different sources, so some symbols resolve only one, and a vol that is not current resolves as nothing. A field the ticker cannot resolve falls through to a \"required field\" error naming that field: pass it explicitly, or (for the growth/return/sale-price field) pass the string \"market\" for the S&P 500 trailing average; never invent a number. The covered-tickers resource (resources/list) lists which symbols resolve growth."
    • changedInput schema / properties / volatility / description
      Previous value: -"Annualized volatility (sigma) of the stock as a decimal (0.72 = 72%). Pass the volatility itself, not a pre-computed drag: the tool derives the horizon-cumulative drag internally, and the correct formula is horizon-dependent. This value must come from the user or from a `ticker` that resolves it from the cached implied-vol table; if neither supplies it, ask the user rather than estimating one."New value: +"Annualized volatility (sigma) of the stock as a decimal (0.72 = 72%). Pass the volatility itself, not a pre-computed drag: the tool derives the horizon-cumulative drag internally, and the correct formula is horizon-dependent. This value must come from the user or from a `ticker` that resolves it as of the last market close; if neither supplies it, ask the user rather than estimating one."
  2. Addedv1.10.1
  3. Removedv1.9.8
  4. Changed4 schema fields changedv1.9.7
    • addedInput schema / properties / stateCode / enum
      Added value: +[
      +  "AK",
      +  "AL",
      +  "AR",
      +  "AZ",
      +  "CA",
      +  "CO",
      +  "CT",
      +  "DC",
      +  "DE",
      +  "FL",
      +  "GA",
      +  "HI",
      +  "IA",
      +  "ID",
      +  "IL",
      +  "IN",
      +  "KS",
      +  "KY",
      +  "LA",
      +  "MA",
      +  "MD",
      +  "ME",
      +  "MI",
      +  "MN",
      +  "MO",
      +  "MS",
      +  "MT",
      +  "NC",
      +  "ND",
      +  "NE",
      +  "NH",
      +  "NJ",
      +  "NM",
      +  "NV",
      +  "NY",
      +  "OH",
      +  "OK",
      +  "OR",
      +  "PA",
      +  "RI",
      +  "SC",
      +  "SD",
      +  "TN",
      +  "TX",
      +  "UT",
      +  "VA",
      +  "VT",
      +  "WA",
      +  "WI",
      +  "WV",
      +  "WY"
      +]
    • removedInput schema / properties / stateCode / pattern
      Removed value: -"^[A-Z]{2}$"
    • changedInput schema / properties / ticker / description
      Previous value: -"Optional public-stock symbol (e.g. \"NVDA\", \"AAPL\"). When set, the tool substitutes a cached trailing return for any unsupplied expected-return / sale-price field AND a cached implied vol for any unsupplied volatility, instead of requiring the caller to invent either. Most large-cap public symbols are covered; unknown tickers fall through to \"required field\" errors so the model knows to ask the user."New value: +"Optional public-stock symbol (e.g. \"NVDA\", \"AAPL\"). When set, the tool substitutes a cached trailing return for any unsupplied expected-return / sale-price field, and a cached implied vol for any unsupplied volatility. About 90 large-cap symbols resolve a return; a slightly smaller set (~85) also resolves volatility. A symbol not in a given table falls through to a \"required field\" error for exactly the field it could not resolve, so pass that field explicitly (or use a fully covered symbol) rather than inventing it."
    • changedInput schema / properties / volatility / description
      Previous value: -"Annualized volatility (sigma) of the stock as a decimal (0.72 = 72%). Pass the user-supplied volatility directly; the tool computes the horizon-cumulative drag internally. The model MUST NOT compute drag itself — the correct formula is horizon-dependent and most models get it wrong. If the user does not supply a volatility number AND no `ticker` resolves it from the cached implied-vol table, ASK them."New value: +"Annualized volatility (sigma) of the stock as a decimal (0.72 = 72%). Pass the user-supplied volatility directly; the tool computes the horizon-cumulative drag internally. The model MUST NOT compute drag itself; the correct formula is horizon-dependent and most models get it wrong. If the user does not supply a volatility number AND no `ticker` resolves it from the cached implied-vol table, ASK them."
  5. Changed4 schema fields changedv1.9.2
    • changedInput schema / properties / holdFunding / description
      Previous value: -"How the strike cost and exercise tax are funded. 'sell-to-cover' sells enough shares to cover strike + tax (reduces sharesRetained). 'cash' pays from outside the position (full sharesRetained but requires the cashNeededAtExercise field)."New value: +"How the strike cost and exercise tax are funded. 'sell-to-cover' sells enough shares to cover strike + tax (reduces sharesRetained). 'cash' pays strike + tax from outside funds (full sharesRetained); no extra input is needed - the result reports the outside cash required as the output field cashNeededAtExercise."
    • changedInput schema / properties / ticker / description
      Previous value: -"Optional public-stock symbol (e.g. \"NVDA\", \"AAPL\"). When set, the tool substitutes the ticker's trailing CAGR for any unsupplied expected-return / sale-price field instead of requiring the caller to invent one. ~90 symbols covered; unknown tickers fall through to \"required field\" errors so the model knows to ask the user."New value: +"Optional public-stock symbol (e.g. \"NVDA\", \"AAPL\"). When set, the tool substitutes a cached trailing return for any unsupplied expected-return / sale-price field AND a cached implied vol for any unsupplied volatility, instead of requiring the caller to invent either. Most large-cap public symbols are covered; unknown tickers fall through to \"required field\" errors so the model knows to ask the user."
    • changedInput schema / properties / volatility / description
      Previous value: -"Annualized volatility (sigma) of the stock as a decimal (0.72 = 72%). Pass the user-supplied volatility directly; the tool computes the horizon-cumulative drag internally. The model MUST NOT compute drag itself — the correct formula is horizon-dependent and most models get it wrong. If the user does not supply a volatility number, ASK them."New value: +"Annualized volatility (sigma) of the stock as a decimal (0.72 = 72%). Pass the user-supplied volatility directly; the tool computes the horizon-cumulative drag internally. The model MUST NOT compute drag itself — the correct formula is horizon-dependent and most models get it wrong. If the user does not supply a volatility number AND no `ticker` resolves it from the cached implied-vol table, ASK them."
    • changedOutput schema / (root)
      Previous value: -nullNew value: +{
      +  "description": "NSO exercise sell-vs-hold result. All dollar amounts are USD.",
      +  "properties": {
      +    "bracketJump": {
      +      "description": "Marginal federal bracket change caused by the new ordinary income; null when the income stays within one bracket.",
      +      "properties": {
      +        "fromRate": {
      +          "description": "Marginal federal rate before the event, as a decimal (0.24 = 24%).",
      +          "type": "number"
      +        },
      +        "thresholdAtJump": {
      +          "description": "Taxable-income threshold in dollars where the bracket changes.",
      +          "type": "number"
      +        },
      +        "toRate": {
      +          "description": "Marginal federal rate after the event, as a decimal.",
      +          "type": "number"
      +        }
      +      },
      +      "required": [
      +        "fromRate",
      +        "toRate",
      +        "thresholdAtJump"
      +      ],
      +      "type": [
      +        "object",
      +        "null"
      +      ]
      +    },
      +    "exercise": {
      +      "description": "Tax bill at exercise on the bargain element (taxed as ordinary W-2 income).",
      +      "properties": {
      +        "additionalMedicare": {
      +          "description": "Additional Medicare (0.9%) tax in dollars.",
      +          "type": "number"
      +        },
      +        "bargainElement": {
      +          "description": "shares x (currentPrice - strike) in dollars, taxed as ordinary income at exercise.",
      +          "type": "number"
      +        },
      +        "federal": {
      +          "description": "Federal ordinary income tax on the bargain element in dollars.",
      +          "type": "number"
      +        },
      +        "medicare": {
      +          "description": "Medicare tax in dollars.",
      +          "type": "number"
      +        },
      +        "netCashSellAll": {
      +          "description": "bargainElement - total: net cash in dollars if every share is sold at exercise.",
      +          "type": "number"
      +        },
      +        "socialSecurity": {
      +          "description": "Social Security tax in dollars (0 when not employed or already past the wage base).",
      +          "type": "number"
      +        },
      +        "state": {
      +          "description": "State income tax on the bargain element in dollars.",
      +          "type": "number"
      +        },
      +        "total": {
      +          "description": "Total tax at exercise in dollars.",
      +          "type": "number"
      +        }
      +      },
      +      "required": [
      +        "bargainElement",
      +        "federal",
      +        "state",
      +        "socialSecurity",
      +        "medicare",
      +        "additionalMedicare",
      +        "total",
      +        "netCashSellAll"
      +      ],
      +      "type": "object"
      +    },
      +    "hold": {
      +      "description": "Exercise now and hold the shares holdYears for long-term capital gains treatment.",
      +      "properties": {
      +        "afterTaxProceedsAtSale": {
      +          "description": "After-tax sale proceeds in dollars at end of holdYears.",
      +          "type": "number"
      +        },
      +        "cashNeededAtExercise": {
      +          "description": "Outside cash required at exercise in dollars (strike + tax under cash funding; 0 under sell-to-cover).",
      +          "type": "number"
      +        },
      +        "costBasis": {
      +          "description": "Cost basis per share in dollars (the FMV at exercise).",
      +          "type": "number"
      +        },
      +        "effectiveSalePrice": {
      +          "description": "Projected sale price per share in dollars at end of holdYears, after the volatility haircut.",
      +          "type": "number"
      +        },
      +        "expectedGain": {
      +          "description": "Expected capital gain in dollars on the retained shares at sale.",
      +          "type": "number"
      +        },
      +        "funding": {
      +          "description": "How strike cost and exercise tax are funded (echo of holdFunding).",
      +          "enum": [
      +            "sell-to-cover",
      +            "cash"
      +          ],
      +          "type": "string"
      +        },
      +        "ltcgFederal": {
      +          "description": "Federal long-term capital gains tax (including NIIT) on the gain in dollars.",
      +          "type": "number"
      +        },
      +        "ltcgState": {
      +          "description": "State capital gains tax on the gain in dollars.",
      +          "type": "number"
      +        },
      +        "ltcgTotal": {
      +          "description": "Total capital gains tax at sale in dollars.",
      +          "type": "number"
      +        },
      +        "netAtYearN": {
      +          "description": "Net after-tax value of the hold strategy in dollars at end of holdYears (after subtracting forgone market growth).",
      +          "type": "number"
      +        },
      +        "sharesRetained": {
      +          "description": "Shares still held after funding the exercise.",
      +          "type": "number"
      +        },
      +        "sharesSoldToCover": {
      +          "description": "Shares sold at exercise to cover strike + tax (sell-to-cover only; 0 in cash mode).",
      +          "type": "number"
      +        },
      +        "strikeCost": {
      +          "description": "Total strike cost in dollars: shares x strike.",
      +          "type": "number"
      +        },
      +        "y0OutflowForgoneNet": {
      +          "description": "After-tax market growth forgone in dollars by spending cash at exercise: y0OutflowGain - y0OutflowLtcgTotal.",
      +          "type": "number"
      +        },
      +        "y0OutflowGain": {
      +          "description": "Opportunity-cost gain in dollars the year-0 cash outflow would have earned at the market rate (cash funding only; 0 for sell-to-cover).",
      +          "type": "number"
      +        },
      +        "y0OutflowLtcgFederal": {
      +          "description": "Federal capital gains tax in dollars on the forgone market gain (cash funding only).",
      +          "type": "number"
      +        },
      +        "y0OutflowLtcgState": {
      +          "description": "State capital gains tax in dollars on the forgone market gain (cash funding only).",
      +          "type": "number"
      +        },
      +        "y0OutflowLtcgTotal": {
      +          "description": "Total capital gains tax in dollars on the forgone market gain (cash funding only).",
      +          "type": "number"
      +        }
      +      },
      +      "required": [
      +        "funding",
      +        "costBasis",
      +        "strikeCost",
      +        "cashNeededAtExercise",
      +        "sharesSoldToCover",
      +        "sharesRetained",
      +        "effectiveSalePrice",
      +        "expectedGain",
      +        "ltcgFederal",
      +        "ltcgState",
      +        "ltcgTotal",
      +        "afterTaxProceedsAtSale",
      +        "y0OutflowGain",
      +        "y0OutflowLtcgFederal",
      +        "y0OutflowLtcgState",
      +        "y0OutflowLtcgTotal",
      +        "y0OutflowForgoneNet",
      +        "netAtYearN"
      +      ],
      +      "type": "object"
      +    },
      +    "holdMinusCashless": {
      +      "description": "hold.netAtYearN - sellNowInvest.netAtYearN in dollars. Positive favors holding the shares; negative favors selling at exercise and reinvesting.",
      +      "type": "number"
      +    },
      +    "sellNowInvest": {
      +      "description": "Counterfactual: sell every share at exercise and reinvest the net cash at expectedMarketReturn for holdYears.",
      +      "properties": {
      +        "ltcgFederal": {
      +          "description": "Federal capital gains tax (including NIIT) in dollars on the market gain at the horizon.",
      +          "type": "number"
      +        },
      +        "ltcgState": {
      +          "description": "State capital gains tax in dollars on the market gain.",
      +          "type": "number"
      +        },
      +        "ltcgTotal": {
      +          "description": "Total capital gains tax in dollars on the market gain.",
      +          "type": "number"
      +        },
      +        "marketGain": {
      +          "description": "Market growth in dollars on the reinvested cash over holdYears.",
      +          "type": "number"
      +        },
      +        "netAtYearN": {
      +          "description": "Net after-tax value of sell-now-and-invest in dollars at end of holdYears.",
      +          "type": "number"
      +        },
      +        "netCashAtY0": {
      +          "description": "Net cash in dollars after exercise tax, available to reinvest.",
      +          "type": "number"
      +        }
      +      },
      +      "required": [
      +        "netCashAtY0",
      +        "marketGain",
      +        "ltcgFederal",
      +        "ltcgState",
      +        "ltcgTotal",
      +        "netAtYearN"
      +      ],
      +      "type": "object"
      +    }
      +  },
      +  "required": [
      +    "exercise",
      +    "bracketJump",
      +    "hold",
      +    "sellNowInvest",
      +    "holdMinusCashless"
      +  ],
      +  "type": "object"
      +}
  6. Changed3 schema fields changedv1.3.4
    • removedInput schema / properties / haircut
      Removed value: -{
      -  "description": "Multiplicative haircut on expectedSalePrice (0..1) capturing volatility drag at the hold horizon. 0.20 = 20% haircut.",
      -  "maximum": 1,
      -  "minimum": 0,
      -  "type": "number"
      -}
    • addedInput schema / properties / volatility
      Added value: +{
      +  "description": "Annualized volatility (sigma) of the stock as a decimal (0.72 = 72%). Pass the user-supplied volatility directly; the tool computes the horizon-cumulative drag internally. The model MUST NOT compute drag itself — the correct formula is horizon-dependent and most models get it wrong. If the user does not supply a volatility number, ASK them.",
      +  "minimum": 0,
      +  "type": "number"
      +}
    • changedInput schema / required
      Previous value: -[
      -  "shares",
      -  "strike",
      -  "currentPrice",
      -  "ordinaryIncome",
      -  "filingStatus",
      -  "stateCode",
      -  "stillEmployed",
      -  "holdYears",
      -  "haircut",
      -  "holdFunding"
      -]New value: +[
      +  "shares",
      +  "strike",
      +  "currentPrice",
      +  "ordinaryIncome",
      +  "filingStatus",
      +  "stateCode",
      +  "stillEmployed",
      +  "holdYears",
      +  "holdFunding"
      +]
  7. Changed10 schema fields changedv1.2.2
    • addedInput schema / properties / currentPrice / description
      Added value: +"Current fair market value per share, USD. The bargain element at exercise is shares × (currentPrice − strike)."
    • addedInput schema / properties / filingStatus / description
      Added value: +"Federal filing status. Drives ordinary brackets and LTCG brackets used at the hold horizon."
    • addedInput schema / properties / haircut / description
      Added value: +"Multiplicative haircut on expectedSalePrice (0..1) capturing volatility drag at the hold horizon. 0.20 = 20% haircut."
    • addedInput schema / properties / holdFunding / description
      Added value: +"How the strike cost and exercise tax are funded. 'sell-to-cover' sells enough shares to cover strike + tax (reduces sharesRetained). 'cash' pays from outside the position (full sharesRetained but requires the cashNeededAtExercise field)."
    • addedInput schema / properties / holdYears / description
      Added value: +"Years to hold after exercise (minimum 1). At ≥1 year, the appreciation since exercise is LTCG; sub-1-year holds are out of scope."
    • addedInput schema / properties / ordinaryIncome / description
      Added value: +"Annual W-2 ordinary income before this exercise, USD. Baseline for the bracket walk on the bargain element."
    • addedInput schema / properties / shares / description
      Added value: +"Non-qualified Stock Option (NSO) shares to exercise."
    • addedInput schema / properties / stateCode / description
      Added value: +"Two-letter US state code. Drives state ordinary and LTCG treatment."
    • addedInput schema / properties / stillEmployed / description
      Added value: +"True if still employed at exercise. FICA (Social Security + Medicare + Additional Medicare) applies only when true."
    • addedInput schema / properties / strike / description
      Added value: +"Strike price per share, USD."
  8. First observedv1.2.0

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already mark the tool read-only and idempotent; the description adds meaningful context beyond that: deterministic and offline behavior, no default or fallback for required facts, required-field errors when growth/volatility cannot be resolved, unchecked provenance of supplied numbers, and ticker-resolution limitations. There is no contradiction with the annotations.

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

Conciseness3/5

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

The description is front-loaded with scope and includes a valuable example call, but it is long and contains off-task or redundant content such as verification URLs, a beta URL, and multi-tool independence notes that are not needed to select or invoke the tool. The density is useful, but not every sentence earns its place.

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

Completeness5/5

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

Given the tool's complexity, 14 parameters, and existing output schema, the description is complete: it covers required facts, optional resolution paths, error behavior, parameter interactions, and sibling routing. Output fields are referenced to the output schema rather than exhaustively repeated, which is appropriate.

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

Parameters5/5

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

Schema coverage is 100%, so the baseline is 3, but the description adds significant cross-parameter meaning: growth and volatility must come from one of three sources, neither has a default, 'market' is a sentinel for S&P 500 trailing average, haircut takes precedence over volatility, and expectedSalePrice/expectedMarketReturn have explicit resolution rules. This materially helps an agent assemble a valid call beyond reading individual parameter descriptions.

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 states a specific verb and resource: compare after-tax NSO exercise outcomes (sell-at-exercise vs hold-for-LTCG), including cashless-exercise and tax-trigger questions. It also explicitly names sibling tools for ISOs and RSUs, so the agent can distinguish it from amt_iso_optimize and rsu_sell_vs_hold without opening schemas.

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

Usage Guidelines5/5

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

It gives an explicit 'Use this when' list covering NSO exercise/sell/hold decisions, cashless exercises, and NSO tax questions. It also provides direct exclusions and alternatives: 'for ISOs use amt_iso_optimize, for RSUs use rsu_sell_vs_hold,' and clarifies that sub-1-year holds are out of scope.

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