Skip to main content
Glama

Server Details

31 no-AI tools: image conversion and EXIF stripping, App Store assets, colour maths. No API key.

If you are the author of this connector, you can claim ownership by verifying the domain or GitHub account it belongs to. Claimed connector authors can inspect health checks, view analytics, and manage their listing.
Status
Healthy
Uptime
100.0% over 21 days
Last Tested
Transport
Streamable HTTP · MCP 2025-11-25
URL

TDQS

A4.1/5.0

Scored across 31 tools

Disambiguation4/5

Most tools target distinct utilities, but a few close-domain pairs like image_compress vs image_compression_curve and check_contrast vs find_nearest_passing_color could be confused. Descriptions are clear enough for a careful agent, but selection mistakes are plausible.

Naming Consistency3/5

All names are lowercase snake_case, but the verb/noun order is mixed: most are verb_noun (calculate_*, convert_*, generate_*), while several lead with a noun (appstore_search, oklch_convert, recipe_scale). This is readable but not predictable enough for a 31-tool set.

Tool Count2/5

31 tools is a large grab-bag spanning app store, image, health, color, recipe, plant, music, and text domains. Each tool is individually useful, but the collection is too broad for a single server and would be easier to navigate if split into focused servers.

Completeness4/5

As a general-purpose utility server, it covers each domain's core operations well—color tools both check and fix contrast, image tools compress, resize, convert, and strip metadata, and app-store tools cover search, compare, link, revenue, and asset generation. Minor gaps exist (video conversion drops audio, no arbitrary QR generator), but no domain feels like a dead end.

Available Tools

31 tools
appstore_compare_marketsCompare an App Store listing across marketsA
Read-onlyIdempotent
Inspect

Looks up one app (by numeric App Store id, or a pasted apps.apple.com URL) in up to 30 App Store storefronts in a single call, via Apple's public iTunes Lookup API, and reports per-market availability, localized title, price and rating. Capped at 30 storefronts per call by design -- there are 173 total App Store storefronts, and this deliberately never sweeps all of them in one call; call again with a different storefronts list to cover more markets. Lookups run one at a time with a short pause between each (matching the pacing the source browser tool uses for its 30-market quick-compare), so a full 30-market call takes roughly ten seconds, not an instant burst.

ParametersJSON Schema
NameRequiredDescriptionDefault
appIdOrUrlYesNumeric App Store id (e.g. "6742322421") or a full apps.apple.com URL containing one.
storefrontsNoUp to 30 2-letter storefront codes (e.g. ["US","GB","JP"]) after de-duplication. Omit to use the same 30 major markets the source tool defaults to.

Output Schema

ParametersJSON Schema
NameRequiredDescription
rowsYesOne row per requested storefront, in the order given. A row is one of three shapes: listed, not listed, or errored.
appIdYesThe app ID that was looked up, extracted from the input if a URL was given.
foundYesHow many of them have the app listed.
failedYesHow many lookups errored rather than returning a verdict.
checkedYesHow many storefronts were queried.
distinctTitlesYesNumber of different titles seen across the storefronts that resolved -- more than one means the listing is localized.

TDQS

A4.5/5.0
Behavior5/5

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

Beyond the readOnly/idempotent annotations, the description discloses meaningful behavior: it uses Apple's public iTunes Lookup API, runs lookups sequentially with pauses, and takes roughly ten seconds for a full 30-market call. It also explains the intentional 30-storefront cap rather than silently sweeping all markets.

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 a single dense paragraph that front-loads the main action, then adds capacity, pacing, and repetition guidance. Every sentence adds needed operational detail, and no filler or tautology is present.

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?

For a read-only lookup tool with 100% schema coverage, a rich output schema, and safety annotations, the description covers the key missing context: limits, expected latency, default selection, and how to extend coverage. An agent has everything needed to plan and invoke the tool correctly.

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 coverage is 100%, so the schema already documents both parameters, including the numeric-id-or-URL format and the storefront list behavior. The description adds no new per-parameter semantics, though it reinforces the 30-storefront cap and default market list, which are already in the schema.

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 ('looks up') and resource ('one app by App Store id or URL across up to 30 storefronts'), plus the key reported fields: availability, localized title, price, rating. This distinguishes it crisply from siblings like appstore_search, which is search-by-keyword rather than lookup-by-ID/URL.

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

Usage Guidelines4/5

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

The description gives clear usage context: one app per call, up to 30 markets, and explicitly instructs calling again with a different storefronts list to cover more of the 173 storefronts. It does not name alternatives like appstore_search for other lookup needs, so there is no explicit exclusion, but the conditions are otherwise clear.

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

bpm_delay_calculatorBPM tap tempo & delay time calculatorA
Read-onlyIdempotent
Inspect

Resolves a musical tempo — either a direct BPM, or a set of tap timestamps/intervals run through the same median-filtered outlier rejection and 2-second session-reset logic as GO AI's tap-tempo tool — then returns the full straight/dotted/triplet delay and LFO-rate table (ms and Hz) for a set of note divisions, plus bar-length and bars<->seconds conversion for a time signature. All arithmetic (not a real audio engine) — useful for setting delay/reverb/LFO times to a track's tempo.

ParametersJSON Schema
NameRequiredDescriptionDefault
bpmNoTempo in beats per minute, used directly and unrounded. Provide exactly one of bpm, tapTimesMs, or tapIntervalsMs.
barsNoNumber of bars to convert to seconds. At most one of bars/seconds is used if both are given (bars takes priority).
secondsNoNumber of seconds to convert to bars. Ignored if bars is also given.
divisionsNoNote divisions (denominator of 1/d) to include in the delay table, up to 64. Defaults to [1,2,4,8,16,32].
tapTimesMsNoStrictly increasing millisecond timestamps of taps (e.g. from a high-resolution clock), in tap order, up to 4096. Provide exactly one of bpm, tapTimesMs, or tapIntervalsMs.
timeSignatureNoOne of "4/4","3/4","2/4","6/8","5/4","7/8","12/8" (the site's presets), or a custom {beats, unit} pair. Defaults to 4/4. Uses the written beat count — 6/8 is 6 beats of an eighth-note unit, not 2 dotted-quarter beats.
tapIntervalsMsNoMilliseconds between each tap and the one before it (one fewer entry than the number of taps), up to 4096. Provide exactly one of bpm, tapTimesMs, or tapIntervalsMs.

Output Schema

ParametersJSON Schema
NameRequiredDescription
bpmYesThe tempo the table was computed at, in beats per minute.
barsNoThe bar count that seconds corresponds to. Present only when bars or seconds was supplied.
secondsNoDuration in seconds of that many bars at this tempo. Present only when bars or seconds was supplied.
bpmSourceYesWhere that tempo came from: given directly, or averaged from tap times or tap intervals -- worth surfacing, since a tapped tempo carries the tapper's error.
delayTableYesOne row per requested division, each with straight, dotted and triplet timings.
timeSignatureYesThe time signature used to size a bar.
barDurationSecondsYesLength of one bar in seconds at this tempo and signature.

TDQS

A4.5/5.0
Behavior5/5

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

The description adds meaningful behavioral context beyond the annotations: it discloses the median-filtered outlier rejection and 2-second session-reset logic inherited from GO AI's tap-tempo tool, and explicitly states that it performs arithmetic rather than real audio processing. These details inform an agent about how tap data is interpreted and what the tool does not do, all without contradicting the idempotent and read-only annotations.

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

Conciseness4/5

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

The description packs purpose, method, output, and caveat into a single dense sentence. It is front-loaded with the core resolution logic and output type before the caveat. It could be split into separate sentences for easier scanning, but every clause earns its place and there is no fluff.

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

Completeness4/5

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

An output schema exists, so return values are already defined elsewhere. The description covers the key nuances: tap processing behavior, output table contents, bar-length and bars<->seconds conversion, the arithmetic-only nature, and a typical use case. The mutually-exclusive input requirement is documented in the schema properties, so the description does not need to repeat it. Given the tool's complexity, this is adequately complete.

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

Parameters4/5

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

Schema coverage is 100%, giving each parameter a definition. The description adds value by explaining the overall relationship between parameters (direct BPM vs. tap timestamps vs. intervals) and by mentioning the median-filtering logic that affects how tap data is processed. This goes slightly beyond the schema's individual parameter descriptions, which already document defaults and the mutually-exclusive constraint.

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 action ('Resolves a musical tempo... then returns the full straight/dotted/triplet delay and LFO-rate table') and clearly distinguishes the tool from a real audio engine by adding 'All arithmetic (not a real audio engine)'. It is distinct from all sibling tools, which are unrelated calculators and image/string utilities.

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

Usage Guidelines4/5

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

The description provides a clear use case ('useful for setting delay/reverb/LFO times to a track's tempo') and explains the input modes (direct BPM or tap timestamps/intervals) with the exclusive-OR constraint implied by 'either a direct BPM, or a set of tap timestamps/intervals'. There are no similar sibling tools that would require an explicit alternative, so the lack of when-not-to-use guidance is not a significant gap.

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

build_app_privacy_labelBuild an App Store privacy label checklistA
Read-onlyIdempotent
Inspect

Given which SDKs and features are present in an iOS app (analytics, crash reporting, ads, attribution, accounts, in-app purchases, location, HealthKit, contacts, search, etc.), returns the App Store Connect privacy data types those items most likely require declaring, grouped the way Apple groups them (Contact info, Identifiers, Usage data, Diagnostics, ...), each with the reason (which chosen item(s) caused it), plus whether App Tracking Transparency/tracking applies and the vendor-documentation date the mapping was checked against. This is a fixed, deterministic lookup table shipped with GO AI's privacy-label-builder page -- not a code scanner and not compliance advice. SDKs change what they collect (sometimes in a minor version) and app configuration changes it further, so treat the result as a starting checklist to verify against each SDK's current documentation, not an answer to submit as-is.

ParametersJSON Schema
NameRequiredDescriptionDefault
itemsYesSDKs/features present in the app binary. One or more of: firebaseAnalytics, crashlytics, sentry, amplitude, admob, metaSdk, attribution, push, accounts, signInApple, iap, revenuecat, support, userPhotos, preciseLocation, healthKit, contactsAccess, search. Repeats are ignored.

Output Schema

ParametersJSON Schema
NameRequiredDescription
groupsYesData types to declare, grouped the way App Store Connect groups them.
sdkCountYesHow many distinct SDKs/features were considered, after repeats collapse.
disclaimerYesStanding note that this is a deterministic lookup, not a code scan and not compliance advice.
trackingNoteYesExplanation of the tracking verdict and what it obliges.
checklistTextYesThe whole result as pasteable plain text, for dropping into a ticket or a submission checklist.
dataTypeCountYesTotal number of distinct data types to declare across all groups.
trackingRequiredYesTrue when at least one chosen item implies App Tracking Transparency / the "Used to Track You" declaration.
vendorDocumentationAsOfYesThe date this mapping table was last checked against vendor documentation -- SDKs change what they collect, so this bounds how much to trust the answer.

TDQS

A4.5/5.0
Behavior5/5

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

Annotations already declare readOnly, idempotent, and non-destructive, and the description adds complementary behavior: fixed deterministic lookup table, the mapping date it was checked against, grouping with reasons, and dependency on SDK versions and app configuration. This extra context goes beyond the annotations and helps the agent set correct expectations.

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 dense but every sentence contributes: what the tool returns, how output is grouped, why it is deterministic, and the important caveats about SDK changes and verification. The core operation is stated upfront, with caveats after.

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?

With one well-documented parameter and an output schema present, the description supplies all additional context an agent needs: returned categories, reasons, ATT/tracking applicability, vendor-documentation date, and caveats. Nothing about correct invocation is left ambiguous.

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 100%, so the schema already documents 'items' as the list of SDKs/features present, including the full enum and repeat handling. The description reinforces that idea ('Given which SDKs and features are present') but does not add new semantics for individual enum values or mapping rules, so baseline 3 is appropriate.

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 uses a specific verb ('returns the App Store Connect privacy data types') tied to a concrete input (SDKs/features) and output shape (grouped by Apple's categories, with reasons and ATT applicability). It also draws a clear boundary from related tasks by stating it is not a code scanner or compliance advice, so an agent can distinguish it from app-store sibling tools.

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

Usage Guidelines4/5

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

It states what the tool is (a fixed lookup table for building a starting privacy-label checklist) and what it is not (not a code scanner, not compliance advice, not a final answer). It gives a practical 'how to use' instruction: verify against each SDK's current documentation. It stops short of naming a sibling alternative, but no sibling tool in the same space is present.

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

calculate_app_store_net_revenueApp Store net revenue calculatorA
Read-onlyIdempotent
Inspect

Computes what a developer actually receives from an App Store sale: first removes the storefront's VAT/GST already baked into the customer-facing sticker price, then applies Apple's commission (standard 30%, Small Business Program 15%, or the post-year-one subscription 15%) to that tax-exclusive remainder -- NOT to the sticker price itself, which is the mistake most naive calculators make. Returns a full price breakdown, the effective share of the sticker Apple actually keeps, a side-by-side comparison across all three commission rates (including per-1,000-sales figures), a naive-calculator sanity check showing how far off a 'just take the commission off the top' estimate would be, and warnings when the selected storefront's real tax rate varies by province, state or category (India, Canada, Brazil) rather than being a single fixed number.

ParametersJSON Schema
NameRequiredDescriptionDefault
priceYesThe customer-facing sticker price for this storefront (already tax-inclusive everywhere except the US).
countryCodeNoTwo-letter App Store storefront code (e.g. us, gb, de, jp, in). Selects the default tax rate and currency symbol. Defaults to gb, matching the tool page.gb
taxRatePercentNoOverride the storefront's default tax rate baked into the price, as a percent (e.g. 20 for 20% VAT). Defaults to the selected countryCode's standard rate (0 for the US). Some storefronts' real rates vary by province, state or category -- see the warnings array.
commissionScenarioNoWhich Apple commission rate applies to the tax-exclusive remainder: 'standard30' (30%, the default for most apps), 'small15' (15%, Apple's Small Business Program for developers under the program's proceeds threshold), or 'yearTwo15' (15%, the reduced rate a subscription bills at after a subscriber has accumulated one year of paid, unlapsed service). All three are always returned side by side in the comparison array regardless of which is selected here.standard30

Output Schema

ParametersJSON Schema
NameRequiredDescription
inputYesWhat the calculation actually ran on, including the defaults that were filled in.
warningsYesCautions about the storefront, chiefly that its real tax rate varies by province, state or category. Empty when the rate is a single fixed number.
breakdownYesThe sticker price decomposed in the order the money is actually removed.
comparisonYesAll three commission scenarios side by side, regardless of which was requested.
naiveComparisonYesA sanity check against the common mistake of applying commission to the tax-inclusive price.
appleShareOfStickerPercentYesApple's commission as a percentage of the sticker price, which is lower than the headline rate because tax comes off first.

TDQS

A4.6/5.0
Behavior5/5

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

The annotations already declare readOnlyHint and idempotentHint, and the description goes well beyond that: it explains the tax-deduct-then-commission order, the NOT-on-sticker-price caveat, the returned breakdown, the three-way comparison including per-1,000-sales figures, the naive-calculator sanity check, and warnings for variable tax rates in specific countries.

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

Conciseness4/5

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

The description is dense but front-loaded with the core purpose and calculation order, then enumerates the output artifacts and warnings. It could be more scannable as bullets, but every clause earns its place and the length is justified by the tool's complexity.

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?

With a complete input schema, an output schema, and annotations, the description adds the essential methodology, the distinguishing correctness caveat, the list of returned comparisons, and the warning behavior. An agent has everything needed to decide whether and how to call this tool.

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

Parameters4/5

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

The schema covers 100% of parameters with good descriptions, so the baseline is 3. The description adds extra meaning by clarifying the tax-exclusive remainder concept behind taxRatePercent, explaining each commissionScenario value, and noting that all three commission scenarios are returned side by side regardless of selection.

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: it 'computes what a developer actually receives from an App Store sale' with a clear methodology. It also differentiates itself from naive calculators and, by its unique function, from sibling tools like appstore_compare_markets and appstore_search.

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

Usage Guidelines4/5

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

The description makes the usage context clear: it is for calculating net revenue after tax and Apple commission. It doesn't explicitly name alternatives or state when not to use it, but the sibling tools are sufficiently different that no exclusion is necessary, and the intended scenario is unambiguous.

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

calculate_bmi_and_body_fatBMI and Navy body fat calculatorA
Read-onlyIdempotent
Inspect

Computes BMI (with its standard weight-category label) and body fat percentage via the US Navy circumference method from height, weight, neck, waist, and (for females) hip measurements, plus the resulting lean body mass. The Navy equations are fitted in inches, so metric inputs are converted internally; the body-fat percentage is clamped to 2-75% and rounded to a whole number, matching GO AI's body-fat tool page exactly, including its 'waist must exceed neck (and hip, for the female formula)' impossible-input warning and its out-of-fitted-range caution for raw results below 4% or above 60%.

ParametersJSON Schema
NameRequiredDescriptionDefault
hipNoHip circumference at the widest point, in cm or inches. Required when sex is 'female'; ignored for 'male'.
sexYesSex, which selects the Navy formula variant.
neckYesNeck circumference, just below the larynx, in cm or inches.
unitsNometric = cm/kg, imperial = inches/lb. Applies to every measurement below.metric
waistYesWaist circumference (at the navel for men, at the narrowest point for women), in cm or inches.
heightYesHeight, in cm (metric) or inches (imperial).
weightYesWeight, in kg (metric) or lb (imperial). Used for BMI and lean mass, not for the body-fat percentage itself.

Output Schema

ParametersJSON Schema
NameRequiredDescription
bmiYesBody mass index to 1 decimal place, or null if it could not be computed.
unitsYesWhich unit system leanMass is expressed in -- otherwise the bare number is ambiguous.
leanMassYesLean mass in the same unit system as the input (kg for metric, lb for imperial). Null when body fat could not be computed.
warningsYesPlain-language cautions: the measurements make the formula undefined, or the result sits outside the range it was fitted on. Empty when neither applies.
bodyFatRawYesThe same figure before clamping and rounding, to 2 decimals -- this is the one that reveals an implausible measurement. Null in the same case as bodyFatPercent.
bmiCategoryYesThe WHO band that BMI falls in, e.g. 'Underweight', 'Normal', 'Overweight', 'Obese'. Null when bmi is null.
bodyFatPercentYesUS Navy tape-method body fat as a whole percent, clamped to 2-75. Null when the measurements make the formula undefined (see warnings).

TDQS

A4.4/5.0
Behavior5/5

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

Annotations already mark it read-only and idempotent, and the description adds substantial behavioral detail: internal metric-to-inch conversion, clamping of body-fat percentage to 2-75%, rounding to a whole number, and exact replication of GO AI's warnings for impossible inputs and out-of-range fitted results. This goes well beyond the safe-read signal of 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.

Conciseness4/5

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

Two sentences with no filler; the primary function is front-loaded and secondary behavioral details follow. The second sentence is dense but every clause conveys necessary constraints or compatibility details, so it 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 (sex-specific formula, unit handling, clamping, warnings), the description covers all nontrivial behavioral facets an agent must know. An output schema exists, so return-value details are not needed in the description. The description is complete for correct invocation.

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 coverage is 100%, so the baseline is 3. The description adds some context beyond the schema, such as the internal inch conversion and the fact that weight is not used for the body-fat percentage, but most parameter meaning (sex variants, hip required for female, units) is already fully documented in the schema.

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 names a specific verb (Computes) and exact resources (BMI, Navy body-fat percentage, lean body mass), and enumerates the required measurements. It is immediately distinguishable from siblings like calculate_tdee, which addresses a different metric.

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

Usage Guidelines4/5

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

The description precisely defines the tool's scope (BMI plus Navy body-fat method), giving an agent clear context for when to choose it. It does not explicitly name alternatives or exclusion conditions, but the unique formula and output metrics make the intended use unambiguous given the sibling list.

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

calculate_tdeeTDEE & macro calculatorA
Read-onlyIdempotent
Inspect

Calculate Basal Metabolic Rate with Mifflin-St Jeor and Harris-Benedict (plus Katch-McArdle when body_fat_percent is given), then Total Daily Energy Expenditure, a goal-adjusted calorie target (cut/maintain/bulk), and a protein/fat/carb macro split, from sex, age, height, weight and activity level. These are population-average formulas, not a measurement of the person's actual metabolism -- real expenditure commonly varies 200-300 kcal from the estimate, and the result includes a warning flag when a deficit target falls below the usual safety floor.

ParametersJSON Schema
NameRequiredDescriptionDefault
ageYesAge in years, 14-100. Every BMR formula here subtracts a per-year term, so this moves the result directly.
sexYesBiological sex. Selects the sex constant in the BMR formulas (Mifflin-St Jeor +5/-161, Harris-Benedict its own pair) and the calorie safety floor the goal target is checked against (1,500 kcal/day for male, 1,200 for female).
goalYesCalorie adjustment applied to TDEE to get the target: lose_20 is a 20% deficit, lose_15 a 15% deficit, maintain no change, gain_10 a 10% surplus. A deficit landing under the safety floor sets below_safety_floor in the result.
unitsNoWhich height/weight pair is read. 'metric' uses height_cm and weight_kg; 'imperial' uses height_ft (+ height_in) and weight_lb. The pair belonging to the other system is ignored, not merged. Defaults to 'metric'.metric
height_cmNoHeight in centimetres. Required when units is 'metric', ignored when units is 'imperial'.
height_ftNoHeight, whole feet, combined with height_in. Required when units is 'imperial', ignored when units is 'metric'.
height_inNoHeight, the inches remainder on top of height_ft (0-11). Used only when units is 'imperial', and treated as 0 when omitted -- so 6 ft flat is height_ft 6 with no height_in.
weight_kgNoWeight in kilograms. Required when units is 'metric', ignored when units is 'imperial'.
weight_lbNoWeight in pounds, converted internally at 2.20462 lb per kg. Required when units is 'imperial', ignored when units is 'metric'.
activity_levelYesMultiplier applied to BMR to reach TDEE: sedentary x1.2 (desk job, no exercise), light x1.375 (1-3 sessions/week), moderate x1.55 (3-5), very_active x1.725 (6-7), extra_active x1.9 (physical job or twice a day).
body_fat_percentNoOptional. Unlocks the Katch-McArdle formula, which uses lean mass instead of total weight.

Output Schema

ParametersJSON Schema
NameRequiredDescription
bmrYesBasal metabolic rate by formula, before any activity multiplier.
macrosYesA protein/fat/carb split of goal_target_kcal.
goal_pctYesThe goal adjustment as a signed percentage (-20, -15, 0, or +10).
tdee_kcalYesTotal daily energy expenditure in kcal/day: primary BMR x activity factor.
disclaimerYesStanding note that these are population-average estimates, not a measurement.
goal_labelYesWording for that adjustment, e.g. '15% deficit', 'maintenance', '10% surplus'.
spread_kcalYesDifference in kcal/day between the highest and lowest TDEE among the formulas that computed.
lean_mass_kgYesLean body mass in kg, or null when body_fat_percent was not supplied.
activity_labelYesThe full human-readable label for that activity level.
safety_warningYesThe full warning text when below_safety_floor is true, otherwise null.
activity_factorYesThe multiplier the chosen activity_level maps to (1.2 to 1.9).
primary_formulaYesWhich formula drove the headline numbers: 'Katch-McArdle' when body fat % was given, otherwise 'Mifflin-St Jeor'.
resolved_inputsYesWhat the imperial/metric inputs actually resolved to internally -- the numbers every formula above was fed.
goal_target_kcalYesThe goal-adjusted daily calorie target in kcal.
safety_floor_kcalYesThe floor that was checked against: 1500 kcal/day for male, 1200 for female.
below_safety_floorYesTrue when a deficit target falls below the usual floor for this sex -- the one field worth checking before presenting the target.
formula_comparisonYesAll three formulas side by side, so the spread is visible rather than implied.

TDQS

A4.5/5.0
Behavior5/5

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

Annotations already mark the tool as read-only, idempotent, and non-destructive. The description adds meaningful behavioral caveats beyond those: these are population-average formulas, real expenditure varies by 200-300 kcal, and a deficit below the safety floor produces a warning flag. This is exactly the kind of contextual disclosure that helps an agent trust and interpret the result.

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 sentences with no filler. The first sentence front-loads the full computation pipeline, and the second earns its place by disclosing formula limitations and the safety-floor warning behavior.

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 an 11-parameter tool with a full output schema and safety annotations, the description plus schema covers the formula selection, unit-system handling, goal enums, activity multipliers, safety floor, and output warning flag. Nothing essential is missing for correct selection and invocation.

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 100%, and each parameter already has detailed descriptions covering units, ranges, conditionals, defaults, and formula effects. The tool description adds only high-level grouping and the Katch-McArdle condition, so it does not need to compensate for schema gaps; the baseline of 3 applies.

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 names a specific computation pipeline (BMR via Mifflin-St Jeor, Harris-Benedict, and optional Katch-McArdle; then TDEE; then goal-adjusted target and macro split) and the exact inputs involved. This makes it clearly distinguishable from sibling tools like calculate_bmi_and_body_fat and project_weight_goal_date.

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

Usage Guidelines4/5

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

The description gives a clear context for when to invoke the tool: any time a TDEE estimate, calorie target, or macro split is needed from body measurements, activity, and goal. It does not explicitly name alternatives or exclusion criteria, but the use case is unambiguous and well-scoped.

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

check_contrastWCAG/APCA contrast checkerA
Read-onlyIdempotent
Inspect

Computes the WCAG 2.x contrast ratio between a foreground (text) and background hex colour, and reports AA/AAA pass or fail for normal text, large text, and UI components (icons/borders/focus rings) separately, since WCAG grades each case on its own threshold. Also reports the newer APCA Lc figure alongside it for information -- APCA is not yet what conformance is measured against, only the WCAG ratio is.

ParametersJSON Schema
NameRequiredDescriptionDefault
backgroundYesBackground colour as a hex string, same format as foreground.
foregroundYesForeground/text colour as a hex string, e.g. "#6b7280" or "6b7" (3- or 6-digit, leading # optional).

Output Schema

ParametersJSON Schema
NameRequiredDescription
apcaYesThe APCA (WCAG 3 draft) reading, which models perceived contrast rather than a pure luminance ratio and often disagrees with WCAG 2.
casesYesThe pair judged against each WCAG use case, since one ratio passes for large text and fails for body copy.
ratioYesWCAG 2 contrast ratio, from 1 (identical) to 21 (black on white).
summaryYesThe tally, so a caller can gate on one boolean instead of walking cases.
ratioDisplayYesThat ratio formatted the way it is conventionally written, e.g. '4.53:1'.
backgroundHexYesThe background colour normalised to hex.
foregroundHexYesThe foreground colour normalised to hex.

TDQS

A4.3/5.0
Behavior4/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It clearly discloses the core behavior, the separate grading categories, and the APCA caveat. It does not mention input edge cases or error behavior, but for a simple deterministic calculator the main behavior is transparently described.

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?

Two sentences, front-loaded with the main computation, then the output policy and the APCA caveat. Every clause earns its place, and there is no repetition of schema information or filler.

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?

An output schema exists, so return values do not need to be spelled out. The description covers what is computed, how pass/fail is broken down, the different WCAG thresholds, and the APCA informational role. For a tool of this complexity, nothing essential is missing.

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?

The schema is mostly minimal, with foreground lacking a field-level description and background only saying 'same format as foreground'. The tool description adds useful meaning by identifying foreground as text and both values as hex colours, but it does not specify accepted hex formats, case sensitivity, or whether alpha values are supported. The description adds some value over the schema but does not fully compensate for the sparse parameter documentation.

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 ('Computes the WCAG 2.x contrast ratio') and a precise resource (foreground/background hex colours). It also distinguishes the tool from siblings by explaining exactly what it reports: AA/AAA pass/fail for normal text, large text, and UI components, plus an informational APCA Lc value.

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

Usage Guidelines4/5

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

The description gives clear context for when to use the tool: when WCAG 2.x contrast conformance needs to be evaluated, with separate thresholds per category. It also warns that APCA is informational and not the conformance basis, which prevents misuse. It does not explicitly name alternatives like find_nearest_passing_color, so no exclusionary guidance is provided.

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

check_strings_filesiOS .strings localization checkerA
Read-onlyIdempotent
Inspect

Compares two or more iOS Localizable.strings files (sent as base64-encoded raw file bytes, not text) against a base file and reports keys missing from each other file, keys present in another file but not the base ("extra", reported but not counted toward the issue total), values byte-identical to the base (often untranslated, sometimes intentionally so), duplicate values under different keys within the same file (case- and trailing-punctuation-insensitive), and lines that fail to parse with their line number. Sniffs a UTF-8 or UTF-16 LE/BE byte-order mark per file so files exported by Xcode in UTF-16 decode correctly instead of producing a wall of parse errors. Does not support the newer .xcstrings JSON catalogue format, and does not check plural rules or placeholder (%@/%d) consistency between files -- it is a pure key/value diff.

ParametersJSON Schema
NameRequiredDescriptionDefault
filesYesTwo to 40 .strings files to compare against each other.
baseFileNoExact name of one of files[] to treat as the base ("compare against") file. When omitted, auto-selects the first file (after sorting all names alphabetically) whose name starts with "base." / "base-" / "base_" or "en." / "en-" / "en_" (case-insensitive), falling back to the alphabetically-first file if none match. A name that does not match any loaded file falls back the same way.

Output Schema

ParametersJSON Schema
NameRequiredDescription
filesYesPer-file summary, including the base file.
badLinesYesLines that are neither a comment, blank, nor a parseable key/value pair -- typically a missing semicolon or an unescaped quote.
baseFileYesWhich file every other file was compared against, whether given or chosen automatically.
extraKeysYesKeys in a translation that the base file no longer has -- usually left behind by a rename or a deletion.
fileCountYesHow many files were compared.
issueCountYesTotal findings across every category below. Zero means the files agree.
keysInBaseYesNumber of keys in the base file -- the denominator for the translation coverage.
missingKeysYesKeys present in the base file but absent from another -- untranslated strings that will fall back at runtime.
duplicateValuesYesKeys within one file that share a value, which is how a copy-paste translation error looks.
identicalToBaseYesKeys whose translation is byte-identical to the base language. Sometimes correct (a proper noun), often a forgotten translation.
missingKeysStringsYesEvery missing key formatted as ready-to-paste .strings entries with the base value, so the gap can be filled without retyping. Empty when nothing is missing.

TDQS

A4.8/5.0
Behavior5/5

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

Annotations only declare read-only, idempotent, and non-destructive behavior; the description adds substantial behavioral detail beyond that: BOM sniffing for UTF-8/UTF-16 decoding, duplicate detection that is case- and trailing-punctuation-insensitive, 'extra' keys being reported but not counted toward the issue total, and parse failures reported with line numbers. This gives the agent a realistic picture of side effects and edge cases.

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

Conciseness4/5

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

The description is long but dense, with every clause contributing a distinct behavioral fact: checks performed, encoding handling, counting nuance, and format limitations. It is front-loaded with the core purpose and avoids filler, though the single-paragraph format makes it slightly harder to scan than a structured list would be.

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?

For a tool with this complexity, the description covers all critical invocation context: input format, base file selection behavior, supported encodings, exact output categories, and explicit limitations. Since an output schema exists, the description does not need to document return values, and the schema covers parameter constraints like min/max file count.

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

Parameters4/5

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

The schema already describes both parameters well (raw base64 bytes, base file auto-selection), so the baseline is high. The description adds extra meaning by explaining why raw bytes matter for BOM sniffing and UTF-16 decoding, and by clarifying the base-file comparison semantics and what the tool counts as issues. This goes beyond simply repeating schema information.

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: it 'Compares two or more iOS Localizable.strings files' against a base file and enumerates the exact checks performed (missing keys, extras, untranslated values, duplicates, parse errors). It also clarifies what it does not do ('does not support .xcstrings', no plural/placeholder checks), which sharply distinguishes it from any alternative tool.

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?

The description explicitly scopes when this tool applies: comparing legacy .strings files, with files passed as base64-encoded raw bytes. It also gives clear when-not guidance by stating it does not support the newer .xcstrings catalogue format and does not validate plural rules or placeholders, effectively positioning it as a pure key/value diff tool.

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

convert_heic_to_jpg_pngConvert HEIC/HEIF to JPG or PNGA
Read-onlyIdempotent
Inspect

Converts iPhone HEIC/HEIF photos to JPEG or PNG, decoding with a WebAssembly build of libheif (LGPL-3.0) and re-encoding with sharp -- the same pipeline as GO AI's browser-based HEIC converter tool, run server-side. Accepts 1-50 files as {filename, dataBase64} (each up to ~30MB decoded); every file is byte-sniffed by its actual magic bytes, never by filename extension or claimed mime type, so an iPhone photo that iOS already delivered as a JPEG (picked from Photos rather than Files) is detected and reported as already_converted -- passed through unchanged, not re-encoded -- while real HEIC/HEIF input is decoded and re-encoded to the requested format (JPEG with a caller-set quality 50-100, or lossless PNG). Any orientation stored on the HEIC is applied automatically during decode, so output comes out right-side up. Metadata (EXIF: GPS, timestamp, camera) is not carried over, matching the source page. Each converted or passed-through file is returned individually (inline if small, as a download link if large), or bundled as one converted.zip when bundleAsZip is true and more than one file produced output; a file that fails to decode is reported with status failed and an error message rather than aborting the batch. The JSON report lists status, dimensions, and before/after byte sizes for every input file, in input order.

ParametersJSON Schema
NameRequiredDescriptionDefault
filesYes1 to 50 files, in the order results are reported. Mix HEIC/HEIF and already-JPEG/PNG files freely -- each is byte-sniffed on its own. The batch is additionally capped at 4000000 bytes of combined decoded input, which is what binds in practice for real photos.
formatNoOutput format for any real HEIC/HEIF input. Ignored for input already sniffed as JPEG or PNG, which is passed through unchanged rather than re-encoded.image/jpeg
qualityNoJPEG quality, 50-100. Ignored when format is image/png (PNG is lossless).
bundleAsZipNoWhen true and more than one file produced output bytes (converted or passed-through), bundle them into one converted.zip instead of returning each file as its own separate output.

Output Schema

ParametersJSON Schema
NameRequiredDescription
formatYesOutput MIME type requested for the batch.
resultsYesOne row per input file, in the order supplied. Rows come in three shapes depending on status.

TDQS

A4.4/5.0
Behavior5/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, and destructiveHint=false, and the description adds substantial behavioral detail: magic-byte sniffing over filenames, already_converted pass-through, automatic orientation, metadata stripping, per-file failure isolation, and zip bundling. This far exceeds the annotation baseline.

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

Conciseness4/5

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

The description is long but information-dense and logically structured: purpose, input shape, format detection, orientation/metadata, output modes, and failure behavior. It front-loads the core purpose. Minor redundancy with the schema and the ~30MB inconsistency keep it from a 5.

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?

For a tool with four parameters, one required array, and rich output behavior, the description covers input limits, pass-through behavior, output formats, orientation handling, metadata non-preservation, response modes, zip bundling, and error handling. The presence of an output schema means return-value detail is not required, and nothing material is missing.

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 coverage is 100%, so the baseline is 3. The description adds useful interaction semantics (quality ignored for PNG, format ignored for pass-through, bundling only when more than one file produces output), but it also states 'each up to ~30MB decoded' while the schema caps each decoded file at 4000000 bytes and the whole batch at 4000000 bytes combined. This contradiction weakens trust in the parameter guidance.

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: 'Converts iPhone HEIC/HEIF photos to JPEG or PNG.' It clearly distinguishes itself from siblings like convert_video_to_gif, image_compress, resize_images, and strip_image_metadata by naming the input format, output formats, and the conversion pipeline.

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

Usage Guidelines4/5

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

The description gives clear context on when the tool is appropriate: converting HEIC/HEIF photos, with byte-sniffing so already-JPEG/PNG files are passed through unchanged. It doesn't explicitly name alternatives or say 'use X instead', but the intended use case is unambiguous given the sibling set.

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

convert_video_to_gifTrim a short video into a GIF, MP4 or WebMA
Read-onlyIdempotent
Inspect

Ports GO AI's browser Live Photo-to-GIF tool server-side: trims a SHORT video clip and converts it to an animated GIF, or (a server-side addition the browser tool cannot do) a real MP4/WebM video. Input is base64 video bytes plus its mimeType (any container/codec this server's ffmpeg build can decode) and a trim range in seconds (startSeconds/endSeconds).

HARD LIMITS -- this runs in a 400 MB container on one shared vCPU behind a proxy that hangs up at 125s, so an over-budget request is REJECTED up front with a message naming the limit rather than accepted and then killed. Read these before calling: (1) the video itself must decode to at most 4 MB -- send a pre-trimmed clip, not a whole recording; (2) the trim range must span at least 0.25s and at most 10s; (3) width x height x frames must not exceed 12 megapixels TOTAL across the whole animation. That third one is the binding limit in practice and it is easy to trip with a portrait clip: at size=480 a 9:16 video is 480x854, so it fits about 29 frames (~2.9s at fps=10) -- while a 16:9 video at the same size is 480x270 and fits the full frame cap. If a call is rejected, the error names the exact frame/size that would fit; do not retry with the same numbers.

size picks the output width in px (0 keeps the source's own width, clamped to 720; otherwise 720/480/320/240, default 320). Height is derived to preserve aspect ratio and both are forced even (a hard requirement of GIF and most video codecs). fps (10/12/15/20/25, default 10) is the target sampling rate; because GIF frame delays are whole hundredths of a second, the achieved rate (actualFps in the result) is rounded and rarely matches exactly what was requested -- always read actualFps back, do not assume it equals fps. speed (0.5/1/1.5/2) scales playback; speed below 1 samples MORE frames from the same span and so costs more of the pixel budget. motion is loop (plays once per cycle, restarts abruptly), bounce (plays forward then backward so it never visibly cuts -- this roughly DOUBLES the emitted frame count and therefore halves the span that fits the pixel budget), or once (plays through and stops; GIF only, this disables the NETSCAPE2.0 loop extension).

For format 'gif' (the default): at most 120 frames are sampled from the trim range (bounce mirrors the middle back on top of that afterward, so a bounced GIF can carry up to 238 frames if the pixel budget allows), and colours (256/128/64, default 256) sets the palette size. The palette is a single median-cut palette built across every sampled frame at once, not per frame, exactly like the source -- a clip with wildly different scenes (e.g. a cut between two very different shots) will show banding because the whole clip is sharing one 256-or-fewer-colour palette. dither (default true) enables Floyd-Steinberg dithering, which smooths gradients at the cost of file size and adds visible noise to flat graphics/screen recordings -- turn it off for those. For format 'mp4' (H.264) or 'webm' (VP9): frame sampling and the GIF palette pipeline are skipped entirely and ffmpeg encodes the trimmed/scaled/speed-adjusted clip directly in one pass; audio is always dropped, since the source tool this is ported from is silent-GIF/loop focused and has no audio-preserving path of its own. The same trim-span and pixel limits apply. Frame extraction for GIF approximates the source's exact evenly-spaced-timestamp sampling with ffmpeg's own fps-filter resampling of the decoded stream (documented in code as a deliberate simplification, not a literal port) -- for most clips this is visually indistinguishable, but timing will not be bit-identical to the browser tool's output for the same input. Returns the encoded file (inline if small, otherwise a download link) plus a JSON stats object: frameCount (gif only), width, height, byteSize, requestedFps, actualFps, delayCentiseconds (gif only), format, outputPixels, maxOutputPixels, and more. An oversized request, a trim under 0.25s, an undecodable video, or an unsupported codec is reported as a tool error naming the problem, never a crash.

ParametersJSON Schema
NameRequiredDescriptionDefault
fpsNoTarget sampling/output frame rate: 10 (default), 12, 15, 20 or 25. Higher fps spends the frame budget faster, so it shortens the trim range that will fit. For GIF, the true achieved rate is reported back as actualFps and will differ slightly due to whole-centisecond frame delays.
sizeNoOutput width in px: 0 keeps the source's own width (clamped to 720); otherwise 720, 480, 320 (default) or 240. Height is derived to preserve aspect ratio; both dimensions are forced even. Bigger sizes buy fewer frames -- width x height x frames is capped at 12 megapixels.
speedNoPlayback speed multiplier: 0.5, 1 (default), 1.5 or 2. Higher speed samples fewer frames per second of source video (and so fits a longer trim); 0.5 samples twice as many.
ditherNoFloyd-Steinberg dithering for the GIF palette (default true). Smooths gradients at the cost of file size and adds noise to flat graphics -- turn off for screen recordings or flat artwork. Ignored for mp4/webm.
formatNoOutput container. 'gif' (default) runs the full sampling + shared-palette + LZW pipeline. 'mp4' (H.264) and 'webm' (VP9) skip that pipeline and have ffmpeg encode the trimmed/scaled/speed-adjusted clip directly, with audio always dropped.gif
motionNo'loop' (restarts from the first frame each cycle), 'bounce' (plays forward then backward, never visibly cuts -- roughly doubles the emitted frame count and so halves the span that fits the pixel budget), or 'once' (plays through and stops -- GIF only, disables looping).loop
coloursNoGIF palette size: 256 (default), 128 or 64 shared colours across the whole clip. Ignored for mp4/webm.
mimeTypeYesThe source video's mime type, e.g. "video/quicktime" or "video/mp4". Used only to pick a temp-file extension; the actual format is content-sniffed by ffmpeg.
endSecondsYesTrim end, in seconds. Must leave at least 0.25s and at most 10s between startSeconds and endSeconds.
videoBase64YesBase64-encoded source video bytes (not a file path or URL). The decoded video must be at most 4 MB; larger payloads are rejected before any decoding happens.
startSecondsNoTrim start, in seconds from the beginning of the video. Clamped to the video's own duration.

Output Schema

ParametersJSON Schema
NameRequiredDescription
audioNoWhat happened to the source audio track. mp4/webm output only.
speedYesSpeed multiplier applied to the source.
widthYesOutput width in px.
ditherNoWhether dithering was applied during quantisation. GIF output only.
formatYesOutput format. This field decides which of the conditional fields below are present.
heightYesOutput height in px.
motionYesPlayback behaviour applied.
trimEndNoEnd of the trimmed span in seconds. GIF output only.
byteSizeYesSize of the produced file in bytes.
trimSpanNoLength of the trimmed span in seconds. GIF output only.
actualFpsYesThe frame rate actually achieved, which can differ because frames are sampled by ffmpeg's fps filter rather than seeking to exact times.
maxColorsNoPalette size the median-cut quantiser was allowed. GIF output only.
trimStartNoStart of the trimmed span in seconds. GIF output only.
frameCountNoNumber of frames encoded. GIF output only.
outputPixelsYesWidth x height x frames -- the bound that actually governs this tool's memory use.
requestedFpsYesThe frame rate that was asked for.
durationSecondsNoDuration of the produced clip in seconds. mp4/webm output only.
maxOutputPixelsYesThe ceiling outputPixels was checked against, so a rejection is explicable from the result.
delayCentisecondsNoPer-frame delay written into the GIF, in centiseconds. GIF output only.

TDQS

A4.7/5.0
Behavior5/5

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

Beyond the readOnly/idempotent/destructive annotations, it discloses hard resource limits ('400 MB container ... proxy that hangs up at 125s', '4 MB', '12 megapixels TOTAL'), rejection semantics ('REJECTED up front with a message naming the limit'), implementation quirks ('actualFps ... rarely matches exactly'), audio dropping, and the non-bit-identical GIF timing simplification. It even describes error behavior: 'reported as a tool error naming the problem, never a crash.' This is rich, non-redundant behavioral context.

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

Conciseness4/5

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

The description is long, but the content is front-loaded with HARD LIMITS and each major parameter has a connected consequence. Some redundancy remains with the input schema and repeated background about the browser/source tool, so a slightly tighter edit would improve it, but nothing is trivial filler.

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?

For an 11-parameter tool with hard constraints, the description is complete: input format, limits, per-format pipeline, return payload, failure modes, and output schema presence are all covered. An agent has enough to decide whether and how to call it without needing to probe the server.

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?

Although the schema already documents parameters at 100% coverage, the description adds meaningful tradeoffs: size 0 clamps to 720, bounce 'roughly DOUBLES the emitted frame count', speed below 1 'samples MORE frames', and the 480x854 versus 480x270 worked example for the pixel budget. Each parameter's consequence is tied to hard limits, going beyond enum/default 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 uses a specific verb+resource: 'trims a SHORT video clip and converts it to an animated GIF, or ... a real MP4/WebM video.' It names exact output formats and the video input, so it is easy to tell apart from sibling tools like convert_heic_to_jpg_png or image_compress. The title also reinforces the same scope without ambiguity.

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

Usage Guidelines4/5

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

The description gives explicit conditions: 'send a pre-trimmed clip, not a whole recording', 'Read these before calling', and 'do not retry with the same numbers' after rejection. It explains format-specific choices and when to turn dithering off. It does not, however, name any sibling-tool alternative to route to when this tool is not appropriate, so it stops short of a full when/else guide.

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

css_clamp_calculatorCSS clamp() CalculatorA
Read-onlyIdempotent
Inspect

Generates a CSS clamp() declaration (in rem) that linearly interpolates a size between a value at a small viewport width and a value at a large one, exactly matching GO AI's clamp() calculator tool (same slope/intercept math, rounded to 4 decimal places). Returns a status: 'sameViewports' when the two viewport widths are equal (no line can be drawn -- css is null), 'flat' when the min and max sizes are equal (a constant, clamp() unneeded), 'inverted' when the max size is smaller than the min (still valid CSS, but the computed size shrinks as the viewport grows), or 'ok' otherwise. Optionally evaluates the computed pixel size at a list of preview viewport widths, and optionally generates a matched fluid type scale by multiplying both endpoints by ratio^step for a given number of steps -- unlike the website's five-option dropdown, any positive ratio is accepted here since the underlying math is not limited to those presets.

ParametersJSON Schema
NameRequiredDescriptionDefault
maxSizePxYesSize in px at the large viewport.
minSizePxYesSize in px at the small viewport.
typeScaleNoOptional: when provided, also returns a fluid type scale of this many steps, each step scaling both endpoints by ratio^step.
maxViewportPxYesThe large viewport width in px.
minViewportPxYesThe small viewport width in px.
rootFontSizePxNoRoot font size in px used to convert the px sizes to rem. Defaults to 16; a value of 0 also falls back to 16.
previewViewportsPxNoOptional list of viewport widths (px), up to 64, to evaluate the resulting clamp() at, returned as computed pixel sizes.

Output Schema

ParametersJSON Schema
NameRequiredDescription
cssYesThe finished CSS clamp() declaration in rem, or null when status is sameViewports.
mathYesThe four numbers the css string is assembled from, each rounded to 4 decimal places. Null when status is sameViewports.
noteYesHuman-readable explanation of a non-ok status, or an empty string when status is ok.
scaleYesThe fluid type scale, one entry per step. Empty when typeScale was omitted or no step could be built.
statusYes'ok' for a normal fluid size; 'sameViewports' when the two viewport widths match, so no line can be drawn and css is null; 'flat' when both sizes are equal (a constant, clamp() unneeded); 'inverted' when the max size is below the min, which is valid CSS that shrinks as the viewport grows.
previewYesOne entry per requested preview viewport, in the order given. Empty when previewViewportsPx was omitted.

TDQS

A4.6/5.0
Behavior5/5

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

The description thoroughly discloses behavior beyond the annotations: exact matching math, rounding to 4 decimals, the four status outcomes with their meanings, and the effects of optional preview and type-scale parameters. Since annotations already mark the tool read-only and idempotent, the description goes further by explaining edge cases like 'sameViewports' and 'flat' that directly influence how an agent should interpret results.

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

Conciseness4/5

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

The description is fairly long but every sentence carries useful information: core behavior, status semantics, preview behavior, and type-scale differences from the website. It is front-loaded with the primary purpose and then layers edge cases and optional features logically. A slightly tighter structure could separate the status enumeration from the optional-feature explanation, but nothing here is filler.

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, the description is complete: it explains return statuses, edge cases, optional preview/type-scale behavior, and exact compatibility with an existing tool. The output schema exists, so the description does not need to spell out the full return shape, and the parameter schema covers all inputs. An agent has everything needed to select and invoke this tool correctly.

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

Parameters4/5

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

The input schema already covers 100% of parameter descriptions, so the description does not need to repeat them. It adds meaningful semantic context by explaining the interpolation model, the status behaviors tied to parameter values, the preview evaluation behavior, and the type-scale math ('multiplying both endpoints by ratio^step'). This goes beyond the schema's per-field 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 opens with a specific verb and resource: 'Generates a CSS clamp() declaration (in rem)' with precise linear-interpolation semantics. It covers the core behavior, distinguishes the tool from the broader sibling set of calculators/filters, and adds enough detail about statuses and optional features that an agent can immediately understand what the tool produces.

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

Usage Guidelines4/5

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

The purpose is stated clearly enough that an agent can infer when to use it: whenever a fluid clamp() value needs to be generated for a design. It also clarifies an important behavioral divergence from the reference website ('any positive ratio is accepted here'), which helps avoid incorrect assumptions. It does not explicitly enumerate when-not-to-use or name sibling alternatives, but no direct sibling appears to compete for this task.

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

estimate_ai_tokensEstimate AI token count, context fit & costA
Read-onlyIdempotent
Inspect

Heuristic estimate (NOT a real per-model BPE tokenizer) of how many tokens a piece of text will become, ported from GO AI's browser-side token counter. Blends a chars/4 and words/0.75 baseline with surcharges for punctuation, digits and non-Latin script; typically within ±10-15% for ordinary English prose and ±15-25% when non-Latin script is detected -- code and heavily punctuated text tend to tokenize denser than this suggests. Also reports whether the text fits a set of context windows (default: 8K/128K/200K/1M/2M tokens, or pass your own) and, if you supply per-million-token prices, an estimated USD cost. Use a provider's own tokenizer for exact billing figures.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYesThe text to estimate, up to 1000000 characters (roughly 250K tokens). An empty string is valid and yields 0 tokens.
callsNoNumber of times this input/output pair will be sent, to scale total cost. Defaults to 1; values below 1 are treated as 1.
contextWindowsNoCustom context windows to check fit against, e.g. [{"name":"my-model","sizeTokens":32000}]. Omit to use the site default set (8K, 128K, 200K, 1M, 2M).
expectedOutputTokensNoAssumed reply length in tokens, used only to estimate output cost. Defaults to 500.
inputPricePerMillionUsdNoUSD price per 1,000,000 input tokens, from the provider's current pricing page. Omit to skip input cost -- no default is assumed, since a stale hardcoded price would be worse than none.
outputPricePerMillionUsdNoUSD price per 1,000,000 output tokens. Omit to skip output cost.

Output Schema

ParametersJSON Schema
NameRequiredDescription
costYesProjected spend. Every field is null unless the matching per-million price was supplied.
wordsYesWhitespace-delimited word count.
tokensYesEstimated token count for the text.
charactersYesCharacter count of the input text.
contextFitYesThe text measured against each context window, so the caller sees which models it fits.
readingTimeYesHow long the text takes a person to read, as a sanity check on the size.
accuracyBandYesAn honesty bound on the token count: this is a heuristic, not a real tokenizer, and the band says how far off it usually is.
charsPerTokenYesCharacters divided by tokens -- the density that drives the accuracy band.

TDQS

A4.7/5.0
Behavior5/5

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

Beyond the readOnly/idempotent annotations, it discloses the heuristic algorithm, expected accuracy ranges (±10-15% and ±15-25%), and known failure modes (code and heavily punctuated text). It also explains the default context windows and that cost is only estimated when prices are supplied.

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?

Four sentences pack the core purpose, accuracy caveats, auxiliary outputs, and the exact-billing caveat without repetition. The most important scoping statement ('NOT a real per-model BPE tokenizer') is front-loaded.

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?

For a moderately complex estimation tool with an output schema, the description covers algorithm, accuracy, context-window defaults, cost behavior, and the exact-tokenizer alternative. Nothing an agent needs to call it correctly is missing.

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 coverage is 100%, so the schema already documents all six parameters. The description adds context about default windows and price-dependent cost estimation, but it does not add per-parameter syntax or semantics beyond what the schema provides. Baseline 3 is appropriate.

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 names a specific action (estimate), resource (AI token count), and scope (text tokenization plus context-window fit and cost). It also explicitly disclaims being a real BPE tokenizer, which separates it from exact tokenizer tools. The title and description align.

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 when-not: 'Use a provider's own tokenizer for exact billing figures,' and the heuristic/accuracy framing implies use when an approximation is acceptable. The alternative is clearly named, even though it is not a sibling tool.

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

estimate_window_lightWindow light estimator for houseplantsA
Read-onlyIdempotent
Inspect

Estimates the indoor light level a window gives a plant, in the category words houseplant care guides use (Direct sun / Bright indirect / Medium light / Low light / Too dark), from the window's hemisphere, compass aspect, what obstructs it, and how far back the plant sits from the glass. This is a small heuristic scoring table ported from GO AI's window-light tool page, not a physical light-meter reading or a per-location sun-position model — it correctly flips which compass direction is 'bright' for the southern hemisphere and flags the direct-west-sun scorch risk, and returns the reasoning behind the verdict plus a shortlist of plants suited to that light level.

ParametersJSON Schema
NameRequiredDescriptionDefault
aspectYesTrue compass direction the window faces (before any hemisphere adjustment).
blockedYesWhat stands between the glass and the open sky: 'clear' = nothing, 'sheer' = a net curtain/blind or distant trees, 'near' = a tree or building close by, 'heavy' = mostly blocked.
distanceYesHow far back from the glass the plant sits: 'sill' = on the sill, 'd1' = within an arm's reach, 'd2' = one to two metres back, 'd3' = two to three metres back, 'd4' = further into the room.
hemisphereYesHemisphere the window is in. South of the equator the sun tracks the northern sky, so the compass direction that gets the strongest light is reversed from what most (northern-hemisphere-written) plant guides assume.

Output Schema

ParametersJSON Schema
NameRequiredDescription
bandYesThe score bucketed into a named light level, the single field most callers want.
scoreYesThe computed light score the band and verdict are derived from -- higher is brighter.
aspectYesThe compass direction the window faces, echoed from the input.
plantsYesPlant types suited to this light level.
verdictYesOne-line summary of what this window is good for.
reasoningYesWhy the score came out where it did, naming the aspect, glazing and distance contributions.
verdictSubYesA supporting line qualifying the verdict.
solarAspectYesThat aspect mapped to its solar equivalent for the given hemisphere -- a south-facing window means the opposite thing north and south of the equator.

TDQS

A4.4/5.0
Behavior5/5

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

Annotations already declare readOnlyHint=true, idempotentHint=true, destructiveHint=false, and the description adds valuable behavioral specifics: it correctly flips the bright compass direction for the southern hemisphere, flags direct-west-sun scorch risk, and returns reasoning plus a plant shortlist. These are behaviors an agent could not infer from the schema or annotations. No contradiction with annotations.

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

Conciseness4/5

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

Two sentences, front-loaded with the core function, then exclusions and behavioral specifics. The second sentence is dense but every clause adds relevant information about hemisphere handling, scorch risk, and return contents. Slightly heavy punctuation and enumeration, but nothing wasteful for this level of complexity.

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?

An output schema exists, so return structure is already covered, and the description still previews the verdict reasoning and plant shortlist. All four required input parameters are described and the heuristic nature is transparent. Nothing an agent needs to invoke this correctly is missing.

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 100%, with all four parameters fully explained by their enum descriptions. The description's mention of 'hemisphere, compass aspect, what obstructs it, and how far back' restates the same semantics in prose without adding new meaning. Baseline 3 applies because the schema carries the parameter-documentation burden.

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 opens with a specific verb ('Estimates') and resource ('indoor light level a window gives a plant'), enumerates the output categories, and names the four input factors. It also distinguishes itself from physical light meters and sun-position models, which separates it from any would-be sibling. The purpose is unmistakable and complete.

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

Usage Guidelines4/5

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

The description gives clear context: it is a heuristic scoring table for houseplant care, not a precise light meter or sun-position model. This is an implicit exclusion of precision tools, though it does not explicitly name a sibling tool to use instead. It is enough for an agent to know when this tool is appropriate.

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

find_nearest_passing_colorNearest WCAG-passing colour (hue-preserving)A
Read-onlyIdempotent
Inspect

Given a foreground/background hex pair, moves only the lightness of one side (hue and saturation held fixed) in the smallest step that clears a target WCAG contrast ratio, so a failing brand colour can be fixed without draining its hue. Defaults to the 4.5:1 normal-text AA threshold, matching the source page's fix buttons; pass a named threshold ('normal-aa', 'normal-aaa', 'large-aa', 'large-aaa', 'ui-aa') or a custom numeric ratio to target something else. Reports found:false when even pure black or white in that hue cannot reach the target -- that pair needs a different hue, not a different lightness.

ParametersJSON Schema
NameRequiredDescriptionDefault
adjustYesWhich colour to move toward passing; the other one stays fixed.
targetNoWCAG threshold to clear: 'normal-aa' 4.5:1 (default, matches the page's buttons), 'normal-aaa' 7:1, 'large-aa'/'ui-aa' 3:1, 'large-aaa' 4.5:1, or any custom positive ratio.normal-aa
backgroundYesBackground colour as a hex string, same format as foreground.
foregroundYesForeground/text colour as a hex string, e.g. "#6b7280" (3- or 6-digit, leading # optional).

Output Schema

ParametersJSON Schema
NameRequiredDescription
foundYesWhether a passing colour was reached. False means the target is unreachable by lightness alone from this starting colour.
adjustYesWhich colour was moved, echoed from the input.
messageYesPlain-language summary of the outcome, including why no colour was found when found is false.
movedHexYesThe adjusted colour in hex, or null when nothing was moved or nothing was found.
newRatioYesThe contrast ratio after the move, or null when there was no move.
directionYesWhether the colour was lightened or darkened; null when nothing moved.
targetRatioYesThe contrast ratio being aimed for, resolved from the named target or given directly.
currentRatioYesThe ratio the original pair had.
alreadyPassingYesTrue when the input pair already met the target and nothing needed moving.
lightnessStepPercentYesHow far the colour had to travel in lightness, as a percentage -- a large value means the passing colour is no longer the colour that was asked for.

TDQS

A4.7/5.0
Behavior5/5

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

Annotations (readOnlyHint=true, idempotentHint=true, destructiveHint=false) already cover the safety profile, and the description adds substantial algorithm context: hue and saturation held fixed, minimal step, threshold-to-ratio mappings, and the found:false failure mode with its semantic consequence. No contradiction with annotations — 'moves only lightness' describes the computation, not a side effect.

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?

Three sentences, front-loaded with the core algorithm, then threshold behavior, then the found:false failure mode. Every sentence earns its place, and the enum listing compresses schema information efficiently rather than inflating the description.

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?

For a 4-parameter tool with an output schema and full annotations, the description covers the algorithm, default rationale, threshold selection, and failure semantics. Return-value format is already handled by the output schema, so nothing an agent needs to decide whether to call it correctly is missing.

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

Parameters4/5

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

Schema coverage is 100% with detailed parameter descriptions (hex format, ratio mappings, adjust meaning), so the baseline is 3. The description adds conceptual glue by tying adjust to the algorithm ('moves only the lightness of one side') and clarifying target's role in the fixing workflow, going slightly beyond the schema's largely format-level documentation.

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?

States a specific verb+resource: 'moves only the lightness of one side' of a foreground/background hex pair 'in the smallest step that clears a target WCAG contrast ratio.' It is clearly a fixing/adjustment tool rather than a verification tool, distinguishing it from sibling check_contrast, and the title's 'hue-preserving' plus the description's 'hue and saturation held fixed' further differentiate it from color-conversion siblings like oklch_convert.

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

Usage Guidelines4/5

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

Gives clear context for when to use it: fixing a failing brand colour without draining its hue, with the default threshold 'matching the source page's fix buttons.' The found:false escape hatch also guides the agent (a different hue is needed, not further lightness tuning). However, it never explicitly names an alternative tool or states when not to use it, so the exclusion logic is left to inference.

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

generate_app_icon_setApp icon set generator (iOS + Android)A
Read-onlyIdempotent
Inspect

Turns one source image into a ZIP containing a complete iOS Xcode AppIcon.appiconset (AppIcon.png at 1024x1024, plus a hand-written Contents.json Xcode accepts, and optionally AppIcon-Dark.png and AppIcon-Tinted.png for the appearance variants modern iOS asks for), a full Android mipmap-mdpi/hdpi/xhdpi/xxhdpi/xxxhdpi set of ic_launcher.png files (48/72/96/144/192px), and a flat sizes/ folder with 24 icon-.png files from 16 to 1024px. Any transparency in the source is flattened onto the given background colour first (Apple rejects icons with alpha). A non-square source is centered and letterboxed onto a square canvas rather than stretched. Every output size is produced by repeatedly halving the source (a box-filter-equivalent downscale) before one final resize to the exact target, which keeps small icons sharp instead of muddy. The dark variant is a mechanical darken (source composited at 82% opacity over black) offered as a starting point, not a real design pass -- review it before shipping. The tinted variant is a Rec.709 greyscale luminance map, which is what iOS actually wants for that slot (it applies the colour itself). The JSON result reports the source dimensions, whether it had transparency, the file count, and two non-fatal warnings when the source was not square or not exactly 1024px on its longest side. Always returns as a resource_link (a 30+ file ZIP is never small enough to inline) -- fetch the link to get the archive.

ParametersJSON Schema
NameRequiredDescriptionDefault
variantsNoWhich iOS appearance variants to include alongside the required light AppIcon.png. 'none': light only. 'dark': light + AppIcon-Dark.png. 'all': light + AppIcon-Dark.png + AppIcon-Tinted.png. Contents.json is written to match whichever set is chosen.all
imageBase64YesBase64-encoded source image bytes (PNG, JPEG, or WebP). 1024x1024 is ideal; anything else is padded to square and/or scaled.
backgroundColorNoCSS/hex colour (e.g. "#ffffff") used to flatten transparency and to pad a non-square source before it is centered onto a square canvas.#ffffff

Output Schema

ParametersJSON Schema
NameRequiredDescription
filesYesEvery file in the archive, so the contents can be checked without unzipping it.
sourceYesDimensions of the image that was supplied, before squaring.
filesOutYesTotal number of files inside the ZIP.
hadAlphaYesTrue when the source carried transparency, which was flattened onto backgroundColor because Apple rejects icons with alpha.
variantsYesWhich appearance variants were generated, echoed from the input.
warningsYesNon-fatal quality warnings. Both are null on an ideal 1024px square source -- neither stops the ZIP being produced.
backgroundColorYesThe colour transparency was flattened onto, and the letterbox colour for a non-square source.

TDQS

A4.7/5.0
Behavior5/5

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

The description goes far beyond the readOnly/idempotent hints by disclosing the flattening behaviour, letterboxing, halving-based downscale algorithm, the mechanical nature of the dark variant, the Rec.709 luminance map for the tinted variant, and the unconditional resource_link return. No contradiction with annotations.

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

Conciseness4/5

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

The purpose is front-loaded and every clause carries relevant information, but the single dense paragraph is longer than strictly necessary and could benefit from scannable structure. It remains informative and free of filler.

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?

For a tool with an output schema already present, the description covers inputs, edge cases, output contents, return transport, and non-fatal warnings. Nothing an agent needs to invoke or understand the result is missing.

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?

Despite 100% schema coverage, the description enriches every parameter: acceptable image formats and ideal dimensions, the visual meaning of each variants enum value, and the colour's dual role in flattening and padding. This adds meaning well beyond the JSON schema.

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 opens with a specific verb and resource: 'Turns one source image into a ZIP' containing concrete iOS and Android icon sets. It enumerates the exact artifacts (AppIcon.appiconset, mipmap ic_launcher files, sizes folder) and distinguishes itself from sibling generators like generate_favicon_set by platform and output structure.

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

Usage Guidelines4/5

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

The description gives strong operational context: ideal input is 1024x1024, non-square images are letterboxed, transparency is flattened, and the dark variant should be reviewed before shipping. It doesn't explicitly name when to choose this over generate_favicon_set or other resize tools, so it stops short of a 5.

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

generate_favicon_setGenerate a favicon and web-app-manifest setA
Read-onlyIdempotent
Inspect

Turns one source image into a complete favicon set: favicon.ico (a real multi-resolution ICO container holding 16, 32 and 48px PNG-encoded entries -- not a single-size file with an .ico extension), favicon-96x96.png, apple-touch-icon.png (180px, flattened onto backgroundColor because iOS paints transparency black there), web-app-manifest-192x192.png and web-app-manifest-512x512.png (optionally padded to Android's maskable safe zone -- the artwork shrunk to a centred 80%-width square on the background colour, reproducing the source tool's square padding rather than clipping to the actual inscribed-circle safe area), site.webmanifest, and the exact 5-line HTML snippet (favicon.ico link, favicon-96x96.png link, apple-touch-icon link, apple-mobile-web-app-title meta, manifest link) ready to paste. A non-square source is centred on a transparent square canvas first, so nothing is stretched. Ported from GO AI's browser-based favicon tool, run server-side with @napi-rs/canvas instead of a DOM . Every PNG and the .ico are small (a few KB to worst-case a couple hundred KB), so all six files are returned individually rather than zipped -- each name matters (favicon.ico and site.webmanifest belong at the site root) and there is no batching benefit to a ZIP at this size. The JSON result carries the manifest JSON text, the HTML snippet text, a per-file table of what each output is for, and a warning when the source is smaller than 512px on its longer side (the 512 icon will be upscaled). Fails with a clear message rather than throwing if the input cannot be decoded as an image.

ParametersJSON Schema
NameRequiredDescriptionDefault
siteNameNoFull site name, used as the manifest's "name" field and as the fallback for short_name / the apple-mobile-web-app-title meta tag when shortName is empty.My site
shortNameNoShort name for a home-screen label (roughly 12 characters fit). Used as the manifest's "short_name" and in the apple-mobile-web-app-title meta tag; falls back to siteName when empty.
themeColorNoHex colour (# optional, 3- or 6-digit) written to the manifest's theme_color field. Purely metadata -- never painted onto any icon.#0a0a0c
imageBase64YesSource image, base64-encoded. Any raster/SVG format @napi-rs/canvas can decode (PNG, JPEG, WebP, GIF, SVG). May be non-square and may carry transparency -- it is centred on a square canvas before anything else happens to it. Capped by this server's per-input byte limit.
backgroundColorNoHex colour (# optional, 3- or 6-digit) used three ways: the manifest's background_color field, the flatten colour behind the apple-touch-icon (iOS does not honour alpha there), and the padding colour around the maskable-cropped manifest icons.#ffffff
maskablePaddingNoWhen true (default), the 192x192 and 512x512 manifest icons are padded: the artwork is shrunk to a centred 80%-width square on the background colour, matching Android's maskable-icon safe zone, and the manifest icons' "purpose" is set to "maskable". When false, those two icons are the plain resized artwork with "purpose": "any".

Output Schema

ParametersJSON Schema
NameRequiredDescription
tableYesEvery generated file and what it is for, so the set can be checked without unzipping it.
snippetYesThe <link> and <meta> tags to paste into the page head, matching the files that were generated.
warningYesA quality caution about the source image, chiefly that it was not square or too small. Null when there is nothing to flag.
manifestYesA complete web app manifest as JSON text, ready to serve as site.webmanifest.
themeColorYesTheme colour written into the manifest and meta tags.
sourceWidthYesSource image width in px.
squaredSideYesSide length of the square canvas the source was fitted onto, in px.
sourceHeightYesSource image height in px.
backgroundColorYesBackground colour written into the manifest.
maskablePaddingYesWhether the maskable icon was padded to survive Android's safe-zone crop.

TDQS

A4.2/5.0
Behavior5/5

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

Even with annotations already declaring readOnly, idempotent, and non-destructive hints, the description adds critical behavioral detail: real multi-resolution ICO structure, iOS apple-touch-icon flattening on backgroundColor, maskable safe-zone padding, an upscaling warning for sources under 512px, and graceful failure on undecodable input. These go well beyond the structured annotations.

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

Conciseness4/5

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

The description is a dense single paragraph, but each parenthetical earns its place: ICO structure, iOS transparency, maskable padding, warning, and failure mode. It is front-loaded with the output list. Slight structure improvements with bullets could help readability, but nothing is filler.

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?

For a 6-parameter tool with an output schema, the description is fully complete: it covers the complete output set, source-image centering behavior, iOS flattening, Android maskable padding, the 512px upscaling warning, and error handling. Nothing an agent needs to invoke it correctly is missing.

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 100% and every parameter has a detailed schema description, including defaults and use cases. The tool description adds little parameter-level meaning beyond the schema; its mentions of backgroundColor and maskablePadding behaviors are already covered in the input schema.

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 opens with a specific verb and resource — "Turns one source image into a complete favicon set" — then enumerates every output file and the HTML snippet. This clearly distinguishes it from the sibling generate_app_icon_set, which targets a different resource.

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

Usage Guidelines3/5

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

The description states what the tool does but never explicitly says when to use it over alternatives or names exclusions. The purpose is evident, so usage is implied, but an agent comparing it to generate_app_icon_set has to infer the distinction from the title and output list rather than being guided.

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

image_compressCompress an image (JPEG/WebP/AVIF)A
Read-onlyIdempotent
Inspect

Downscales an image to fit within a maximum dimension (default 1600px on the longer side, never enlarges -- matching GO AI's browser compressor tool) and re-encodes it as JPEG, WebP or AVIF at a given quality (1-100 scale, default 75). Input is base64-encoded image bytes (any format sharp/libvips can decode: JPEG, PNG, WebP, AVIF, GIF, TIFF, ...), not a file path or URL, capped at this server's input size limit. Returns the compressed image bytes plus a stats object: original and compressed dimensions and byte sizes, and the percent size change (positive = smaller, negative = the re-encode came out bigger, which happens when compressing an already-small, already-compressed image). Re-encoding always strips EXIF/ICC metadata, exactly like the source tool.

ParametersJSON Schema
NameRequiredDescriptionDefault
formatNoOutput format. 'webp' (default, matches the source's own default whenever WebP is available) is smaller than JPEG at equivalent visual quality; 'jpeg' is the safest choice for old viewers or print; 'avif' is smaller still but slower to encode.webp
qualityNoEncoder quality, 5-100 (matches the source's slider range). Defaults to 75. 100 is the least-lossy setting the encoder offers, not truly lossless.
imageBase64YesBase64-encoded source image bytes (not a file path or URL).
maxDimensionNoLonger-side cap in pixels before encoding; the image is downscaled to fit inside this (aspect preserved) and is never enlarged. Defaults to 1600, matching the source.

Output Schema

ParametersJSON Schema
NameRequiredDescription
formatYesOutput format used.
qualityYesQuality level the image was encoded at.
workingYesThe image after being fitted to maxDimension but before re-encoding. Never an enlargement.
originalYesThe image as supplied.
compressedYesThe returned file. Its bytes come back as a separate content block.
percentChangeYesSize change against the original as a percentage; negative means smaller. Can be positive when re-encoding an already-optimised file.

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already mark this as read-only, idempotent, and non-destructive, and the description adds substantial behavioral detail beyond that: it never enlarges, always strips EXIF/ICC metadata, can produce a larger output than input (negative percent change), and quality 100 is not truly lossless. This gives the agent an accurate model of edge-case behavior.

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 dense but every sentence earns its place: main behavior first, then input constraints, output shape, and edge cases. It is front-loaded with the core action and uses the remaining sentences to clarify exactly what the agent should expect, without fluff or repetition of the schema.

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 moderate complexity, full schema coverage, rich annotations, and an output schema, the description is complete. It covers input format and limits, output stats semantics, metadata stripping, enlargement behavior, and format tradeoffs. An agent has everything needed to invoke it correctly and interpret results.

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%, yet the description still adds meaning beyond the schema: maxDimension is explained as a longer-side cap that never enlarges, quality is tied to the source slider range with 100 clarified as least-lossy, and imageBase64 is described as accepting any format sharp/libvips can decode and being subject to a server input limit. This goes well beyond the baseline for full schema coverage.

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 opens with a specific verb and resource: 'Downscales an image... and re-encodes it as JPEG, WebP or AVIF.' It clearly distinguishes itself from file-path-based tools by stating input is base64-encoded image bytes, not a file path or URL, and from pure resizers by emphasizing re-encoding and compression output.

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

Usage Guidelines4/5

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

The description gives clear context for when to use the tool: compressing an image to a max dimension and re-encoding at a quality level. It explicitly warns that input must be base64 bytes rather than a file path or URL, which prevents a common misuse. It does not name sibling alternatives or state when-not-to-use, so it stops short of a 5.

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

image_compression_curveImage compression size-vs-quality curveA
Read-onlyIdempotent
Inspect

Analysis-only tool (no image bytes returned): re-encodes an image at the same fixed 14 quality levels GO AI's browser compressor samples to draw its size-against-quality curve (5, 10, ..., through 100 -- see the qualities in each returned point), for JPEG, WebP or AVIF, and reports the resulting byte size and percent change at each level. The image is first downscaled to fit within maxDimension (default 1600px on the longer side, never enlarged), exactly as the source's working canvas is, then every quality level is encoded one at a time. Because this repeats the encode 14 times, its maxDimension is capped at 2000px -- lower than image_compress's 4000px -- which costs nothing in practice, since the knee of the curve is a property of the image's content and sits in the same place at either size. Use this to find that 'knee' (where size stops dropping much per quality point) for a specific image, or image_compress to actually get the compressed bytes at one chosen quality. Input is base64-encoded image bytes, not a file path or URL.

ParametersJSON Schema
NameRequiredDescriptionDefault
formatNoFormat to sample the curve in. Defaults to 'webp', matching the source's own default.webp
imageBase64YesBase64-encoded source image bytes (not a file path or URL).
maxDimensionNoLonger-side cap in pixels before encoding, aspect preserved, never enlarged. Defaults to 1600, matching the source. Capped at 2000 here (vs 4000 for image_compress) because this tool runs the encode 14 times.

Output Schema

ParametersJSON Schema
NameRequiredDescription
formatYesFormat every point was encoded in.
pointsYesThe 14 fixed sample points, ascending by quality. The 'knee' -- where size stops dropping much per quality point -- is what this tool exists to locate. No image bytes are returned.
workingYesThe downscaled image every quality level was encoded from. The knee sits in the same place at either size, so this does not distort the curve.
originalYesThe image as supplied.

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already cover safety (readOnlyHint, idempotentHint, destructiveHint=false), and the description adds substantial behavioral context on top: the 14 repeated encodes, the downscaling-to-maxDimension pipeline, no image bytes returned, and the cap rationale ('costs nothing in practice, since the knee of the curve is a property of the image's content'). No contradiction with annotations.

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

Conciseness4/5

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

Dense but well-organized: core behavior, pipeline, limitation with justification, usage routing, input format. Every sentence earns its place, though a few points (input format, never-enlarged) repeat schema text and the caveat about the knee's position is somewhat elaborate. Front-loading is excellent.

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?

Complete for a tool of this complexity: it specifies the fixed quality levels, the supported formats, the downscale behavior, the cap and its rationale, the output shape (byte size and percent change per point), and the sibling routing. The output schema exists and annotations cover the safety profile, so the description needn't repeat those.

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

Parameters4/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; the description adds real value by explaining the why behind maxDimension's cap (14 encodes) and why that cap doesn't hurt accuracy. It also clarifies the input contract (base64 bytes, not path/URL) in prose. Slightly redundant with schema text on defaults, hence 4 rather than 5.

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?

Opens with the defining trait ('Analysis-only tool (no image bytes returned)') and states a specific action: re-encode at 14 fixed quality levels and report byte size and percent change per level. It explicitly differentiates from the sibling image_compress ('or image_compress to actually get the compressed bytes'), so an agent can distinguish them 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.

Usage Guidelines5/5

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

Gives an explicit selection rule: 'Use this to find that knee... for a specific image, or image_compress to actually get the compressed bytes at one chosen quality.' It also surfaces the operational difference (2000px cap vs image_compress's 4000px) and explains why that difference is practically irrelevant, removing a potential reason for an agent to wrongly pick the other tool.

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

inspect_mobileprovisionInspect a provisioning profileA
Read-onlyIdempotent
Inspect

Reads an iOS/macOS provisioning profile (.mobileprovision or .provisionprofile, given as base64) by byte-searching its CMS/PKCS7-signed container for the embedded '<?xml ... ' property list and parsing that XML -- it never validates or decodes the cryptographic signature, and does not report on the embedded certificates beyond how many there are. Reports expiry status and days remaining, profile type (development/ad hoc vs. enterprise vs. App Store distribution, classified only from the registered-device list and the ProvisionsAllDevices flag), team and application identifiers, the full entitlements dictionary, and the count and UDIDs of registered devices. Returns a structured { ok: false, error, message } result (not a thrown error) for an unreadable input, a file with no embedded plist, or a plist that fails to parse.

ParametersJSON Schema
NameRequiredDescriptionDefault
base64YesBase64-encoded raw bytes of the .mobileprovision or .provisionprofile file.

Output Schema

ParametersJSON Schema
NameRequiredDescription
okYesAlways true on a readable profile. An unreadable file, a file with no embedded plist, or a plist that will not parse comes back as an error result instead, with the reason as its text.
nameYesThe profile's name, or null if the plist omits it.
uuidYesThe profile's UUID.
expiryYesWhether this profile still works, which is the question most callers are actually asking.
devicesYesThe registered device UDIDs, normally strings. Empty when the profile registers none.
platformYesPlatforms the profile covers, normally an array such as ['ios']. Null when absent.
teamNameYesDeveloper team name.
appIdNameYesThe App ID name registered with the profile.
createdDateYesCreation date as an ISO 8601 string, or null when absent or unparseable.
deviceCountYesNumber of registered device UDIDs; 0 for an App Store or enterprise profile.
profileTypeYesDevelopment/ad hoc vs. enterprise vs. App Store distribution.
rawPlistXmlYesThe embedded property list as raw XML, for anything this report does not surface.
entitlementsYesThe full entitlements dictionary verbatim from the profile, keys and values as the plist held them.
expirationDateYesExpiry date as an ISO 8601 string, or null when absent or unparseable.
teamIdentifierYesTeam identifiers from the plist, normally an array of one team ID string. Null when absent.
timeToLiveDaysYesThe profile's TimeToLive in days, or null when absent.
certificateCountYesHow many signing certificates are embedded. The certificates themselves are not decoded -- only counted.
applicationIdentifierYesThe application-identifier entitlement, i.e. team ID plus bundle ID -- the field that says what this profile actually signs.

TDQS

A4.5/5.0
Behavior5/5

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

Even though annotations already declare readOnlyHint=true and idempotentHint=true, the description adds substantial behavioral detail: byte-searching the CMS/PKCS7 container, never validating or decoding the signature, not reporting certificate details beyond count, using only specific fields for profile type classification, and returning a structured { ok: false, error, message } result instead of throwing. This goes far beyond the annotations and is highly valuable for an agent deciding whether to trust results.

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 dense but every sentence earns its place: primary behavior, parsing method, non-behaviors, reported fields, and error handling. It front-loads the core action before enumerating outputs, and despite length, it contains no filler or repetition.

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?

For a tool of this complexity with an output schema available, the description is exceptionally complete. It covers input format, parsing internals, limitations, all reported result categories, and error behavior, leaving no practical question unanswered about what the tool does or how it behaves.

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?

The input schema already fully describes the single parameter 'base64' as 'Base64-encoded raw bytes of the .mobileprovision or .provisionprofile file,' and schema coverage is 100%. The description essentially repeats this information ("given as base64") without adding deeper formatting, encoding, or size guidance, so it reaches the baseline but no more.

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 tool reads and parses iOS/macOS provisioning profiles from base64 input, with a specific verb ('Reads') and resource ('provisioning profile'). It lists the exact reported outputs (expiry status, profile type, identifiers, entitlements, device UDIDs), making it unmistakable among the sibling tools, none of which are similar.

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

Usage Guidelines4/5

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

The description implies when to use the tool: whenever you need to inspect the contents of a .mobileprovision or .provisionprofile file, including expiry, type, entitlements, and device list. It gives clear context but does not explicitly name alternatives or state when not to use it; it merely notes that signature validation is not performed, which is more a behavioral limitation than a usage exclusion.

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

oklch_convertConvert an OKLCH / hex / RGB / HSL colorA
Read-onlyIdempotent
Inspect

Converts a color to and from OKLCH. Accepts either a CSS color string via color (hex, rgb()/rgba(), hsl()/hsla(), or oklch()) or explicit OKLCH lightness/chroma/hue via l, c and h — supply one or the other. Returns the canonical oklch() text for the color exactly as requested, plus its sRGB-gamut-mapped hex/rgb()/hsl() equivalents. Gamut mapping follows the CSS Color 4 algorithm (bisecting on chroma at constant lightness and hue, not channel-clipping) and only targets sRGB — there is no P3 or Rec2020 support.

ParametersJSON Schema
NameRequiredDescriptionDefault
cNoOKLCH chroma. sRGB's most saturated colour reaches about 0.31; the source page's own slider goes to 0.4 so out-of-gamut requests are possible on purpose. Used (with l and h) only when color is omitted.
hNoOKLCH hue in degrees, 0-360. Used (with l and c) only when color is omitted.
lNoOKLCH lightness, 0 (black) to 1 (white). Used (with c and h) only when color is omitted.
colorNoCSS color to convert from — hex ('#5b8cff' or '#58f'), rgb()/rgba(), hsl()/hsla(), or oklch(). Takes precedence over l/c/h when both are supplied.

Output Schema

ParametersJSON Schema
NameRequiredDescription
hexYesThe sRGB result as a hex string.
hslYesThe same colour in HSL, for code that still expects it.
rgbYesThe same colour as 8-bit sRGB channels.
mappedYesTrue when the colour was gamut-mapped to fit sRGB, meaning hex below is a near miss rather than the exact request.
hslTextYesThe colour as a CSS hsl() declaration.
inGamutYesWhether the requested colour exists in sRGB.
rgbTextYesThe colour as a CSS rgb() declaration.
oklchTextYesThe colour as a CSS oklch() declaration.
requestedYesThe OKLCH coordinates that were asked for, before any gamut mapping -- keep these to see how far the sRGB answer had to move.

TDQS

A4.7/5.0
Behavior5/5

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

Annotations already mark the tool as read-only and idempotent, and the description goes well beyond those by explaining the exact return shape, the canonical oklch() output, the sRGB-gamut-mapped equivalents, and the specific CSS Color 4 gamut-mapping algorithm. This gives an agent a strong behavioral model before invoking the tool.

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 dense but every sentence earns its place: it covers input modes, output form, gamut mapping behavior, and a key limitation. The most important usage constraint is front-loaded, and there is no redundant or filler content.

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 output schema already documents return values, the description covers all necessary invocation context: accepted input formats, parameter precedence, mutual exclusivity, gamut-mapping behavior, and the sRGB-only constraint. An agent has enough information to call the tool correctly and interpret its output in context.

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

Parameters4/5

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

Schema coverage for all four parameters is 100%, so the baseline is met. The description adds important relational meaning by clarifying that l/c/h should be used only when `color` is omitted and that `color` takes precedence when both are supplied, which helps an agent select the correct parameter combination.

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 ('Converts a color to and from OKLCH') and names the exact input forms and output forms. It is immediately distinguishable from sibling tools like oklch_ramp or check_contrast because it clearly identifies this as a color-space converter with canonical OKLCH output.

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

Usage Guidelines4/5

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

The description clearly explains the two mutually exclusive input modes ('supply one or the other') and notes that `color` takes precedence over l/c/h. It also gives a useful exclusion by stating there is no P3 or Rec2020 support, though it does not explicitly name alternative sibling tools for those cases.

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

oklch_rampBuild an OKLCH swatch ramp / CSS custom-property blockA
Read-onlyIdempotent
Inspect

Builds a perceptually even OKLCH swatch ramp from a base color (same color or l/c/h input as oklch_convert) by sweeping one channel — hue, lightness, or chroma — while holding the other two fixed, so every swatch keeps the same visual weight (this is the reason to build a ramp in OKLCH rather than HSL). Each step is gamut-mapped to sRGB with the CSS Color 4 chroma-reduction algorithm and returned with its hex preview, plus a ready-to-paste :root { --name-1: oklch(...); ... } CSS block.

ParametersJSON Schema
NameRequiredDescriptionDefault
cNoOKLCH chroma. sRGB's most saturated colour reaches about 0.31; the source page's own slider goes to 0.4 so out-of-gamut requests are possible on purpose. Used (with l and h) only when color is omitted.
hNoOKLCH hue in degrees, 0-360. Used (with l and c) only when color is omitted.
lNoOKLCH lightness, 0 (black) to 1 (white). Used (with c and h) only when color is omitted.
modeNoWhich channel the ramp sweeps while holding the other two fixed: 'hue' rotates hue around the wheel, 'light' sweeps lightness from a near-black floor to a near-white ceiling, 'chroma' sweeps chroma from 0 up to the base colour's own chroma.hue
nameNoStem for the generated CSS custom properties (e.g. 'brand' produces --brand-1, --brand-2, ...); sanitized to [a-z0-9-] the same way the source page does.brand
colorNoCSS color to convert from — hex ('#5b8cff' or '#58f'), rgb()/rgba(), hsl()/hsla(), or oklch(). Takes precedence over l/c/h when both are supplied.
stepsNoNumber of swatches in the ramp, 3-24.

Output Schema

ParametersJSON Schema
NameRequiredDescription
cssYesThe whole ramp as CSS custom properties, ready to paste into a stylesheet.
baseYesThe OKLCH coordinates the ramp was generated from.
modeYesWhich coordinate was varied across the steps, echoed from the input.
stepsYesThe ramp in order. Because each step is mapped independently, some can be mapped and others not.

TDQS

A4.5/5.0
Behavior5/5

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

Annotations already declare readOnly, idempotent, and non-destructive, so the description adds substantial behavioral value: gamut-mapping to sRGB via CSS Color 4 chroma reduction, returning hex previews, generating a ready-to-paste CSS block, and holding two channels fixed while sweeping one. No contradiction with annotations.

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?

Two dense sentences with no filler. The first sentence front-loads the core purpose and rationale, and the second covers the output format and gamut handling. Every clause 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?

For a 7-parameter tool with full schema coverage and an output schema, the description is complete: it explains what the tool does, why OKLCH is used, how channels are swept, how gamut mapping works, and what the return payload includes. Nothing essential is missing.

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 coverage is 100%, so the baseline is 3. The description adds some useful context by framing the inputs as 'same color or l/c/h input as oklch_convert' and explaining the ramp mechanism, but it mostly restates what the schema already documents for mode and steps.

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 ('Builds'), a precise resource ('OKLCH swatch ramp'), and the exact mechanism (sweeping one channel while holding the other two fixed). It clearly distinguishes this from oklch_convert by describing the multi-swatch ramp output and the CSS custom-property block.

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

Usage Guidelines4/5

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

The description gives clear usage context: build a ramp when you need perceptually even swatches, and it explicitly references oklch_convert as the shared input convention and contrasts with HSL. It does not explicitly say 'use oklch_convert instead for a single color conversion,' but the distinction is strongly implied.

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

plant_watering_calendarPlant watering calendar (.ics)A
Read-onlyIdempotent
Inspect

Builds a soil-check interval per houseplant from a fixed drought-tolerance table (32 common houseplants), adjusted by pot size, pot material, light and season, and returns both a schedule breakdown and the full text of a downloadable RFC 5545 .ics calendar file -- one recurring all-day 'check the soil' reminder per plant (deliberately never 'water', since only the plant's own soil can say that). Matches GO AI's browser watering-calendar tool exactly, including its northern-hemisphere-season default when season is omitted, its terracotta/glazed/low-light/winter multipliers, and its 75-octet .ics line folding. The starting intervals are heuristic drought-tolerance bands, not a measurement of any specific plant, pot or room -- the tool says so in its own FAQ.

ParametersJSON Schema
NameRequiredDescriptionDefault
lightNoLight level: bright direct sun dries soil fastest, low light slowest. Defaults to bright-but-indirect.indirect
plantsYesHouseplant ids to include (each event uses this exact catalog, no free text). Duplicates are ignored. Ids: snake, zz, aloe, jade, echeveria, cactus, hawortia, ponytail, rubber, monstera, pothos, philodendron, fiddle, dracaena, yucca, schefflera, spider, peaceLily, anthurium, orchid, birdOfPara, parlour, kentia, areca, chinese, prayer, calathea, fern, maidenhair, fittonia, begonia, africanViolet.
seasonNoCurrent season, which scales every interval (summer fastest, winter roughly 60% slower). If omitted, defaults from today's calendar month assuming the *northern* hemisphere, exactly like the source page -- pass this explicitly for a southern-hemisphere season or to plan for a season other than the current one.
potSizeNoPot diameter band: small (under 12cm), medium (12-25cm, default), large (over 25cm). A larger pot holds more soil and dries more slowly.medium
startDateNoFirst reminder date, YYYY-MM-DD. Every plant's recurring event starts on this same date, each with its own repeat interval. Defaults to today.
potMaterialNoUnglazed terracotta breathes and dries noticeably faster than plastic (default) or glazed ceramic.plastic

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYesStanding caution that these intervals are a starting point to adjust against the actual soil.
summaryYesThe set at a glance, for deciding a single watering day.
calendarYesThe schedule as a subscribable calendar. Returned inline as text, not as a file download.
scheduleYesOne entry per requested plant, in catalogue order.

TDQS

A4.5/5.0
Behavior5/5

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

Beyond the annotations (readOnlyHint, idempotentHint, destructiveHint=false), the description adds substantial behavioral context: the output format, the one-recurring-event-per-plant design, the deliberate refusal to label reminders as 'water', the exact compatibility target including northern-hemisphere defaults and 75-octet line folding, and the heuristic nature of the intervals. No contradiction exists between the description and 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.

Conciseness5/5

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

The description is dense but every sentence earns its place: the first sentence states the core function and output, the second captures compatibility and exact behavioral matching, and the third adds a necessary caveat about heuristic values. It is front-loaded with the most important information and contains no filler or repetition of schema details.

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 (6 parameters, 32-plant catalog, computed schedule, .ics generation), the description covers the key behavioral model, output format, defaults, compatibility, and limitations. An output schema exists to document return values, so the description does not need to restate them. Nothing essential for an agent to select and invoke the tool correctly is missing.

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 100%, and the input schema already explains each parameter's meaning, defaults, and effect (e.g., larger pot dries slower, terracotta dries faster, season defaults from northern hemisphere). The description adds overall algorithm context but does not meaningfully deepen per-parameter semantics beyond what the schema already provides, so the baseline score of 3 is appropriate.

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 opens with a specific action and object: it builds a soil-check interval per houseplant from a fixed drought-tolerance table and returns both a schedule breakdown and an RFC 5545 .ics file. It also clarifies the exact scope (32 common houseplants) and the deliberate distinction between 'check the soil' and 'water', making the purpose unmistakable and easily distinguishable from any sibling tool.

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

Usage Guidelines4/5

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

The description clearly conveys when this tool is appropriate: when generating soil-check reminder calendars for houseplants, and specifically when matching GO AI's browser watering-calendar tool. It does not explicitly name excluded alternatives, but the sibling list contains no comparable tool, and the description is specific enough that an agent would not confuse it with the other calculators or app-store utilities.

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

project_weight_goal_dateWeight loss goal-date projectorA
Read-onlyIdempotent
Inspect

Projects the calendar date a goal body weight is reached under a fixed daily calorie intake. Unlike a naive straight-line calculator (kg to lose x 7700 / deficit, also returned here for comparison), this recomputes Mifflin-St Jeor maintenance calories at each simulated day as weight falls, so the effective deficit narrows the way it really does -- meaning the projected date is later than, or equal to, the naive one, never earlier. Refuses to project a date when the resulting intake falls below a 1500 kcal (men) / 1200 kcal (women) floor, and reports a 'plateau' outcome instead of a date when the goal sits at or below the weight where maintenance would settle at that intake (an asymptote that a fixed intake alone can never cross). Adult-only inputs (18+); only projects weight loss (goalWeight must be less than currentWeight).

ParametersJSON Schema
NameRequiredDescriptionDefault
ageYesAge in years, 18-120. The source page is not validated for children; 18+ only.
sexYesBiological sex, used by the Mifflin-St Jeor formula.
unitsNometric = cm/kg, imperial = ft+in/lb. Applies to height and both weight fields.metric
heightCmNoHeight in centimeters. Required when units is 'metric'.
heightFtNoFeet part of height. Required when units is 'imperial'.
heightInNoInches part of height (0-11). Required when units is 'imperial'.
goalWeightYesGoal body weight, same unit as currentWeight. Must be less than currentWeight -- this tool only projects weight loss, matching the source page.
activityLevelYesActivity multiplier bucket, the site's five options: sedentary x1.2 (desk job, no exercise), light x1.375 (1-3 sessions/week), moderate x1.55 (3-5 sessions/week, the site default), very_active x1.725 (6-7 sessions/week), extra_active x1.9 (physical job or 2x/day training).
currentWeightYesCurrent body weight, kg if units is metric, lb if imperial.
referenceDateNoISO date (YYYY-MM-DD) the projection counts forward from. Defaults to today (UTC) when omitted.
dailyDeficitKcalYesDaily calorie deficit to hold against today's computed maintenance. The site's quick picks are 250/500/750/1000 kcal, but any positive number is accepted.
includeChartSeriesNoIf true, also return the weekly weight series and the naive straight-line end week -- the same two lines the source page charts. Set false to skip them for a lighter response.

Output Schema

ParametersJSON Schema
NameRequiredDescription
noteYesPlain-language reading of the result, including why the two answers differ or why the goal is unreachable.
seriesYesWeekly projection points for plotting. Null when includeChartSeries was false.
statusYesHow the projection ended: the goal is reached, or it settles short of the goal, or it is unreachable at this intake. The field to branch on before reading the dates.
gapDaysYesHow many days longer the honest answer is than the naive one -- the whole point of the tool. Null when the goal is never reached.
plateauYesTrue when the settle weight is above the goal, i.e. this deficit alone will never get there. Null when it does not apply.
floorKcalYesThe intake floor that was enforced, below which the projection is not modelled.
naiveDaysYesDays predicted by the flat 7,700 kcal-per-kg rule that ignores the falling maintenance -- the figure most calculators stop at.
daysToGoalYesDays to the goal under the adaptive model, or null when the goal is never reached.
intakeKcalYesDaily intake implied by the requested deficit, after the safety floor is applied.
naiveWeeksYesThe same figure in whole weeks.
horizonDaysYesLength of the modelled horizon in days, or null when not modelled.
weeksToGoalYesThe same figure in whole weeks.
goalWeightKgYesTarget weight in kg, after any imperial conversion.
projectedDateYesCalendar date the goal is reached, ISO YYYY-MM-DD, or null when it is not reached.
settleWeightKgYesThe weight this intake eventually settles at, since maintenance falls as weight does. Null when it does not apply.
currentWeightKgYesStarting weight in kg, after any imperial conversion.
maintenanceKcalYesMaintenance calories at the starting weight.
naiveLineEndWeekYesWhere the naive straight line would end on the same axes, so the two can be drawn together. Null when there is no series.
activityMultiplierYesThe multiplier the chosen activity level maps to.
reachedWithinHorizonYesWhether the goal is reached inside the modelled horizon.

TDQS

A4.7/5.0
Behavior5/5

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

Even with readOnlyHint, idempotentHint, and destructiveHint already present, the description adds substantial behavioral detail: it recomputes Mifflin-St Jeor maintenance daily, the projected date is never earlier than the naive one, it refuses intakes below sex-specific floors, and it reports a plateau outcome at the asymptote. This goes far beyond the annotations and tells the agent exactly what the tool will and won't return.

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 dense but every sentence earns its place: core purpose first, then the algorithmic distinction, then refusal and plateau edge cases, then input restrictions. There is no filler, and the most decision-relevant facts are front-loaded.

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, 12 parameters, and output schema, the description is remarkably complete. It covers the algorithm, edge cases, refusal behavior, the plateau outcome, adult-only inputs, and the loss-only constraint. The output schema exists, so not detailing return values is acceptable, and nothing critical appears missing for an agent to invoke this correctly.

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

Parameters4/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, and the description does add meaning beyond the schema by explaining parameter interactions: the 1500/1200 kcal floor ties sex to dailyDeficitKcal, and the plateau/asymptote discussion clarifies what goalWeight values will produce no date. It doesn't walk through every parameter, but the schema already handles that, so the additional semantics are genuinely valuable.

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 opens with a specific verb and resource: it 'Projects the calendar date a goal body weight is reached under a fixed daily calorie intake.' It also distinguishes itself from the naive straight-line calculator and explains the key algorithmic difference, so an agent can clearly identify what this tool does and how it differs from a simpler projection approach.

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

Usage Guidelines4/5

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

The description gives clear when-to-use context: weight-loss projection with a fixed daily deficit, and it explicitly lists when-not conditions: adult-only inputs, goalWeight must be less than currentWeight, and refusal scenarios that yield a plateau outcome. It does not name alternative sibling tools explicitly, though the naive calculator is mentioned as a comparison, so the routing guidance is strong but not fully exhaustive.

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

recipe_convert_ingredientCup-to-grams converter (per-ingredient density)A
Read-onlyIdempotent
Inspect

Converts an amount of one ingredient between cup, tablespoon, teaspoon, millilitre, gram and ounce using that specific ingredient's own USDA FoodData Central portion weight, not a single water-based figure applied to everything -- a cup of flour (125 g) and a cup of honey (339 g) do not weigh the same. Supports an ingredient's own measurement-style variants where USDA publishes more than one (packed vs loose brown sugar, sifted vs unsifted powdered sugar, whole vs sliced/slivered/ground nuts, etc.), and for baking powder, baking soda and yeast uses USDA's own published teaspoon weight rather than dividing the cup weight by 48, since a spoon measurement of a fine powder is not proportionate to its cup measurement. Also reports, for information, what a generic water-density converter would have said for the same input and by how much that would have been wrong -- omitted when the requested unit is already grams or ounces.

ParametersJSON Schema
NameRequiredDescriptionDefault
unitYesUnit the amount is given in.
amountYesAmount as a decimal ("1.5"), a simple fraction ("3/4"), or a mixed number ("1 1/2").
variantNoMeasurement style for ingredients that have more than one USDA figure (e.g. sugar-brown: "packed"/"loose"; sugar-icing: "unsifted"/"sifted"; almonds: "whole"/"sliced"/"slivered"/"ground"). Omit for ingredients with only one style.
cupSizeMlNoCup size in ml, for the 'cup' unit (and 'tbsp'/'tsp' on ingredients without their own USDA spoon weight). Defaults to the US customary cup (236.588 ml); other common values are 240 (US nutrition-label cup) or 250 (metric cup). Does not affect the reported density, which is always derived from the customary cup regardless of this value, matching the source tool.
ingredientYesWhich ingredient to convert, identified by its GO AI recipe-tool id (e.g. "flour-ap", "sugar-brown", "honey").

Output Schema

ParametersJSON Schema
NameRequiredDescription
gramsYesThe converted weight in grams, unrounded.
ouncesYesThe converted weight in ounces, unrounded.
gramsRoundedYesThe same weight rounded the way a kitchen scale reads it -- the number to actually use.
workSentenceYesThe conversion written out as an equation, so the caller can check the cup size and density rather than trusting a bare number.
densityGPerMlYesThe density used for this ingredient, in grams per millilitre.
ouncesRoundedYesThe same weight in ounces, rounded for practical use.
waterComparisonYesHow this ingredient compares in weight to the same volume of water -- the intuition that explains why a cup of flour and a cup of honey are nothing alike.
densityGPerMlRoundedYesThat density rounded for display.

TDQS

A4.6/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 substantive behavior: per-ingredient USDA portion weights, packed/sifted/whole variant handling, intentional teaspoon-weight treatment for baking powder/soda/yeast, and an informational comparison against a generic water-density conversion that is omitted for g/oz. This goes well beyond the structured annotations and informs correct expectation.

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

Conciseness4/5

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

Long but front-loaded: the core conversion purpose comes first, followed by illustrative examples and special-case behavior. Every sentence adds information; could be tightened by breaking into shorter sentences, but not padded.

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 a 5-parameter schema with full descriptions and an output schema, the description covers the tool's domain, exceptions, and extra output behavior, leaving no significant gap for an agent to know when and how to invoke it.

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

Parameters4/5

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

Schema coverage is 100%, so baseline is 3; description nonetheless adds meaning by explaining why ingredient is needed (per-ingredient densities differ) and by describing variant behavior and the special teaspoon handling for baking powder/soda/yeast. The parameter-level details like amount formats and cupSizeMl remain in the schema, but the conceptual context strengthens correct parameter selection.

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?

States a specific verb ('Converts') and resource ('an amount of one ingredient') with the full unit set, and further specifies per-ingredient USDA density, contrasting with water-based converters. The title and description together make it unmistakably a single-ingredient unit converter rather than a recipe scaler.

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

Usage Guidelines4/5

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

The description conveys a clear use case: converting one ingredient amount between cup/tbsp/tsp/ml/g/oz using USDA densities. It does not name recipe_scale or give exclusion criteria, so while context is clear, explicit differentiation from the closest sibling is missing.

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

recipe_scaleRecipe servings scaler with gram weightsA
Read-onlyIdempotent
Inspect

Scales a pasted recipe (one ingredient per array entry, e.g. '2 cups all-purpose flour') from one servings count to another, multiplying each line's leading amount by the ratio and reformatting it as a whole number or simple/mixed fraction rather than a decimal. When a line's unit and ingredient can both be recognised against GO AI's density table, appends the scaled amount's weight in grams in parentheses; lines already given in grams or ounces, lines whose ingredient isn't recognised, and lines that don't start with a parseable amount are returned unchanged (the last completely as-is, typos included).

ParametersJSON Schema
NameRequiredDescriptionDefault
linesYesOne ingredient per array entry, as free text (e.g. "2 cups all-purpose flour", "1/2 tsp table salt"), up to 500 lines of 1000 characters each. Lines with no parseable leading amount, or with an amount but no recognisable unit/ingredient after it, are returned unchanged.
cupSizeMlNoCup size in ml used when converting a scaled "cup"/"tbsp"/"tsp" amount to grams. Defaults to the US customary cup (236.588 ml); other common values are 240 or 250.
toServingsNoServings wanted. 0 (or an omitted field) falls back to 1, matching the source tool's own guard.
fromServingsNoServings the recipe as written makes. 0 (or an omitted field) falls back to 1, matching the source tool's own guard.

Output Schema

ParametersJSON Schema
NameRequiredDescription
linesYesThe ingredient lines rescaled, one output line per input line and in the same order. A line whose quantity could not be parsed is returned unchanged rather than dropped, so the list stays aligned with the recipe it came from.

TDQS

A4.2/5.0
Behavior5/5

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

Beyond the readOnly and idempotent annotations, the description discloses exact edge-case behavior: which lines are returned unchanged, when gram weights are appended, and that decimals are reformatted as whole numbers or fractions. It also clarifies that unparseable lines are copied verbatim, typos included, and none of this contradicts 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.

Conciseness4/5

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

The description is front-loaded with the primary action and has no filler. The second sentence is dense, covering multiple conditionals and exceptions in one long clause, but each element contributes material behavior or edge-case information.

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 full schema coverage, safety annotations, and presence of an output schema, the description covers all agent-relevant behavior: input format, scaling logic, output formatting, and fallback handling for unrecognized lines. No critical call-time decision is left unexplained.

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 coverage is 100%, so the schema already documents all four parameters in detail, including defaults and guard behavior. The description adds useful examples and processing context, but it does not materially extend the per-parameter semantics already present in the schema. A baseline 3 is appropriate.

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 opens with a specific verb-resource pair ('Scales a pasted recipe') and immediately states the transformation: multiplying leading amounts by the servings ratio and reformatting amounts as fractions. It also specifies the gram-weight append behavior, clearly distinguishing this batch recipe-scaler from a single-ingredient converter like recipe_convert_ingredient.

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

Usage Guidelines3/5

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

The description implies the tool is for scaling a pasted multi-line recipe and clearly describes the input shape. However, it does not explicitly name an alternative or state when to prefer it over sibling tools such as recipe_convert_ingredient, so the agent must infer the routing decision.

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

recommend_ai_modelRecommend which AI model to use for a taskA
Read-onlyIdempotent
Inspect

Static editorial recommendation -- NOT a live benchmark or leaderboard -- for which of six AI models (GPT-5, Gemini, Grok 4, Claude, DeepSeek, Kimi) to use for a given kind of task, ported from GO AI's own daily side-by-side-use judgement, current as of August 2026. Pick a task and get the recommended model plus the reasoning and a second-opinion backup. The optional priority can bias the pick toward cost (routes to DeepSeek) or freshness (routes to Grok) instead of the default quality pick -- but only for tasks where that tradeoff is actually offered; otherwise the quality default is returned unchanged. This reflects one team's opinion, not measured accuracy or pricing data.

ParametersJSON Schema
NameRequiredDescriptionDefault
taskYesThe kind of task, one of: write (Long or structured writing), copy (Punchy copy, hooks, social), edit (Editing my own draft), longdoc (Summarising long documents), code (Writing code), review (Reviewing code someone else wrote), math (Maths and calculations), news (Current events, what's trending), research (Research and synthesis), translate (Translation), brainstorm (Brainstorming ideas), voice (Talking out loud), prose (Prose you'll publish as-is), longinput (Very long inputs on a budget), image (Generating images), video (Generating video), bulk (Running the same prompt hundreds of times).
priorityNo"quality" (default) returns the task's default best-result pick. "cost" swaps to DeepSeek when this task supports a cheaper substitute. "fresh" swaps to Grok when this task supports a fresher/more current substitute. Has no effect for tasks that don't offer that tradeoff.quality

Output Schema

ParametersJSON Schema
NameRequiredDescription
tagYesShort label for why this model wins the task, e.g. its standout strength.
whyYesThe reasoning behind the recommendation.
noteYesWhat that adjustment was, when adjusted is true; otherwise null.
modelYesThe recommended model's display name.
backupYesA second choice worth trying if the first does not suit.
adjustedYesTrue when the stated priority (cost or freshness) moved the answer away from the quality-first pick.
modelKeyYesStable identifier for the recommended model, safe to switch on.
taskLabelYesHuman-readable name of the task that was matched.
disclaimerYesStanding note that this is a fixed editorial table, not a live benchmark.

TDQS

A4.5/5.0
Behavior5/5

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

Beyond the readOnlyHint/idempotentHint annotations, the description adds valuable behavioral context: results are static and editorial, current as of August 2026, based on one team's daily judgement, and not a measured benchmark. It also discloses the priority bias behavior and the edge case where priority has no effect, which aligns with and extends the structured metadata.

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?

Three dense sentences, each earning its place: the first scopes the tool and its limitations, the second states the core behavior and output, and the third explains the optional parameter and caveat. The key framing ('NOT a live benchmark') is front-loaded, and there is no filler.

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?

For a two-parameter, read-only tool with a rich output schema and annotations, the description provides everything an agent needs: what it does, the output shape, parameter behavior, staleness date, and reliability caveats. Nothing essential is missing for correct invocation.

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 100%: both `task` and `priority` are well-documented in the input schema, including the full list of task kinds and the meaning of each priority value. The description reinforces the cost/freshness routing (DeepSeek/Grok) already present in the schema but adds no new parameter-level meaning, so the baseline of 3 applies.

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 ('recommend') and resource (AI model choice), lists the exact six models, and clearly scopes the tool as a static editorial recommendation rather than a live benchmark. It also names the output ('recommended model plus the reasoning and a second-opinion backup'), making the tool's function unmistakable.

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

Usage Guidelines4/5

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

The description makes clear when to use the tool ('Pick a task and get the recommended model') and gives useful behavioral context about the optional priority parameter, including when it has no effect. It lacks an explicit 'use X instead of Y' sibling comparison, but the caveat 'NOT a live benchmark or leaderboard' and 'not measured accuracy or pricing data' help an agent decide when this opinion-based tool is appropriate.

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

render_app_store_screenshotApp Store screenshot generatorA
Read-onlyIdempotent
Inspect

Composite one or more app screenshots into a drawn device frame (rounded bezel, Dynamic Island on phone-shaped canvases) with a gradient background and optional headline/caption text, and export as PNG at any/all of the 4 official App Store Connect sizes. A blank page (image omitted) draws no device, for a title card or an outro. A device positioned past the edge of its page is drawn spilling onto the neighbouring page too, so a set can run one screenshot into the next. LIMITS — this runs on a small shared host, so an over-limit call is refused immediately, before any rendering, with a message saying how to split it: at most 10 pages at any one size, 2 at size:"all" (which renders every page 4 times over — a longer set needs one call per size); at most 12 rendered PNGs per call (pages x sizes); 6 MP per screenshot and 4 MB of screenshot bytes summed across the whole call (send JPEG, not PNG, for a multi-page set); and at most 40 text layers of 512 characters each.

ParametersJSON Schema
NameRequiredDescriptionDefault
sizeNoWhich official App Store Connect size to export: 6.9"/6.7"/6.5" iPhone or 13" iPad, or "all" for every size (returned as a ZIP). "all" renders 4 canvases per page, so it is limited to 2 pages per call; for a longer set, make one call per size — a single-size call carries all 10.6.9
pagesYesOne entry per exported page/PNG, in order. Use null for a blank page. At most 10 pages at any single size; size:"all" renders every page 4 times over and fits 2. Over that, the call is refused before any rendering.
textsNoZero or more text layers, at most 40. Order is z-order (later entries draw on top); each is confined to one page.
joinedSceneNoWhen true and there is more than one page, one gradient stretches across the whole set instead of repeating per page.
backgroundToNoGradient end color (bottom-right), hex.#2b2140
backgroundFromNoGradient start color (top-left), hex.#12121a

Output Schema

ParametersJSON Schema
NameRequiredDescription
sizesRenderedYesOne entry per rendered device size. The images themselves come back as separate content blocks -- a single render inline, a multi-page or multi-size batch as a ZIP behind a resource_link.

TDQS

A4.7/5.0
Behavior5/5

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

With annotations already declaring readOnly/idempotent/non-destructive, the description adds substantial behavioral context: over-limit calls are refused before rendering, a blank page draws no device, a device past a page edge spills onto the neighboring page, and 'all' size renders each page 4 times with a 2-page limit. These behaviors go far beyond what annotations convey.

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

Conciseness4/5

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

The description is long but well-structured, with the core purpose front-loaded and a clearly labeled LIMITS block. Nearly every sentence adds value, though a few constraints are restated from the schema (e.g., 10-page maximum, 40 text layers), which keeps it from being perfectly lean.

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?

For a tool with six parameters and an output schema, the description covers all critical context: output format and aggregation, blank-page and multi-page behavior, over-limit refusal semantics, per-image size constraints, and text-layer limits. Nothing an agent needs to correctly compose a call is missing.

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?

Although the schema already documents each parameter at 100% coverage, the description adds meaningful cross-parameter semantics: aggregate limits (pages x sizes, max 12 PNGs, 6 MB body cap, 4 MB summed screenshot bytes), the JPEG-before-PNG guidance for multi-page sets, and the page-spill interaction between device offsets and page boundaries. These constraints are not derivable from 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 opens with a specific verb and resource: 'Composite one or more app screenshots into a drawn device frame... and export as PNG at any/all of the 4 official App Store Connect sizes.' It clearly distinguishes itself from sibling image tools (app icon set, favicon, resize, compress) by naming the exact deliverable and the device-frame rendering behavior.

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

Usage Guidelines4/5

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

The description gives clear context for when to use the tool: when App Store Connect screenshots need to be composed and exported at official sizes. It also provides operational guidance (use JPEG for multi-page sets, split oversized calls per size), but it does not explicitly name alternatives or state when not to use it, so it lacks a full exclusionary comparison.

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

resize_imagesBatch image resizer with real presetsA
Read-onlyIdempotent
Inspect

Resizes a batch of 1+ images to a named real-world preset (App Store screenshots, Open Graph/social cards, Android launcher icon densities, a favicon set) or a custom width/height, using contain (whole picture, padded), cover (fills the frame, crops overflow) or stretch (distorts to fit) fitting. Each resized image is PNG-encoded (EXIF/ICC metadata stripped on re-encode, matching the source page) and every image is auto-rotated per its embedded EXIF orientation before resizing. All outputs are packaged into one uncompressed ZIP (PNG bytes are already compressed, so a second pass would not shrink them), foldered by preset (e.g. "social/photo-1200x630.png", or "android/mipmap-hdpi/icon.png" for the Android density preset, which names each size's folder after its density instead of suffixing the pixel size). This always returns as a resource_link (a batch ZIP is never small enough, or singular enough, to inline) -- fetch the link to get the archive. Per-file output dimensions and byte sizes are reported in the JSON result. Each input image (base64) is capped by this server's per-input byte limit.

ParametersJSON Schema
NameRequiredDescriptionDefault
fitNoHow each image fills its target box. 'contain' keeps the whole picture and pads the gap with paddingColor. 'cover' fills the box and crops whatever hangs over. 'stretch' distorts the image to the exact box, ignoring aspect ratio.contain
imagesYesOne to 60 images. Each is resized independently to every size in the chosen preset. A call is additionally capped at 48000000 total output pixels across images x sizes, so a preset with large or numerous sizes admits fewer images than one with small ones.
presetNoWhich size(s) to produce for every image. appstore69: App Store screenshot, 6.9" iPhone, 1320x2868. appstore67: App Store screenshot, 6.7" iPhone, 1290x2796. appstoreIpad: App Store screenshot, 13" iPad, 2064x2752. androidIcon: Android launcher icon, 5 densities (48/72/96/144/192px, each output foldered as mipmap-mdpi/hdpi/xhdpi/xxhdpi/xxxhdpi). favicon: favicon set, 16/32/48/180/192/512px. og: Open Graph card, 1200x630. xcard: X (Twitter) summary card, 1200x628. linkedin: LinkedIn share image, 1200x627. igSquare: Instagram square post, 1080x1080. igPortrait: Instagram portrait post, 1080x1350. igStory: Instagram story, 1080x1920. youtube: YouTube thumbnail, 1280x720. custom: a single arbitrary size taken from customWidth/customHeight.og
customWidthNoTarget width in px. Only used when preset is "custom".
customHeightNoTarget height in px. Only used when preset is "custom".
paddingColorNoHex color (e.g. "#ffffff") used to pad the frame when fit is "contain". Ignored for "cover" and "stretch".#ffffff

Output Schema

ParametersJSON Schema
NameRequiredDescription
fitYesHow each image was fitted to its target box.
sizesYesHow many target sizes the chosen preset expands to.
presetYesThe preset that was applied, echoed from the input.
outputsYesEvery produced file, so the archive can be checked without unzipping it.
filesOutYesHow many files were produced, i.e. imagesIn x sizes.
imagesInYesHow many source images were supplied.
paddingColorYesThe letterbox colour, which only applies to fit 'contain'. Null for 'cover' and 'stretch', where nothing is padded.

TDQS

A4.3/5.0
Behavior5/5

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

Beyond the readOnly/idempotent annotations, the description discloses major behavioral traits: output is always PNG-encoded with EXIF/ICC stripped, images are auto-rotated by EXIF orientation, everything is bundled into an uncompressed ZIP with an explanation of why no second compression pass is done, results always arrive as a resource_link, and per-file dimensions/byte sizes are returned in JSON. It also notes per-input byte caps. This is rich, non-obvious behavior that meaningfully helps an agent anticipate outcomes.

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

Conciseness4/5

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

The description is long but information-dense; every sentence carries operational value. It is front-loaded with the core action and fitting modes, then proceeds logically through encoding, packaging, return transport, and limits. Some parentheticals could be trimmed, but the structure is easy to scan and nothing feels redundant.

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 (multiple presets, fit modes, custom sizes, packaging, limits), the description is nearly exhaustive. It covers input constraints, output format and naming, the always-resource_link return behavior, result contents, and the pixel cap. An output schema exists, so the omission of detailed return schema is acceptable. There is no obvious missing context that would prevent correct invocation.

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%, and the description goes well beyond it: it gives concrete ZIP folder naming examples (social/photo-1200x630.png, android/mipmap-hdpi/icon.png) that clarify the preset parameter's effect, explains how the Android density preset names folders instead of suffixing pixel sizes, and calls out the 48,000,000 total output pixel cap and the per-input byte cap on imageBase64. These details materially change how an agent should size a request.

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

Purpose4/5

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

The description opens with a specific verb+resource: 'Resizes a batch of 1+ images to a named real-world preset...' and details fitting modes, output format, and packaging. It is unmistakably clear about what the tool does, but it does not explicitly differentiate itself from overlapping siblings such as generate_favicon_set, generate_app_icon_set, or image_compress, so it stops short of a 5.

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

Usage Guidelines3/5

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

Usage is implied through the phrase 'batch of 1+ images' and the preset-oriented workflow; an agent can infer when to reach for this tool. However, the description never names alternatives or states when this tool should be preferred over a single-purpose generator or compressor, nor does it give any when-not-to-use guidance.

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

strip_image_metadataStrip image metadata (EXIF/GPS/XMP/IPTC/PNG text)A
Read-onlyIdempotent
Inspect

Reads a JPEG or PNG's hidden metadata (JPEG: EXIF — camera make/model, lens, body/lens serial numbers, capture date, GPS coordinates and altitude — plus XMP, IPTC/Photoshop resources and comment segments; PNG: tEXt/iTXt/zTXt/eXIf/tIME chunks) and returns a structured report of exactly what it found, including the size of every metadata segment/chunk that was removed. Optionally (include_cleaned_image, default true) also returns the same image with those segments/chunks stripped at the byte level: no re-encoding and no pixel decode, so the compressed image data, ICC profile, and JFIF/PNG structure are copied unchanged. It runs a byte-for-byte self-check that the retained image data is actually unchanged before handing back a cleaned file — if that check fails, no cleaned file is returned even though the report is still produced. Only JPEG and PNG signatures are recognized (not TIFF or other formats); stripping a photo's EXIF Orientation tag can make it appear rotated, which the report flags.

ParametersJSON Schema
NameRequiredDescriptionDefault
filenameNoOriginal filename, used only to name the returned cleaned file (defaults to "image"). Format detection is by content signature, not by this name or extension.
image_base64YesBase64-encoded bytes of the JPEG or PNG file to inspect.
include_cleaned_imageNoWhen true (default), also return the metadata-stripped image bytes alongside the report. Set to false to get only the report.

Output Schema

ParametersJSON Schema
NameRequiredDescription
gpsYesEmbedded location, or null when the file carried none. The single most sensitive thing in a photo, so it is surfaced on its own rather than only inside fields.
makeYesCamera make, when EXIF carried one.
modelYesCamera model, when EXIF carried one.
fieldsYesEvery metadata field read from the file -- this is the disclosure the caller is usually checking for.
formatYesWhich of the two supported formats the file was read as.
messageYesPlain-language summary, including why no cleaned file was returned when that is the case.
verifiedYesResult of the byte-for-byte check that the retained image data is unchanged. Null when no cleaning was attempted; false means the check failed and no cleaned file was returned.
recognizedYesAlways true here. A file that is neither a JPEG nor a PNG comes back as an error result instead, not as recognized:false.
fieldsFoundYesHow many individual metadata fields were read out.
removedBytesYesBytes saved by stripping, or null when no cleaned image was produced.
metadataFoundYesWhether any strippable metadata was present at all.
verifiedBytesYesHow many bytes that check compared. Null when no cleaning was attempted.
removedSegmentsYesEach metadata segment or chunk that was stripped, with its size.
rotationWarningYesTrue when an EXIF Orientation tag was removed, which can make the cleaned image appear rotated in viewers that relied on it.
cleanedSizeBytesYesSize of the cleaned file, or null when no cleaned image was produced.
originalSizeBytesYesSize of the supplied file in bytes.
removedSegmentCountYesNumber of segments/chunks removed.
cleanedImageIncludedYesWhether a cleaned file accompanies this report as a content block. False when include_cleaned_image was off, when there was nothing to strip, or when verification failed.

TDQS

A4.6/5.0
Behavior5/5

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

The description goes well beyond annotations, disclosing exact metadata types, byte-level stripping behavior, the self-check that prevents returning corrupted files, and the Orientation-tag rotation caveat. This is rich behavioral context that annotations could not provide on their own.

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

Conciseness4/5

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

The description is long but every sentence carries distinct information: input formats, metadata types, stripping mechanics, self-check behavior, and edge-case warnings. It is front-loaded with the core action, though the density may make it slightly harder to scan.

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?

For a tool with an output schema, the description covers all essential operational context: what formats are accepted, what data is extracted, what happens during stripping, the failure mode, and a notable post-strip side effect. Nothing critical for correct invocation appears missing.

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

Parameters4/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. The description adds meaningful detail by explaining how include_cleaned_image controls output, how filename only names the returned file, and that format detection relies on content signature rather than filename.

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 tool reads JPEG/PNG hidden metadata and returns a structured report, optionally producing a byte-level stripped copy. It is specific about formats and distinguishes itself from similar image tools by emphasizing no re-encoding and no pixel decode.

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

Usage Guidelines4/5

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

It gives clear context for when to use the tool: for inspecting/stripping metadata in JPEG/PNG without altering pixel data. It explicitly states unsupported formats (not TIFF), and the no-re-encoding detail implies this is not for compression or resizing, though it doesn't name sibling alternatives explicitly.

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.

  1. 32 tool updates
    • Changedappstore_compare_markets1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "appId": {
        +      "description": "The app ID that was looked up, extracted from the input if a URL was given.",
        +      "type": "string"
        +    },
        +    "checked": {
        +      "description": "How many storefronts were queried.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "distinctTitles": {
        +      "description": "Number of different titles seen across the storefronts that resolved -- more than one means the listing is localized.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "failed": {
        +      "description": "How many lookups errored rather than returning a verdict.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "found": {
        +      "description": "How many of them have the app listed.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "rows": {
        +      "description": "One row per requested storefront, in the order given. A row is one of three shapes: listed, not listed, or errored.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "available": {
        +            "description": "Whether the app is listed on this storefront. When false, the listing fields below are absent.",
        +            "type": "boolean"
        +          },
        +          "country": {
        +            "description": "Storefront code.",
        +            "type": "string"
        +          },
        +          "countryName": {
        +            "description": "Storefront name.",
        +            "type": "string"
        +          },
        +          "currency": {
        +            "description": "ISO currency code; present only when available.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "error": {
        +            "description": "Why this storefront could not be checked. Present only on a failed lookup, which also reports available:false.",
        +            "type": "string"
        +          },
        +          "price": {
        +            "description": "Formatted local price; present only when available.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "rating": {
        +            "description": "Average rating on this storefront; present only when available.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "ratingCount": {
        +            "description": "Rating count on this storefront; present only when available.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "storeLink": {
        +            "description": "Local listing URL; present only when available.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "title": {
        +            "description": "App name on this storefront; present only when available.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          }
        +        },
        +        "required": [
        +          "country",
        +          "countryName",
        +          "available"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "appId",
        +    "checked",
        +    "found",
        +    "failed",
        +    "distinctTitles",
        +    "rows"
        +  ],
        +  "type": "object"
        +}
    • Changedappstore_search1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "apps": {
        +      "description": "The matching apps. Every field is nullable because Apple omits fields per storefront rather than returning empty values.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "appId": {
        +            "description": "Numeric App Store app ID as a string, or null if Apple omitted it.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "artworkUrl": {
        +            "description": "100px artwork URL.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "currency": {
        +            "description": "ISO currency code for that price.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "price": {
        +            "description": "Formatted price as Apple returns it, including the currency symbol.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "rating": {
        +            "description": "Average user rating, or null when the app has none on this storefront.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "ratingCount": {
        +            "description": "Number of ratings; 0 when Apple reports none.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "seller": {
        +            "description": "Publisher name.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "storeLink": {
        +            "description": "Canonical apps.apple.com URL for the listing.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "title": {
        +            "description": "App name on this storefront.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          }
        +        },
        +        "required": [
        +          "appId",
        +          "title",
        +          "seller",
        +          "price",
        +          "currency",
        +          "rating",
        +          "ratingCount",
        +          "storeLink",
        +          "artworkUrl"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "capped": {
        +      "description": "True when the result hit the Search API's 200-per-request ceiling, meaning there are likely more matches than were returned.",
        +      "type": "boolean"
        +    },
        +    "country": {
        +      "description": "The storefront that was searched.",
        +      "type": "string"
        +    },
        +    "entity": {
        +      "description": "Which catalogue was searched (iPhone, iPad or Mac software).",
        +      "type": "string"
        +    },
        +    "resultCount": {
        +      "description": "Apple's own reported match count for the query.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "term": {
        +      "description": "The search term, echoed back.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "term",
        +    "country",
        +    "entity",
        +    "resultCount",
        +    "capped",
        +    "apps"
        +  ],
        +  "type": "object"
        +}
    • Changedbpm_delay_calculator1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "barDurationSeconds": {
        +      "description": "Length of one bar in seconds at this tempo and signature.",
        +      "type": "number"
        +    },
        +    "bars": {
        +      "description": "The bar count that seconds corresponds to. Present only when bars or seconds was supplied.",
        +      "type": "number"
        +    },
        +    "bpm": {
        +      "description": "The tempo the table was computed at, in beats per minute.",
        +      "type": "number"
        +    },
        +    "bpmSource": {
        +      "description": "Where that tempo came from: given directly, or averaged from tap times or tap intervals -- worth surfacing, since a tapped tempo carries the tapper's error.",
        +      "type": "string"
        +    },
        +    "delayTable": {
        +      "description": "One row per requested division, each with straight, dotted and triplet timings.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "division": {
        +            "description": "The note division, as its denominator (4 = quarter note, 8 = eighth, and so on).",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "dottedMs": {
        +            "description": "Dotted variant: 1.5x the straight time.",
        +            "type": "number"
        +          },
        +          "hz": {
        +            "description": "The same interval expressed as a frequency, for setting an LFO rate rather than a delay.",
        +            "type": "number"
        +          },
        +          "label": {
        +            "description": "That division written out.",
        +            "type": "string"
        +          },
        +          "straightMs": {
        +            "description": "Delay time in milliseconds for the straight note.",
        +            "type": "number"
        +          },
        +          "tripletMs": {
        +            "description": "Triplet variant: two thirds of the straight time.",
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "division",
        +          "label",
        +          "straightMs",
        +          "dottedMs",
        +          "tripletMs",
        +          "hz"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "seconds": {
        +      "description": "Duration in seconds of that many bars at this tempo. Present only when bars or seconds was supplied.",
        +      "type": "number"
        +    },
        +    "timeSignature": {
        +      "additionalProperties": false,
        +      "description": "The time signature used to size a bar.",
        +      "properties": {
        +        "beats": {
        +          "description": "Beats per bar (the numerator).",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "label": {
        +          "description": "The signature written out, e.g. '4/4'.",
        +          "type": "string"
        +        },
        +        "unit": {
        +          "description": "Note value that gets the beat (the denominator).",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "beats",
        +        "unit",
        +        "label"
        +      ],
        +      "type": "object"
        +    }
        +  },
        +  "required": [
        +    "bpm",
        +    "bpmSource",
        +    "timeSignature",
        +    "barDurationSeconds",
        +    "delayTable"
        +  ],
        +  "type": "object"
        +}
    • Addedbuild_app_privacy_label
    • Changedbuild_app_store_link1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "appId": {
        +      "description": "The numeric app ID, whether given directly or extracted from a pasted URL.",
        +      "type": "string"
        +    },
        +    "params": {
        +      "description": "Every query parameter in the URL, explained.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "meaning": {
        +            "description": "What that parameter does, since App Store Connect campaign parameters are not self-explanatory.",
        +            "type": "string"
        +          },
        +          "param": {
        +            "description": "Query parameter name.",
        +            "type": "string"
        +          },
        +          "value": {
        +            "description": "Its value in the assembled URL.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "param",
        +          "value",
        +          "meaning"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "qr": {
        +      "additionalProperties": false,
        +      "description": "Details of the rendered QR code. Present only when includeQr was true -- the PNG itself comes back as a separate content block.",
        +      "properties": {
        +        "errorCorrection": {
        +          "description": "Error-correction level used.",
        +          "type": "string"
        +        },
        +        "moduleSize": {
        +          "description": "Symbol size in modules per side.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "pixelSize": {
        +          "description": "Rendered PNG size in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "scale": {
        +          "description": "Pixels per module.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "urlByteLength": {
        +          "description": "UTF-8 byte length of the encoded URL; the version 10 ceiling is 213.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "version": {
        +          "description": "QR symbol version (1-10) the encoder settled on.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "version",
        +        "moduleSize",
        +        "errorCorrection",
        +        "urlByteLength",
        +        "pixelSize",
        +        "scale"
        +      ],
        +      "type": "object"
        +    },
        +    "store": {
        +      "description": "The two-letter storefront code the URL targets.",
        +      "type": "string"
        +    },
        +    "url": {
        +      "description": "The assembled apps.apple.com URL -- the answer most callers want.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "url",
        +    "appId",
        +    "store",
        +    "params"
        +  ],
        +  "type": "object"
        +}
    • Changedcalculate_app_store_net_revenue1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "appleShareOfStickerPercent": {
        +      "description": "Apple's commission as a percentage of the sticker price, which is lower than the headline rate because tax comes off first.",
        +      "type": "number"
        +    },
        +    "breakdown": {
        +      "additionalProperties": false,
        +      "description": "The sticker price decomposed in the order the money is actually removed.",
        +      "properties": {
        +        "appleCommission": {
        +          "description": "Apple's cut in currency.",
        +          "type": "number"
        +        },
        +        "commissionBase": {
        +          "description": "The tax-exclusive remainder Apple actually takes its cut of -- not the sticker price.",
        +          "type": "number"
        +        },
        +        "customerPays": {
        +          "description": "The sticker price the customer is charged.",
        +          "type": "number"
        +        },
        +        "paidToYou": {
        +          "description": "What the developer receives per sale.",
        +          "type": "number"
        +        },
        +        "taxInsidePrice": {
        +          "description": "VAT/GST already baked into that price, removed before commission.",
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "customerPays",
        +        "taxInsidePrice",
        +        "commissionBase",
        +        "appleCommission",
        +        "paidToYou"
        +      ],
        +      "type": "object"
        +    },
        +    "comparison": {
        +      "description": "All three commission scenarios side by side, regardless of which was requested.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "commissionPercent": {
        +            "description": "Headline commission rate for that scenario.",
        +            "type": "number"
        +          },
        +          "label": {
        +            "description": "Human-readable name of the commission scenario.",
        +            "type": "string"
        +          },
        +          "per1000Sales": {
        +            "description": "Net across 1,000 sales, which is where the difference between scenarios becomes legible.",
        +            "type": "number"
        +          },
        +          "scenario": {
        +            "description": "Scenario identifier.",
        +            "type": "string"
        +          },
        +          "youKeep": {
        +            "description": "Net per sale under that scenario.",
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "scenario",
        +          "label",
        +          "commissionPercent",
        +          "youKeep",
        +          "per1000Sales"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "input": {
        +      "additionalProperties": false,
        +      "description": "What the calculation actually ran on, including the defaults that were filled in.",
        +      "properties": {
        +        "commissionScenario": {
        +          "description": "Which commission rate was applied.",
        +          "type": "string"
        +        },
        +        "countryCode": {
        +          "description": "Two-letter storefront code used.",
        +          "type": "string"
        +        },
        +        "countryName": {
        +          "description": "That storefront's full name.",
        +          "type": "string"
        +        },
        +        "currencySymbol": {
        +          "description": "Currency symbol for the storefront -- every money figure below is in this currency.",
        +          "type": "string"
        +        },
        +        "price": {
        +          "description": "The customer-facing sticker price, echoed back.",
        +          "type": "number"
        +        },
        +        "taxRatePercent": {
        +          "description": "The tax rate actually applied, whether supplied or taken from the storefront default.",
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "price",
        +        "countryCode",
        +        "countryName",
        +        "currencySymbol",
        +        "taxRatePercent",
        +        "commissionScenario"
        +      ],
        +      "type": "object"
        +    },
        +    "naiveComparison": {
        +      "additionalProperties": false,
        +      "description": "A sanity check against the common mistake of applying commission to the tax-inclusive price.",
        +      "properties": {
        +        "actualNet": {
        +          "description": "What the correct tax-then-commission order actually yields.",
        +          "type": "number"
        +        },
        +        "commissionPercent": {
        +          "description": "The rate a naive calculation would apply.",
        +          "type": "number"
        +        },
        +        "difference": {
        +          "description": "How far off the naive figure is, per sale.",
        +          "type": "number"
        +        },
        +        "naiveNet": {
        +          "description": "What \"just take the commission off the sticker price\" would predict.",
        +          "type": "number"
        +        },
        +        "sameAsActual": {
        +          "description": "True when the storefront has no tax, so both methods agree.",
        +          "type": "boolean"
        +        }
        +      },
        +      "required": [
        +        "commissionPercent",
        +        "naiveNet",
        +        "actualNet",
        +        "difference",
        +        "sameAsActual"
        +      ],
        +      "type": "object"
        +    },
        +    "warnings": {
        +      "description": "Cautions about the storefront, chiefly that its real tax rate varies by province, state or category. Empty when the rate is a single fixed number.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "input",
        +    "breakdown",
        +    "appleShareOfStickerPercent",
        +    "comparison",
        +    "naiveComparison",
        +    "warnings"
        +  ],
        +  "type": "object"
        +}
    • Changedcalculate_bmi_and_body_fat1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "bmi": {
        +      "description": "Body mass index to 1 decimal place, or null if it could not be computed.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "bmiCategory": {
        +      "description": "The WHO band that BMI falls in, e.g. 'Underweight', 'Normal', 'Overweight', 'Obese'. Null when bmi is null.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "bodyFatPercent": {
        +      "description": "US Navy tape-method body fat as a whole percent, clamped to 2-75. Null when the measurements make the formula undefined (see warnings).",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "bodyFatRaw": {
        +      "description": "The same figure before clamping and rounding, to 2 decimals -- this is the one that reveals an implausible measurement. Null in the same case as bodyFatPercent.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "leanMass": {
        +      "description": "Lean mass in the same unit system as the input (kg for metric, lb for imperial). Null when body fat could not be computed.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "units": {
        +      "description": "Which unit system leanMass is expressed in -- otherwise the bare number is ambiguous.",
        +      "enum": [
        +        "metric",
        +        "imperial"
        +      ],
        +      "type": "string"
        +    },
        +    "warnings": {
        +      "description": "Plain-language cautions: the measurements make the formula undefined, or the result sits outside the range it was fitted on. Empty when neither applies.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "bmi",
        +    "bmiCategory",
        +    "bodyFatPercent",
        +    "bodyFatRaw",
        +    "leanMass",
        +    "units",
        +    "warnings"
        +  ],
        +  "type": "object"
        +}
    • Changedcalculate_tdee11 fields changed
      • addedInput schema / properties / activity_level / description
        Added value: +"Multiplier applied to BMR to reach TDEE: sedentary x1.2 (desk job, no exercise), light x1.375 (1-3 sessions/week), moderate x1.55 (3-5), very_active x1.725 (6-7), extra_active x1.9 (physical job or twice a day)."
      • addedInput schema / properties / age / description
        Added value: +"Age in years, 14-100. Every BMR formula here subtracts a per-year term, so this moves the result directly."
      • addedInput schema / properties / goal / description
        Added value: +"Calorie adjustment applied to TDEE to get the target: lose_20 is a 20% deficit, lose_15 a 15% deficit, maintain no change, gain_10 a 10% surplus. A deficit landing under the safety floor sets below_safety_floor in the result."
      • addedInput schema / properties / height_cm / description
        Added value: +"Height in centimetres. Required when units is 'metric', ignored when units is 'imperial'."
      • addedInput schema / properties / height_ft / description
        Added value: +"Height, whole feet, combined with height_in. Required when units is 'imperial', ignored when units is 'metric'."
      • addedInput schema / properties / height_in / description
        Added value: +"Height, the inches remainder on top of height_ft (0-11). Used only when units is 'imperial', and treated as 0 when omitted -- so 6 ft flat is height_ft 6 with no height_in."
      • addedInput schema / properties / sex / description
        Added value: +"Biological sex. Selects the sex constant in the BMR formulas (Mifflin-St Jeor +5/-161, Harris-Benedict its own pair) and the calorie safety floor the goal target is checked against (1,500 kcal/day for male, 1,200 for female)."
      • addedInput schema / properties / units / description
        Added value: +"Which height/weight pair is read. 'metric' uses height_cm and weight_kg; 'imperial' uses height_ft (+ height_in) and weight_lb. The pair belonging to the other system is ignored, not merged. Defaults to 'metric'."
      • addedInput schema / properties / weight_kg / description
        Added value: +"Weight in kilograms. Required when units is 'metric', ignored when units is 'imperial'."
      • addedInput schema / properties / weight_lb / description
        Added value: +"Weight in pounds, converted internally at 2.20462 lb per kg. Required when units is 'imperial', ignored when units is 'metric'."
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "activity_factor": {
        +      "description": "The multiplier the chosen activity_level maps to (1.2 to 1.9).",
        +      "type": "number"
        +    },
        +    "activity_label": {
        +      "description": "The full human-readable label for that activity level.",
        +      "type": "string"
        +    },
        +    "below_safety_floor": {
        +      "description": "True when a deficit target falls below the usual floor for this sex -- the one field worth checking before presenting the target.",
        +      "type": "boolean"
        +    },
        +    "bmr": {
        +      "additionalProperties": false,
        +      "description": "Basal metabolic rate by formula, before any activity multiplier.",
        +      "properties": {
        +        "harris_benedict": {
        +          "description": "Harris-Benedict BMR in kcal/day; tends to read a little high.",
        +          "type": "number"
        +        },
        +        "katch_mcardle": {
        +          "description": "Katch-McArdle BMR in kcal/day, or null when body_fat_percent was not supplied (it needs lean mass).",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "mifflin_st_jeor": {
        +          "description": "Mifflin-St Jeor BMR in kcal/day -- the modern default.",
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "mifflin_st_jeor",
        +        "harris_benedict",
        +        "katch_mcardle"
        +      ],
        +      "type": "object"
        +    },
        +    "disclaimer": {
        +      "description": "Standing note that these are population-average estimates, not a measurement.",
        +      "type": "string"
        +    },
        +    "formula_comparison": {
        +      "description": "All three formulas side by side, so the spread is visible rather than implied.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "best_for": {
        +            "description": "One line on when this formula is the right one to trust.",
        +            "type": "string"
        +          },
        +          "bmr_kcal": {
        +            "description": "That formula's BMR, or null if it could not be computed.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "formula": {
        +            "description": "Formula name.",
        +            "type": "string"
        +          },
        +          "is_primary": {
        +            "description": "True for the row that produced the headline tdee_kcal.",
        +            "type": "boolean"
        +          },
        +          "tdee_kcal": {
        +            "description": "That formula's BMR times the activity factor, or null.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          }
        +        },
        +        "required": [
        +          "formula",
        +          "bmr_kcal",
        +          "tdee_kcal",
        +          "best_for",
        +          "is_primary"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "goal_label": {
        +      "description": "Wording for that adjustment, e.g. '15% deficit', 'maintenance', '10% surplus'.",
        +      "type": "string"
        +    },
        +    "goal_pct": {
        +      "description": "The goal adjustment as a signed percentage (-20, -15, 0, or +10).",
        +      "type": "number"
        +    },
        +    "goal_target_kcal": {
        +      "description": "The goal-adjusted daily calorie target in kcal.",
        +      "type": "number"
        +    },
        +    "lean_mass_kg": {
        +      "description": "Lean body mass in kg, or null when body_fat_percent was not supplied.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "macros": {
        +      "additionalProperties": false,
        +      "description": "A protein/fat/carb split of goal_target_kcal.",
        +      "properties": {
        +        "carbs_g": {
        +          "description": "Carbohydrate grams per day: whatever the target has left after protein and fat, floored at 0.",
        +          "type": "number"
        +        },
        +        "fat_g": {
        +          "description": "Fat grams per day, from 25% of the target calories.",
        +          "type": "number"
        +        },
        +        "protein_g": {
        +          "description": "Protein grams per day, at 1.8 g per kg of body weight.",
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "protein_g",
        +        "fat_g",
        +        "carbs_g"
        +      ],
        +      "type": "object"
        +    },
        +    "primary_formula": {
        +      "description": "Which formula drove the headline numbers: 'Katch-McArdle' when body fat % was given, otherwise 'Mifflin-St Jeor'.",
        +      "type": "string"
        +    },
        +    "resolved_inputs": {
        +      "additionalProperties": false,
        +      "description": "What the imperial/metric inputs actually resolved to internally -- the numbers every formula above was fed.",
        +      "properties": {
        +        "height_cm": {
        +          "description": "Height in cm after any imperial conversion.",
        +          "type": "number"
        +        },
        +        "weight_kg": {
        +          "description": "Weight in kg after any imperial conversion.",
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "height_cm",
        +        "weight_kg"
        +      ],
        +      "type": "object"
        +    },
        +    "safety_floor_kcal": {
        +      "description": "The floor that was checked against: 1500 kcal/day for male, 1200 for female.",
        +      "type": "number"
        +    },
        +    "safety_warning": {
        +      "description": "The full warning text when below_safety_floor is true, otherwise null.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "spread_kcal": {
        +      "description": "Difference in kcal/day between the highest and lowest TDEE among the formulas that computed.",
        +      "type": "number"
        +    },
        +    "tdee_kcal": {
        +      "description": "Total daily energy expenditure in kcal/day: primary BMR x activity factor.",
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "bmr",
        +    "lean_mass_kg",
        +    "primary_formula",
        +    "activity_factor",
        +    "activity_label",
        +    "tdee_kcal",
        +    "goal_pct",
        +    "goal_label",
        +    "goal_target_kcal",
        +    "below_safety_floor",
        +    "safety_floor_kcal",
        +    "safety_warning",
        +    "macros",
        +    "formula_comparison",
        +    "spread_kcal",
        +    "resolved_inputs",
        +    "disclaimer"
        +  ],
        +  "type": "object"
        +}
    • Changedcheck_contrast1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "apca": {
        +      "additionalProperties": false,
        +      "description": "The APCA (WCAG 3 draft) reading, which models perceived contrast rather than a pure luminance ratio and often disagrees with WCAG 2.",
        +      "properties": {
        +        "lc": {
        +          "description": "APCA lightness contrast, signed -- the sign encodes which of the two colours is lighter.",
        +          "type": "number"
        +        },
        +        "lcAbs": {
        +          "description": "Absolute Lc value, which is what the APCA thresholds are stated against.",
        +          "type": "number"
        +        },
        +        "polarity": {
        +          "description": "Whether this is light text on dark, or dark text on light.",
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "lc",
        +        "lcAbs",
        +        "polarity"
        +      ],
        +      "type": "object"
        +    },
        +    "backgroundHex": {
        +      "description": "The background colour normalised to hex.",
        +      "type": "string"
        +    },
        +    "cases": {
        +      "description": "The pair judged against each WCAG use case, since one ratio passes for large text and fails for body copy.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "aaThreshold": {
        +            "description": "The AA ratio this case must meet.",
        +            "type": "number"
        +          },
        +          "aaaThreshold": {
        +            "description": "The AAA ratio, or null for cases WCAG defines no AAA level for (UI components).",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "key": {
        +            "description": "Stable identifier for the use case.",
        +            "type": "string"
        +          },
        +          "label": {
        +            "description": "Human-readable name, e.g. 'Normal text'.",
        +            "type": "string"
        +          },
        +          "need": {
        +            "description": "What this case requires, written out.",
        +            "type": "string"
        +          },
        +          "passAA": {
        +            "description": "Whether the pair meets AA for this case.",
        +            "type": "boolean"
        +          },
        +          "passAAA": {
        +            "description": "Whether it meets AAA, or null where no AAA level exists.",
        +            "type": [
        +              "boolean",
        +              "null"
        +            ]
        +          }
        +        },
        +        "required": [
        +          "key",
        +          "label",
        +          "need",
        +          "aaThreshold",
        +          "aaaThreshold",
        +          "passAA",
        +          "passAAA"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "foregroundHex": {
        +      "description": "The foreground colour normalised to hex.",
        +      "type": "string"
        +    },
        +    "ratio": {
        +      "description": "WCAG 2 contrast ratio, from 1 (identical) to 21 (black on white).",
        +      "type": "number"
        +    },
        +    "ratioDisplay": {
        +      "description": "That ratio formatted the way it is conventionally written, e.g. '4.53:1'.",
        +      "type": "string"
        +    },
        +    "summary": {
        +      "additionalProperties": false,
        +      "description": "The tally, so a caller can gate on one boolean instead of walking cases.",
        +      "properties": {
        +        "allPass": {
        +          "description": "True only when every gradeable check passed.",
        +          "type": "boolean"
        +        },
        +        "graded": {
        +          "description": "How many checks were gradeable (a null AAA level is not counted).",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "passed": {
        +          "description": "How many graded checks passed.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "passed",
        +        "graded",
        +        "allPass"
        +      ],
        +      "type": "object"
        +    }
        +  },
        +  "required": [
        +    "foregroundHex",
        +    "backgroundHex",
        +    "ratio",
        +    "ratioDisplay",
        +    "apca",
        +    "cases",
        +    "summary"
        +  ],
        +  "type": "object"
        +}
    • Changedcheck_strings_files1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "badLines": {
        +      "description": "Lines that are neither a comment, blank, nor a parseable key/value pair -- typically a missing semicolon or an unescaped quote.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "file": {
        +            "description": "The file containing the line.",
        +            "type": "string"
        +          },
        +          "line": {
        +            "description": "1-based line number.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "text": {
        +            "description": "The line as written, so it can be found and fixed.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "file",
        +          "line",
        +          "text"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "baseFile": {
        +      "description": "Which file every other file was compared against, whether given or chosen automatically.",
        +      "type": "string"
        +    },
        +    "duplicateValues": {
        +      "description": "Keys within one file that share a value, which is how a copy-paste translation error looks.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "file": {
        +            "description": "The file containing the duplicates.",
        +            "type": "string"
        +          },
        +          "keys": {
        +            "description": "The keys sharing it, comma-separated.",
        +            "type": "string"
        +          },
        +          "value": {
        +            "description": "The repeated value.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "file",
        +          "value",
        +          "keys"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "extraKeys": {
        +      "description": "Keys in a translation that the base file no longer has -- usually left behind by a rename or a deletion.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "file": {
        +            "description": "The file carrying it.",
        +            "type": "string"
        +          },
        +          "key": {
        +            "description": "The key not present in the base file.",
        +            "type": "string"
        +          },
        +          "value": {
        +            "description": "Its value in that file.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "key",
        +          "file",
        +          "value"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "fileCount": {
        +      "description": "How many files were compared.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "files": {
        +      "description": "Per-file summary, including the base file.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "badLineCount": {
        +            "description": "Unparseable lines in this file.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "encoding": {
        +            "description": "The encoding the file was decoded as -- .strings files are frequently UTF-16, and a wrong guess here explains a zero key count.",
        +            "type": "string"
        +          },
        +          "keyCount": {
        +            "description": "Keys parsed from this file.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "name": {
        +            "description": "Filename as given.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "encoding",
        +          "keyCount",
        +          "badLineCount"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "identicalToBase": {
        +      "description": "Keys whose translation is byte-identical to the base language. Sometimes correct (a proper noun), often a forgotten translation.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "file": {
        +            "description": "The translation file.",
        +            "type": "string"
        +          },
        +          "key": {
        +            "description": "The key whose value matches the base file.",
        +            "type": "string"
        +          },
        +          "value": {
        +            "description": "The shared value.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "key",
        +          "file",
        +          "value"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "issueCount": {
        +      "description": "Total findings across every category below. Zero means the files agree.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "keysInBase": {
        +      "description": "Number of keys in the base file -- the denominator for the translation coverage.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "missingKeys": {
        +      "description": "Keys present in the base file but absent from another -- untranslated strings that will fall back at runtime.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "baseValue": {
        +            "description": "The base file's value for that key, i.e. the string still to be translated.",
        +            "type": "string"
        +          },
        +          "file": {
        +            "description": "The file missing it.",
        +            "type": "string"
        +          },
        +          "key": {
        +            "description": "The key absent from this file.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "key",
        +          "file",
        +          "baseValue"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "missingKeysStrings": {
        +      "description": "Every missing key formatted as ready-to-paste .strings entries with the base value, so the gap can be filled without retyping. Empty when nothing is missing.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "baseFile",
        +    "fileCount",
        +    "keysInBase",
        +    "issueCount",
        +    "missingKeys",
        +    "extraKeys",
        +    "duplicateValues",
        +    "identicalToBase",
        +    "badLines",
        +    "missingKeysStrings",
        +    "files"
        +  ],
        +  "type": "object"
        +}
    • Changedconvert_heic_to_jpg_png1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "format": {
        +      "description": "Output MIME type requested for the batch.",
        +      "enum": [
        +        "image/jpeg",
        +        "image/png"
        +      ],
        +      "type": "string"
        +    },
        +    "results": {
        +      "description": "One row per input file, in the order supplied. Rows come in three shapes depending on status.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "error": {
        +            "description": "Why this file could not be converted. Present only on a failed row -- one bad file does not fail the batch.",
        +            "type": "string"
        +          },
        +          "filename": {
        +            "description": "The input filename, echoed back so rows line up with what was sent.",
        +            "type": "string"
        +          },
        +          "height": {
        +            "anyOf": [
        +              {
        +                "maximum": 9007199254740991,
        +                "minimum": -9007199254740991,
        +                "type": "integer"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Decoded height in px, on the same terms as width."
        +          },
        +          "inputBytes": {
        +            "description": "Size of the supplied file in bytes.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "mimeType": {
        +            "description": "MIME type of the produced file, or null when this input failed.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "outputBytes": {
        +            "anyOf": [
        +              {
        +                "maximum": 9007199254740991,
        +                "minimum": -9007199254740991,
        +                "type": "integer"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Size of the produced file in bytes, or null when this input failed."
        +          },
        +          "outputFilename": {
        +            "description": "Name of the produced file, or null when this input failed.",
        +            "type": [
        +              "string",
        +              "null"
        +            ]
        +          },
        +          "status": {
        +            "description": "Per-file outcome. 'already_converted' means the input was already a JPEG or PNG and was passed through untouched rather than re-encoded.",
        +            "enum": [
        +              "converted",
        +              "already_converted",
        +              "failed"
        +            ],
        +            "type": "string"
        +          },
        +          "width": {
        +            "anyOf": [
        +              {
        +                "maximum": 9007199254740991,
        +                "minimum": -9007199254740991,
        +                "type": "integer"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Decoded width in px. Null for a failed file, and also for a passed-through one, which is never decoded."
        +          }
        +        },
        +        "required": [
        +          "filename",
        +          "outputFilename",
        +          "status",
        +          "mimeType",
        +          "width",
        +          "height",
        +          "inputBytes",
        +          "outputBytes"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "format",
        +    "results"
        +  ],
        +  "type": "object"
        +}
    • Changedconvert_video_to_gif1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "actualFps": {
        +      "description": "The frame rate actually achieved, which can differ because frames are sampled by ffmpeg's fps filter rather than seeking to exact times.",
        +      "type": "number"
        +    },
        +    "audio": {
        +      "description": "What happened to the source audio track. mp4/webm output only.",
        +      "type": "string"
        +    },
        +    "byteSize": {
        +      "description": "Size of the produced file in bytes.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "delayCentiseconds": {
        +      "description": "Per-frame delay written into the GIF, in centiseconds. GIF output only.",
        +      "type": "number"
        +    },
        +    "dither": {
        +      "description": "Whether dithering was applied during quantisation. GIF output only.",
        +      "type": "boolean"
        +    },
        +    "durationSeconds": {
        +      "description": "Duration of the produced clip in seconds. mp4/webm output only.",
        +      "type": "number"
        +    },
        +    "format": {
        +      "description": "Output format. This field decides which of the conditional fields below are present.",
        +      "enum": [
        +        "gif",
        +        "mp4",
        +        "webm"
        +      ],
        +      "type": "string"
        +    },
        +    "frameCount": {
        +      "description": "Number of frames encoded. GIF output only.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "height": {
        +      "description": "Output height in px.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "maxColors": {
        +      "description": "Palette size the median-cut quantiser was allowed. GIF output only.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "maxOutputPixels": {
        +      "description": "The ceiling outputPixels was checked against, so a rejection is explicable from the result.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "motion": {
        +      "description": "Playback behaviour applied.",
        +      "enum": [
        +        "loop",
        +        "bounce",
        +        "once"
        +      ],
        +      "type": "string"
        +    },
        +    "outputPixels": {
        +      "description": "Width x height x frames -- the bound that actually governs this tool's memory use.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "requestedFps": {
        +      "description": "The frame rate that was asked for.",
        +      "type": "number"
        +    },
        +    "speed": {
        +      "description": "Speed multiplier applied to the source.",
        +      "type": "number"
        +    },
        +    "trimEnd": {
        +      "description": "End of the trimmed span in seconds. GIF output only.",
        +      "type": "number"
        +    },
        +    "trimSpan": {
        +      "description": "Length of the trimmed span in seconds. GIF output only.",
        +      "type": "number"
        +    },
        +    "trimStart": {
        +      "description": "Start of the trimmed span in seconds. GIF output only.",
        +      "type": "number"
        +    },
        +    "width": {
        +      "description": "Output width in px.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "format",
        +    "width",
        +    "height",
        +    "byteSize",
        +    "requestedFps",
        +    "actualFps",
        +    "motion",
        +    "speed",
        +    "outputPixels",
        +    "maxOutputPixels"
        +  ],
        +  "type": "object"
        +}
    • Changedcss_clamp_calculator1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "css": {
        +      "description": "The finished CSS clamp() declaration in rem, or null when status is sameViewports.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "math": {
        +      "anyOf": [
        +        {
        +          "additionalProperties": false,
        +          "properties": {
        +            "interceptRem": {
        +              "description": "The constant rem term of the middle (preferred) expression.",
        +              "type": "number"
        +            },
        +            "maxRem": {
        +              "description": "Upper bound of the clamp, in rem.",
        +              "type": "number"
        +            },
        +            "minRem": {
        +              "description": "Lower bound of the clamp, in rem.",
        +              "type": "number"
        +            },
        +            "slopeVw": {
        +              "description": "The vw coefficient of the middle expression, i.e. rem gained per 1% of viewport width.",
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "minRem",
        +            "maxRem",
        +            "interceptRem",
        +            "slopeVw"
        +          ],
        +          "type": "object"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "The four numbers the css string is assembled from, each rounded to 4 decimal places. Null when status is sameViewports."
        +    },
        +    "note": {
        +      "description": "Human-readable explanation of a non-ok status, or an empty string when status is ok.",
        +      "type": "string"
        +    },
        +    "preview": {
        +      "description": "One entry per requested preview viewport, in the order given. Empty when previewViewportsPx was omitted.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "computedSizePx": {
        +            "description": "What the clamp() resolves to at that width, in px, rounded to 2 decimals.",
        +            "type": "number"
        +          },
        +          "viewportPx": {
        +            "description": "The viewport width that was evaluated, echoed from previewViewportsPx.",
        +            "type": "number"
        +          }
        +        },
        +        "required": [
        +          "viewportPx",
        +          "computedSizePx"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "scale": {
        +      "description": "The fluid type scale, one entry per step. Empty when typeScale was omitted or no step could be built.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "css": {
        +            "description": "The clamp() declaration for this step.",
        +            "type": "string"
        +          },
        +          "maxSizePx": {
        +            "description": "This step's size at the large viewport, in px.",
        +            "type": "number"
        +          },
        +          "minSizePx": {
        +            "description": "This step's size at the small viewport, in px.",
        +            "type": "number"
        +          },
        +          "ratioApplied": {
        +            "description": "ratio raised to (step - 1), the multiplier applied to both endpoints.",
        +            "type": "number"
        +          },
        +          "step": {
        +            "description": "1-based step index in the scale.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "step",
        +          "ratioApplied",
        +          "minSizePx",
        +          "maxSizePx",
        +          "css"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "status": {
        +      "description": "'ok' for a normal fluid size; 'sameViewports' when the two viewport widths match, so no line can be drawn and css is null; 'flat' when both sizes are equal (a constant, clamp() unneeded); 'inverted' when the max size is below the min, which is valid CSS that shrinks as the viewport grows.",
        +      "enum": [
        +        "ok",
        +        "sameViewports",
        +        "flat",
        +        "inverted"
        +      ],
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "status",
        +    "note",
        +    "css",
        +    "math",
        +    "preview",
        +    "scale"
        +  ],
        +  "type": "object"
        +}
    • Changedestimate_ai_tokens1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "accuracyBand": {
        +      "additionalProperties": false,
        +      "description": "An honesty bound on the token count: this is a heuristic, not a real tokenizer, and the band says how far off it usually is.",
        +      "properties": {
        +        "approxRange": {
        +          "description": "The expected error range, e.g. a percentage spread.",
        +          "type": "string"
        +        },
        +        "level": {
        +          "description": "How trustworthy this estimate is for text of this kind.",
        +          "type": "string"
        +        },
        +        "note": {
        +          "description": "What about the text put it in this band, and which way it is likely to be wrong.",
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "level",
        +        "approxRange",
        +        "note"
        +      ],
        +      "type": "object"
        +    },
        +    "characters": {
        +      "description": "Character count of the input text.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "charsPerToken": {
        +      "description": "Characters divided by tokens -- the density that drives the accuracy band.",
        +      "type": "number"
        +    },
        +    "contextFit": {
        +      "description": "The text measured against each context window, so the caller sees which models it fits.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "comfortable": {
        +            "description": "Whether it fits with enough headroom left for a useful reply.",
        +            "type": "boolean"
        +          },
        +          "fits": {
        +            "description": "Whether the text fits at all.",
        +            "type": "boolean"
        +          },
        +          "name": {
        +            "description": "Name of the context window being checked against.",
        +            "type": "string"
        +          },
        +          "overByTokens": {
        +            "description": "How many tokens too long the text is, or null when it fits.",
        +            "type": [
        +              "number",
        +              "null"
        +            ]
        +          },
        +          "percentUsed": {
        +            "description": "What percentage of the window the text would occupy.",
        +            "type": "number"
        +          },
        +          "sizeTokens": {
        +            "description": "That window's size in tokens.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "verdict": {
        +            "description": "One-word summary, e.g. 'fits' or 'over'.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "name",
        +          "sizeTokens",
        +          "percentUsed",
        +          "fits",
        +          "comfortable",
        +          "verdict",
        +          "overByTokens"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "cost": {
        +      "additionalProperties": false,
        +      "description": "Projected spend. Every field is null unless the matching per-million price was supplied.",
        +      "properties": {
        +        "inputCost": {
        +          "description": "USD to send this text, or null when no input price was supplied.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "outputCost": {
        +          "description": "USD for the expected output, or null when no output price was supplied.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "totalCost": {
        +          "description": "Input plus output, multiplied by calls. Null when either price was omitted.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        }
        +      },
        +      "required": [
        +        "inputCost",
        +        "outputCost",
        +        "totalCost"
        +      ],
        +      "type": "object"
        +    },
        +    "readingTime": {
        +      "additionalProperties": false,
        +      "description": "How long the text takes a person to read, as a sanity check on the size.",
        +      "properties": {
        +        "minutes": {
        +          "description": "Whole minutes to read at an average pace, or null when underOneMinute is true.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "underOneMinute": {
        +          "description": "True when the text reads in under a minute, in which case minutes is null.",
        +          "type": "boolean"
        +        }
        +      },
        +      "required": [
        +        "underOneMinute",
        +        "minutes"
        +      ],
        +      "type": "object"
        +    },
        +    "tokens": {
        +      "description": "Estimated token count for the text.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "words": {
        +      "description": "Whitespace-delimited word count.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "tokens",
        +    "characters",
        +    "words",
        +    "charsPerToken",
        +    "readingTime",
        +    "accuracyBand",
        +    "contextFit",
        +    "cost"
        +  ],
        +  "type": "object"
        +}
    • Changedestimate_window_light1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "aspect": {
        +      "description": "The compass direction the window faces, echoed from the input.",
        +      "type": "string"
        +    },
        +    "band": {
        +      "description": "The score bucketed into a named light level, the single field most callers want.",
        +      "type": "string"
        +    },
        +    "plants": {
        +      "description": "Plant types suited to this light level.",
        +      "type": "string"
        +    },
        +    "reasoning": {
        +      "description": "Why the score came out where it did, naming the aspect, glazing and distance contributions.",
        +      "type": "string"
        +    },
        +    "score": {
        +      "description": "The computed light score the band and verdict are derived from -- higher is brighter.",
        +      "type": "number"
        +    },
        +    "solarAspect": {
        +      "description": "That aspect mapped to its solar equivalent for the given hemisphere -- a south-facing window means the opposite thing north and south of the equator.",
        +      "type": "string"
        +    },
        +    "verdict": {
        +      "description": "One-line summary of what this window is good for.",
        +      "type": "string"
        +    },
        +    "verdictSub": {
        +      "description": "A supporting line qualifying the verdict.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "score",
        +    "band",
        +    "aspect",
        +    "solarAspect",
        +    "verdict",
        +    "verdictSub",
        +    "reasoning",
        +    "plants"
        +  ],
        +  "type": "object"
        +}
    • Changedfind_nearest_passing_color1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "adjust": {
        +      "description": "Which colour was moved, echoed from the input.",
        +      "enum": [
        +        "foreground",
        +        "background"
        +      ],
        +      "type": "string"
        +    },
        +    "alreadyPassing": {
        +      "description": "True when the input pair already met the target and nothing needed moving.",
        +      "type": "boolean"
        +    },
        +    "currentRatio": {
        +      "description": "The ratio the original pair had.",
        +      "type": "number"
        +    },
        +    "direction": {
        +      "description": "Whether the colour was lightened or darkened; null when nothing moved.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "found": {
        +      "description": "Whether a passing colour was reached. False means the target is unreachable by lightness alone from this starting colour.",
        +      "type": "boolean"
        +    },
        +    "lightnessStepPercent": {
        +      "description": "How far the colour had to travel in lightness, as a percentage -- a large value means the passing colour is no longer the colour that was asked for.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "message": {
        +      "description": "Plain-language summary of the outcome, including why no colour was found when found is false.",
        +      "type": "string"
        +    },
        +    "movedHex": {
        +      "description": "The adjusted colour in hex, or null when nothing was moved or nothing was found.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "newRatio": {
        +      "description": "The contrast ratio after the move, or null when there was no move.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "targetRatio": {
        +      "description": "The contrast ratio being aimed for, resolved from the named target or given directly.",
        +      "type": "number"
        +    }
        +  },
        +  "required": [
        +    "adjust",
        +    "targetRatio",
        +    "currentRatio",
        +    "alreadyPassing",
        +    "found",
        +    "movedHex",
        +    "newRatio",
        +    "lightnessStepPercent",
        +    "direction",
        +    "message"
        +  ],
        +  "type": "object"
        +}
    • Changedgenerate_app_icon_set1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "backgroundColor": {
        +      "description": "The colour transparency was flattened onto, and the letterbox colour for a non-square source.",
        +      "type": "string"
        +    },
        +    "files": {
        +      "description": "Every file in the archive, so the contents can be checked without unzipping it.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "height": {
        +            "anyOf": [
        +              {
        +                "maximum": 9007199254740991,
        +                "minimum": -9007199254740991,
        +                "type": "integer"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Height in px, or null for a non-image entry such as Contents.json."
        +          },
        +          "path": {
        +            "description": "Path of the file inside the ZIP.",
        +            "type": "string"
        +          },
        +          "where": {
        +            "description": "Which part of the set this file belongs to (the Xcode appiconset, an Android mipmap bucket, or the flat sizes folder).",
        +            "type": "string"
        +          },
        +          "width": {
        +            "anyOf": [
        +              {
        +                "maximum": 9007199254740991,
        +                "minimum": -9007199254740991,
        +                "type": "integer"
        +              },
        +              {
        +                "type": "null"
        +              }
        +            ],
        +            "description": "Width in px, or null for a non-image entry such as Contents.json."
        +          }
        +        },
        +        "required": [
        +          "where",
        +          "path",
        +          "width",
        +          "height"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "filesOut": {
        +      "description": "Total number of files inside the ZIP.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "hadAlpha": {
        +      "description": "True when the source carried transparency, which was flattened onto backgroundColor because Apple rejects icons with alpha.",
        +      "type": "boolean"
        +    },
        +    "source": {
        +      "additionalProperties": false,
        +      "description": "Dimensions of the image that was supplied, before squaring.",
        +      "properties": {
        +        "height": {
        +          "description": "Source image height in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "width": {
        +          "description": "Source image width in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "width",
        +        "height"
        +      ],
        +      "type": "object"
        +    },
        +    "variants": {
        +      "description": "Which appearance variants were generated, echoed from the input.",
        +      "enum": [
        +        "none",
        +        "dark",
        +        "all"
        +      ],
        +      "type": "string"
        +    },
        +    "warnings": {
        +      "additionalProperties": false,
        +      "description": "Non-fatal quality warnings. Both are null on an ideal 1024px square source -- neither stops the ZIP being produced.",
        +      "properties": {
        +        "notSquare": {
        +          "description": "Set when the source was not square and had to be letterboxed onto a square canvas; null when it was already square.",
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        },
        +        "small": {
        +          "description": "Set when the source was under 1024px on its longest side, so the largest icons are upscaled; null when it was big enough.",
        +          "type": [
        +            "string",
        +            "null"
        +          ]
        +        }
        +      },
        +      "required": [
        +        "notSquare",
        +        "small"
        +      ],
        +      "type": "object"
        +    }
        +  },
        +  "required": [
        +    "source",
        +    "hadAlpha",
        +    "variants",
        +    "backgroundColor",
        +    "filesOut",
        +    "warnings",
        +    "files"
        +  ],
        +  "type": "object"
        +}
    • Changedgenerate_favicon_set1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "backgroundColor": {
        +      "description": "Background colour written into the manifest.",
        +      "type": "string"
        +    },
        +    "manifest": {
        +      "description": "A complete web app manifest as JSON text, ready to serve as site.webmanifest.",
        +      "type": "string"
        +    },
        +    "maskablePadding": {
        +      "description": "Whether the maskable icon was padded to survive Android's safe-zone crop.",
        +      "type": "boolean"
        +    },
        +    "snippet": {
        +      "description": "The <link> and <meta> tags to paste into the page head, matching the files that were generated.",
        +      "type": "string"
        +    },
        +    "sourceHeight": {
        +      "description": "Source image height in px.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "sourceWidth": {
        +      "description": "Source image width in px.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "squaredSide": {
        +      "description": "Side length of the square canvas the source was fitted onto, in px.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "table": {
        +      "description": "Every generated file and what it is for, so the set can be checked without unzipping it.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "file": {
        +            "description": "Generated filename.",
        +            "type": "string"
        +          },
        +          "sizes": {
        +            "description": "Pixel dimensions the file covers.",
        +            "type": "string"
        +          },
        +          "usedFor": {
        +            "description": "Which browser, platform or surface asks for this file.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "file",
        +          "sizes",
        +          "usedFor"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "themeColor": {
        +      "description": "Theme colour written into the manifest and meta tags.",
        +      "type": "string"
        +    },
        +    "warning": {
        +      "description": "A quality caution about the source image, chiefly that it was not square or too small. Null when there is nothing to flag.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    }
        +  },
        +  "required": [
        +    "manifest",
        +    "snippet",
        +    "table",
        +    "sourceWidth",
        +    "sourceHeight",
        +    "squaredSide",
        +    "maskablePadding",
        +    "themeColor",
        +    "backgroundColor",
        +    "warning"
        +  ],
        +  "type": "object"
        +}
    • Removedgoai_build_app_privacy_label
    • Changedimage_compress1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "compressed": {
        +      "additionalProperties": false,
        +      "description": "The returned file. Its bytes come back as a separate content block.",
        +      "properties": {
        +        "bytes": {
        +          "description": "Output file size in bytes.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "height": {
        +          "description": "Output height in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "width": {
        +          "description": "Output width in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "width",
        +        "height",
        +        "bytes"
        +      ],
        +      "type": "object"
        +    },
        +    "format": {
        +      "description": "Output format used.",
        +      "enum": [
        +        "jpeg",
        +        "webp",
        +        "avif"
        +      ],
        +      "type": "string"
        +    },
        +    "original": {
        +      "additionalProperties": false,
        +      "description": "The image as supplied.",
        +      "properties": {
        +        "bytes": {
        +          "description": "Original file size in bytes.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "height": {
        +          "description": "Original height in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "width": {
        +          "description": "Original width in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "width",
        +        "height",
        +        "bytes"
        +      ],
        +      "type": "object"
        +    },
        +    "percentChange": {
        +      "description": "Size change against the original as a percentage; negative means smaller. Can be positive when re-encoding an already-optimised file.",
        +      "type": "number"
        +    },
        +    "quality": {
        +      "description": "Quality level the image was encoded at.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "working": {
        +      "additionalProperties": false,
        +      "description": "The image after being fitted to maxDimension but before re-encoding. Never an enlargement.",
        +      "properties": {
        +        "height": {
        +          "description": "Height after the downscale, in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "maxDimension": {
        +          "description": "The longest-side cap that was applied.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "width": {
        +          "description": "Width after the downscale, in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "width",
        +        "height",
        +        "maxDimension"
        +      ],
        +      "type": "object"
        +    }
        +  },
        +  "required": [
        +    "format",
        +    "quality",
        +    "original",
        +    "working",
        +    "compressed",
        +    "percentChange"
        +  ],
        +  "type": "object"
        +}
    • Changedimage_compression_curve1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "format": {
        +      "description": "Format every point was encoded in.",
        +      "enum": [
        +        "jpeg",
        +        "webp",
        +        "avif"
        +      ],
        +      "type": "string"
        +    },
        +    "original": {
        +      "additionalProperties": false,
        +      "description": "The image as supplied.",
        +      "properties": {
        +        "bytes": {
        +          "description": "Original file size in bytes.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "height": {
        +          "description": "Original height in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "width": {
        +          "description": "Original width in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "width",
        +        "height",
        +        "bytes"
        +      ],
        +      "type": "object"
        +    },
        +    "points": {
        +      "description": "The 14 fixed sample points, ascending by quality. The 'knee' -- where size stops dropping much per quality point -- is what this tool exists to locate. No image bytes are returned.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "bytes": {
        +            "description": "Encoded size at that quality, in bytes.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "percentChange": {
        +            "description": "Size against the original as a percentage; negative means smaller.",
        +            "type": "number"
        +          },
        +          "quality": {
        +            "description": "The quality level sampled.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "quality",
        +          "bytes",
        +          "percentChange"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "working": {
        +      "additionalProperties": false,
        +      "description": "The downscaled image every quality level was encoded from. The knee sits in the same place at either size, so this does not distort the curve.",
        +      "properties": {
        +        "height": {
        +          "description": "Height the curve was measured at, in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "maxDimension": {
        +          "description": "The longest-side cap that was applied before sampling.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "width": {
        +          "description": "Width the curve was measured at, in px.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "width",
        +        "height",
        +        "maxDimension"
        +      ],
        +      "type": "object"
        +    }
        +  },
        +  "required": [
        +    "format",
        +    "original",
        +    "working",
        +    "points"
        +  ],
        +  "type": "object"
        +}
    • Changedinspect_mobileprovision1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "appIdName": {
        +      "description": "The App ID name registered with the profile.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "applicationIdentifier": {
        +      "description": "The application-identifier entitlement, i.e. team ID plus bundle ID -- the field that says what this profile actually signs.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "certificateCount": {
        +      "description": "How many signing certificates are embedded. The certificates themselves are not decoded -- only counted.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "createdDate": {
        +      "description": "Creation date as an ISO 8601 string, or null when absent or unparseable.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "deviceCount": {
        +      "description": "Number of registered device UDIDs; 0 for an App Store or enterprise profile.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "devices": {
        +      "description": "The registered device UDIDs, normally strings. Empty when the profile registers none.",
        +      "items": {},
        +      "type": "array"
        +    },
        +    "entitlements": {
        +      "additionalProperties": {},
        +      "description": "The full entitlements dictionary verbatim from the profile, keys and values as the plist held them.",
        +      "propertyNames": {
        +        "type": "string"
        +      },
        +      "type": "object"
        +    },
        +    "expirationDate": {
        +      "description": "Expiry date as an ISO 8601 string, or null when absent or unparseable.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "expiry": {
        +      "additionalProperties": false,
        +      "description": "Whether this profile still works, which is the question most callers are actually asking.",
        +      "properties": {
        +        "daysRemaining": {
        +          "description": "Days until expiry, negative once expired. Null when status is unknown.",
        +          "type": [
        +            "number",
        +            "null"
        +          ]
        +        },
        +        "message": {
        +          "description": "That verdict written out with the date. An empty string when status is unknown.",
        +          "type": "string"
        +        },
        +        "status": {
        +          "description": "Expiry verdict; 'expiring_soon' means 30 days or fewer remain, 'unknown' means the profile carried no readable expiry date.",
        +          "enum": [
        +            "valid",
        +            "expiring_soon",
        +            "expired",
        +            "unknown"
        +          ],
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "status",
        +        "daysRemaining",
        +        "message"
        +      ],
        +      "type": "object"
        +    },
        +    "name": {
        +      "description": "The profile's name, or null if the plist omits it.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "ok": {
        +      "const": true,
        +      "description": "Always true on a readable profile. An unreadable file, a file with no embedded plist, or a plist that will not parse comes back as an error result instead, with the reason as its text.",
        +      "type": "boolean"
        +    },
        +    "platform": {
        +      "description": "Platforms the profile covers, normally an array such as ['ios']. Null when absent."
        +    },
        +    "profileType": {
        +      "additionalProperties": false,
        +      "description": "Development/ad hoc vs. enterprise vs. App Store distribution.",
        +      "properties": {
        +        "description": {
        +          "description": "What that kind means for installing the build.",
        +          "type": "string"
        +        },
        +        "kind": {
        +          "description": "Profile kind, inferred from the registered-device list and the ProvisionsAllDevices flag -- not from a field that states it, because none does.",
        +          "enum": [
        +            "development",
        +            "enterprise",
        +            "app_store"
        +          ],
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "kind",
        +        "description"
        +      ],
        +      "type": "object"
        +    },
        +    "rawPlistXml": {
        +      "description": "The embedded property list as raw XML, for anything this report does not surface.",
        +      "type": "string"
        +    },
        +    "teamIdentifier": {
        +      "description": "Team identifiers from the plist, normally an array of one team ID string. Null when absent."
        +    },
        +    "teamName": {
        +      "description": "Developer team name.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "timeToLiveDays": {
        +      "description": "The profile's TimeToLive in days, or null when absent.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "uuid": {
        +      "description": "The profile's UUID.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    }
        +  },
        +  "required": [
        +    "ok",
        +    "name",
        +    "appIdName",
        +    "applicationIdentifier",
        +    "teamName",
        +    "teamIdentifier",
        +    "uuid",
        +    "platform",
        +    "createdDate",
        +    "expirationDate",
        +    "timeToLiveDays",
        +    "expiry",
        +    "profileType",
        +    "certificateCount",
        +    "deviceCount",
        +    "devices",
        +    "entitlements",
        +    "rawPlistXml"
        +  ],
        +  "type": "object"
        +}
    • Changedoklch_convert1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "hex": {
        +      "description": "The sRGB result as a hex string.",
        +      "type": "string"
        +    },
        +    "hsl": {
        +      "additionalProperties": false,
        +      "description": "The same colour in HSL, for code that still expects it.",
        +      "properties": {
        +        "h": {
        +          "description": "Hue in degrees.",
        +          "type": "number"
        +        },
        +        "l": {
        +          "description": "Lightness as a percentage. Note this is HSL lightness, which is not OKLCH lightness.",
        +          "type": "number"
        +        },
        +        "s": {
        +          "description": "Saturation as a percentage.",
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "h",
        +        "s",
        +        "l"
        +      ],
        +      "type": "object"
        +    },
        +    "hslText": {
        +      "description": "The colour as a CSS hsl() declaration.",
        +      "type": "string"
        +    },
        +    "inGamut": {
        +      "description": "Whether the requested colour exists in sRGB.",
        +      "type": "boolean"
        +    },
        +    "mapped": {
        +      "description": "True when the colour was gamut-mapped to fit sRGB, meaning hex below is a near miss rather than the exact request.",
        +      "type": "boolean"
        +    },
        +    "oklchText": {
        +      "description": "The colour as a CSS oklch() declaration.",
        +      "type": "string"
        +    },
        +    "requested": {
        +      "additionalProperties": false,
        +      "description": "The OKLCH coordinates that were asked for, before any gamut mapping -- keep these to see how far the sRGB answer had to move.",
        +      "properties": {
        +        "c": {
        +          "description": "Requested chroma.",
        +          "type": "number"
        +        },
        +        "h": {
        +          "description": "Requested hue angle in degrees.",
        +          "type": "number"
        +        },
        +        "l": {
        +          "description": "Requested lightness, 0-1.",
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "l",
        +        "c",
        +        "h"
        +      ],
        +      "type": "object"
        +    },
        +    "rgb": {
        +      "additionalProperties": false,
        +      "description": "The same colour as 8-bit sRGB channels.",
        +      "properties": {
        +        "b": {
        +          "description": "Blue channel, 0-255.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "g": {
        +          "description": "Green channel, 0-255.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "r": {
        +          "description": "Red channel, 0-255.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        }
        +      },
        +      "required": [
        +        "r",
        +        "g",
        +        "b"
        +      ],
        +      "type": "object"
        +    },
        +    "rgbText": {
        +      "description": "The colour as a CSS rgb() declaration.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "requested",
        +    "oklchText",
        +    "inGamut",
        +    "mapped",
        +    "hex",
        +    "rgb",
        +    "rgbText",
        +    "hsl",
        +    "hslText"
        +  ],
        +  "type": "object"
        +}
    • Changedoklch_ramp1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "base": {
        +      "additionalProperties": false,
        +      "description": "The OKLCH coordinates the ramp was generated from.",
        +      "properties": {
        +        "c": {
        +          "description": "Base chroma.",
        +          "type": "number"
        +        },
        +        "h": {
        +          "description": "Base hue angle in degrees.",
        +          "type": "number"
        +        },
        +        "l": {
        +          "description": "Base lightness, 0-1.",
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "l",
        +        "c",
        +        "h"
        +      ],
        +      "type": "object"
        +    },
        +    "css": {
        +      "description": "The whole ramp as CSS custom properties, ready to paste into a stylesheet.",
        +      "type": "string"
        +    },
        +    "mode": {
        +      "description": "Which coordinate was varied across the steps, echoed from the input.",
        +      "enum": [
        +        "hue",
        +        "light",
        +        "chroma"
        +      ],
        +      "type": "string"
        +    },
        +    "steps": {
        +      "description": "The ramp in order. Because each step is mapped independently, some can be mapped and others not.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "hex": {
        +            "description": "This step as an sRGB hex string.",
        +            "type": "string"
        +          },
        +          "index": {
        +            "description": "0-based position in the ramp.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "mapped": {
        +            "description": "True when this particular step fell outside sRGB and was gamut-mapped.",
        +            "type": "boolean"
        +          },
        +          "oklch": {
        +            "additionalProperties": false,
        +            "description": "This step's OKLCH coordinates.",
        +            "properties": {
        +              "c": {
        +                "description": "Chroma at this step.",
        +                "type": "number"
        +              },
        +              "h": {
        +                "description": "Hue at this step.",
        +                "type": "number"
        +              },
        +              "l": {
        +                "description": "Lightness at this step.",
        +                "type": "number"
        +              }
        +            },
        +            "required": [
        +              "l",
        +              "c",
        +              "h"
        +            ],
        +            "type": "object"
        +          },
        +          "oklchText": {
        +            "description": "This step as a CSS oklch() declaration.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "index",
        +          "oklch",
        +          "oklchText",
        +          "hex",
        +          "mapped"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "base",
        +    "mode",
        +    "steps",
        +    "css"
        +  ],
        +  "type": "object"
        +}
    • Changedplant_watering_calendar1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "calendar": {
        +      "additionalProperties": false,
        +      "description": "The schedule as a subscribable calendar. Returned inline as text, not as a file download.",
        +      "properties": {
        +        "filename": {
        +          "description": "Suggested filename for the calendar file.",
        +          "type": "string"
        +        },
        +        "icsText": {
        +          "description": "A complete iCalendar document with a repeating event per plant, ready to write to a file and import.",
        +          "type": "string"
        +        },
        +        "mimeType": {
        +          "description": "MIME type of icsText, i.e. text/calendar.",
        +          "type": "string"
        +        }
        +      },
        +      "required": [
        +        "filename",
        +        "mimeType",
        +        "icsText"
        +      ],
        +      "type": "object"
        +    },
        +    "note": {
        +      "description": "Standing caution that these intervals are a starting point to adjust against the actual soil.",
        +      "type": "string"
        +    },
        +    "schedule": {
        +      "description": "One entry per requested plant, in catalogue order.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "baseDays": {
        +            "description": "The plant's baseline interval before pot, light and season adjustments.",
        +            "type": "number"
        +          },
        +          "intervalDays": {
        +            "description": "Days between waterings after every adjustment -- the number to act on.",
        +            "type": "number"
        +          },
        +          "key": {
        +            "description": "Stable identifier for the plant, as given in the input.",
        +            "type": "string"
        +          },
        +          "multiplier": {
        +            "description": "The combined adjustment applied to baseDays, so the difference is auditable rather than magic.",
        +            "type": "number"
        +          },
        +          "name": {
        +            "description": "The plant's common name.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "key",
        +          "name",
        +          "intervalDays",
        +          "baseDays",
        +          "multiplier"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "summary": {
        +      "additionalProperties": false,
        +      "description": "The set at a glance, for deciding a single watering day.",
        +      "properties": {
        +        "chosenCount": {
        +          "description": "How many plants were scheduled.",
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        "maxIntervalDays": {
        +          "description": "The longest interval in the set.",
        +          "type": "number"
        +        },
        +        "minIntervalDays": {
        +          "description": "The thirstiest plant's interval -- the cadence that actually governs a shared watering round.",
        +          "type": "number"
        +        },
        +        "seasonFactor": {
        +          "description": "The season multiplier applied to every plant.",
        +          "type": "number"
        +        }
        +      },
        +      "required": [
        +        "chosenCount",
        +        "minIntervalDays",
        +        "maxIntervalDays",
        +        "seasonFactor"
        +      ],
        +      "type": "object"
        +    }
        +  },
        +  "required": [
        +    "schedule",
        +    "summary",
        +    "calendar",
        +    "note"
        +  ],
        +  "type": "object"
        +}
    • Changedproject_weight_goal_date1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "activityMultiplier": {
        +      "description": "The multiplier the chosen activity level maps to.",
        +      "type": "number"
        +    },
        +    "currentWeightKg": {
        +      "description": "Starting weight in kg, after any imperial conversion.",
        +      "type": "number"
        +    },
        +    "daysToGoal": {
        +      "description": "Days to the goal under the adaptive model, or null when the goal is never reached.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "floorKcal": {
        +      "description": "The intake floor that was enforced, below which the projection is not modelled.",
        +      "type": "number"
        +    },
        +    "gapDays": {
        +      "description": "How many days longer the honest answer is than the naive one -- the whole point of the tool. Null when the goal is never reached.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "goalWeightKg": {
        +      "description": "Target weight in kg, after any imperial conversion.",
        +      "type": "number"
        +    },
        +    "horizonDays": {
        +      "description": "Length of the modelled horizon in days, or null when not modelled.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "intakeKcal": {
        +      "description": "Daily intake implied by the requested deficit, after the safety floor is applied.",
        +      "type": "number"
        +    },
        +    "maintenanceKcal": {
        +      "description": "Maintenance calories at the starting weight.",
        +      "type": "number"
        +    },
        +    "naiveDays": {
        +      "description": "Days predicted by the flat 7,700 kcal-per-kg rule that ignores the falling maintenance -- the figure most calculators stop at.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "naiveLineEndWeek": {
        +      "description": "Where the naive straight line would end on the same axes, so the two can be drawn together. Null when there is no series.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "naiveWeeks": {
        +      "description": "The same figure in whole weeks.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "note": {
        +      "description": "Plain-language reading of the result, including why the two answers differ or why the goal is unreachable.",
        +      "type": "string"
        +    },
        +    "plateau": {
        +      "description": "True when the settle weight is above the goal, i.e. this deficit alone will never get there. Null when it does not apply.",
        +      "type": [
        +        "boolean",
        +        "null"
        +      ]
        +    },
        +    "projectedDate": {
        +      "description": "Calendar date the goal is reached, ISO YYYY-MM-DD, or null when it is not reached.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "reachedWithinHorizon": {
        +      "description": "Whether the goal is reached inside the modelled horizon.",
        +      "type": "boolean"
        +    },
        +    "series": {
        +      "anyOf": [
        +        {
        +          "items": {
        +            "additionalProperties": false,
        +            "properties": {
        +              "weekIndex": {
        +                "description": "Weeks from the reference date.",
        +                "maximum": 9007199254740991,
        +                "minimum": -9007199254740991,
        +                "type": "integer"
        +              },
        +              "weightKg": {
        +                "description": "Projected weight at that week under the adaptive model.",
        +                "type": "number"
        +              }
        +            },
        +            "required": [
        +              "weekIndex",
        +              "weightKg"
        +            ],
        +            "type": "object"
        +          },
        +          "type": "array"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Weekly projection points for plotting. Null when includeChartSeries was false."
        +    },
        +    "settleWeightKg": {
        +      "description": "The weight this intake eventually settles at, since maintenance falls as weight does. Null when it does not apply.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    },
        +    "status": {
        +      "description": "How the projection ended: the goal is reached, or it settles short of the goal, or it is unreachable at this intake. The field to branch on before reading the dates.",
        +      "type": "string"
        +    },
        +    "weeksToGoal": {
        +      "description": "The same figure in whole weeks.",
        +      "type": [
        +        "number",
        +        "null"
        +      ]
        +    }
        +  },
        +  "required": [
        +    "currentWeightKg",
        +    "goalWeightKg",
        +    "activityMultiplier",
        +    "maintenanceKcal",
        +    "intakeKcal",
        +    "floorKcal",
        +    "status",
        +    "settleWeightKg",
        +    "plateau",
        +    "reachedWithinHorizon",
        +    "horizonDays",
        +    "naiveDays",
        +    "naiveWeeks",
        +    "daysToGoal",
        +    "weeksToGoal",
        +    "gapDays",
        +    "projectedDate",
        +    "note",
        +    "series",
        +    "naiveLineEndWeek"
        +  ],
        +  "type": "object"
        +}
    • Changedrecipe_convert_ingredient1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "densityGPerMl": {
        +      "description": "The density used for this ingredient, in grams per millilitre.",
        +      "type": "number"
        +    },
        +    "densityGPerMlRounded": {
        +      "description": "That density rounded for display.",
        +      "type": "number"
        +    },
        +    "grams": {
        +      "description": "The converted weight in grams, unrounded.",
        +      "type": "number"
        +    },
        +    "gramsRounded": {
        +      "description": "The same weight rounded the way a kitchen scale reads it -- the number to actually use.",
        +      "type": "number"
        +    },
        +    "ounces": {
        +      "description": "The converted weight in ounces, unrounded.",
        +      "type": "number"
        +    },
        +    "ouncesRounded": {
        +      "description": "The same weight in ounces, rounded for practical use.",
        +      "type": "number"
        +    },
        +    "waterComparison": {
        +      "description": "How this ingredient compares in weight to the same volume of water -- the intuition that explains why a cup of flour and a cup of honey are nothing alike.",
        +      "type": "string"
        +    },
        +    "workSentence": {
        +      "description": "The conversion written out as an equation, so the caller can check the cup size and density rather than trusting a bare number.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "grams",
        +    "gramsRounded",
        +    "ounces",
        +    "ouncesRounded",
        +    "densityGPerMl",
        +    "densityGPerMlRounded",
        +    "workSentence",
        +    "waterComparison"
        +  ],
        +  "type": "object"
        +}
    • Changedrecipe_scale1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "lines": {
        +      "description": "The ingredient lines rescaled, one output line per input line and in the same order. A line whose quantity could not be parsed is returned unchanged rather than dropped, so the list stays aligned with the recipe it came from.",
        +      "items": {
        +        "type": "string"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "lines"
        +  ],
        +  "type": "object"
        +}
    • Changedrecommend_ai_model1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "adjusted": {
        +      "description": "True when the stated priority (cost or freshness) moved the answer away from the quality-first pick.",
        +      "type": "boolean"
        +    },
        +    "backup": {
        +      "description": "A second choice worth trying if the first does not suit.",
        +      "type": "string"
        +    },
        +    "disclaimer": {
        +      "description": "Standing note that this is a fixed editorial table, not a live benchmark.",
        +      "type": "string"
        +    },
        +    "model": {
        +      "description": "The recommended model's display name.",
        +      "type": "string"
        +    },
        +    "modelKey": {
        +      "description": "Stable identifier for the recommended model, safe to switch on.",
        +      "type": "string"
        +    },
        +    "note": {
        +      "description": "What that adjustment was, when adjusted is true; otherwise null.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "tag": {
        +      "description": "Short label for why this model wins the task, e.g. its standout strength.",
        +      "type": "string"
        +    },
        +    "taskLabel": {
        +      "description": "Human-readable name of the task that was matched.",
        +      "type": "string"
        +    },
        +    "why": {
        +      "description": "The reasoning behind the recommendation.",
        +      "type": "string"
        +    }
        +  },
        +  "required": [
        +    "modelKey",
        +    "model",
        +    "tag",
        +    "taskLabel",
        +    "why",
        +    "backup",
        +    "adjusted",
        +    "note",
        +    "disclaimer"
        +  ],
        +  "type": "object"
        +}
    • Changedrender_app_store_screenshot1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "sizesRendered": {
        +      "description": "One entry per rendered device size. The images themselves come back as separate content blocks -- a single render inline, a multi-page or multi-size batch as a ZIP behind a resource_link.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "height": {
        +            "description": "Canvas height in px.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "key": {
        +            "description": "Identifier for the device size, matching the size input.",
        +            "type": "string"
        +          },
        +          "label": {
        +            "description": "Human-readable device name for this size.",
        +            "type": "string"
        +          },
        +          "pageCount": {
        +            "description": "How many pages were rendered at this size.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "width": {
        +            "description": "Canvas width in px, i.e. the exact pixel size App Store Connect expects.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "key",
        +          "label",
        +          "width",
        +          "height",
        +          "pageCount"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    }
        +  },
        +  "required": [
        +    "sizesRendered"
        +  ],
        +  "type": "object"
        +}
    • Changedresize_images1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "filesOut": {
        +      "description": "How many files were produced, i.e. imagesIn x sizes.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "fit": {
        +      "description": "How each image was fitted to its target box.",
        +      "enum": [
        +        "contain",
        +        "cover",
        +        "stretch"
        +      ],
        +      "type": "string"
        +    },
        +    "imagesIn": {
        +      "description": "How many source images were supplied.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "outputs": {
        +      "description": "Every produced file, so the archive can be checked without unzipping it.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "bytes": {
        +            "description": "Output size in bytes.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "file": {
        +            "description": "Path of the file inside the ZIP.",
        +            "type": "string"
        +          },
        +          "height": {
        +            "description": "Output height in px.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          },
        +          "source": {
        +            "description": "Which input image this file came from.",
        +            "type": "string"
        +          },
        +          "width": {
        +            "description": "Output width in px.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "source",
        +          "file",
        +          "width",
        +          "height",
        +          "bytes"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "paddingColor": {
        +      "description": "The letterbox colour, which only applies to fit 'contain'. Null for 'cover' and 'stretch', where nothing is padded.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "preset": {
        +      "description": "The preset that was applied, echoed from the input.",
        +      "type": "string"
        +    },
        +    "sizes": {
        +      "description": "How many target sizes the chosen preset expands to.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    }
        +  },
        +  "required": [
        +    "imagesIn",
        +    "filesOut",
        +    "sizes",
        +    "preset",
        +    "fit",
        +    "paddingColor",
        +    "outputs"
        +  ],
        +  "type": "object"
        +}
    • Changedstrip_image_metadata1 field changed
      • changedOutput schema / (root)
        Previous value: -nullNew value: +{
        +  "$schema": "http://json-schema.org/draft-07/schema#",
        +  "additionalProperties": false,
        +  "properties": {
        +    "cleanedImageIncluded": {
        +      "description": "Whether a cleaned file accompanies this report as a content block. False when include_cleaned_image was off, when there was nothing to strip, or when verification failed.",
        +      "type": "boolean"
        +    },
        +    "cleanedSizeBytes": {
        +      "anyOf": [
        +        {
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Size of the cleaned file, or null when no cleaned image was produced."
        +    },
        +    "fields": {
        +      "description": "Every metadata field read from the file -- this is the disclosure the caller is usually checking for.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "field": {
        +            "description": "Human-readable field name, e.g. Camera make, Latitude, XMP block.",
        +            "type": "string"
        +          },
        +          "value": {
        +            "description": "The value as found, stringified.",
        +            "type": "string"
        +          }
        +        },
        +        "required": [
        +          "field",
        +          "value"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "fieldsFound": {
        +      "description": "How many individual metadata fields were read out.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "format": {
        +      "description": "Which of the two supported formats the file was read as.",
        +      "enum": [
        +        "jpeg",
        +        "png"
        +      ],
        +      "type": "string"
        +    },
        +    "gps": {
        +      "anyOf": [
        +        {
        +          "additionalProperties": false,
        +          "properties": {
        +            "altitudeMeters": {
        +              "description": "Altitude in metres, when recorded.",
        +              "type": [
        +                "number",
        +                "null"
        +              ]
        +            },
        +            "dateStamp": {
        +              "description": "GPS date stamp, when recorded.",
        +              "type": [
        +                "string",
        +                "null"
        +              ]
        +            },
        +            "latitude": {
        +              "description": "Latitude in decimal degrees, to 6 places.",
        +              "type": "number"
        +            },
        +            "longitude": {
        +              "description": "Longitude in decimal degrees, to 6 places.",
        +              "type": "number"
        +            }
        +          },
        +          "required": [
        +            "latitude",
        +            "longitude",
        +            "altitudeMeters",
        +            "dateStamp"
        +          ],
        +          "type": "object"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Embedded location, or null when the file carried none. The single most sensitive thing in a photo, so it is surfaced on its own rather than only inside fields."
        +    },
        +    "make": {
        +      "description": "Camera make, when EXIF carried one.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "message": {
        +      "description": "Plain-language summary, including why no cleaned file was returned when that is the case.",
        +      "type": "string"
        +    },
        +    "metadataFound": {
        +      "description": "Whether any strippable metadata was present at all.",
        +      "type": "boolean"
        +    },
        +    "model": {
        +      "description": "Camera model, when EXIF carried one.",
        +      "type": [
        +        "string",
        +        "null"
        +      ]
        +    },
        +    "originalSizeBytes": {
        +      "description": "Size of the supplied file in bytes.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "recognized": {
        +      "const": true,
        +      "description": "Always true here. A file that is neither a JPEG nor a PNG comes back as an error result instead, not as recognized:false.",
        +      "type": "boolean"
        +    },
        +    "removedBytes": {
        +      "anyOf": [
        +        {
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "Bytes saved by stripping, or null when no cleaned image was produced."
        +    },
        +    "removedSegmentCount": {
        +      "description": "Number of segments/chunks removed.",
        +      "maximum": 9007199254740991,
        +      "minimum": -9007199254740991,
        +      "type": "integer"
        +    },
        +    "removedSegments": {
        +      "description": "Each metadata segment or chunk that was stripped, with its size.",
        +      "items": {
        +        "additionalProperties": false,
        +        "properties": {
        +          "label": {
        +            "description": "Which segment or chunk was removed, e.g. EXIF, XMP, a PNG tEXt chunk.",
        +            "type": "string"
        +          },
        +          "sizeBytes": {
        +            "description": "Its size in bytes.",
        +            "maximum": 9007199254740991,
        +            "minimum": -9007199254740991,
        +            "type": "integer"
        +          }
        +        },
        +        "required": [
        +          "label",
        +          "sizeBytes"
        +        ],
        +        "type": "object"
        +      },
        +      "type": "array"
        +    },
        +    "rotationWarning": {
        +      "description": "True when an EXIF Orientation tag was removed, which can make the cleaned image appear rotated in viewers that relied on it.",
        +      "type": "boolean"
        +    },
        +    "verified": {
        +      "description": "Result of the byte-for-byte check that the retained image data is unchanged. Null when no cleaning was attempted; false means the check failed and no cleaned file was returned.",
        +      "type": [
        +        "boolean",
        +        "null"
        +      ]
        +    },
        +    "verifiedBytes": {
        +      "anyOf": [
        +        {
        +          "maximum": 9007199254740991,
        +          "minimum": -9007199254740991,
        +          "type": "integer"
        +        },
        +        {
        +          "type": "null"
        +        }
        +      ],
        +      "description": "How many bytes that check compared. Null when no cleaning was attempted."
        +    }
        +  },
        +  "required": [
        +    "recognized",
        +    "format",
        +    "metadataFound",
        +    "fieldsFound",
        +    "fields",
        +    "make",
        +    "model",
        +    "gps",
        +    "rotationWarning",
        +    "removedSegments",
        +    "removedSegmentCount",
        +    "originalSizeBytes",
        +    "cleanedSizeBytes",
        +    "removedBytes",
        +    "verified",
        +    "verifiedBytes",
        +    "cleanedImageIncluded",
        +    "message"
        +  ],
        +  "type": "object"
        +}
  2. 31 tool updates
    • First observedappstore_compare_markets
    • First observedappstore_search
    • First observedbpm_delay_calculator
    • First observedbuild_app_store_link
    • First observedcalculate_app_store_net_revenue
    • First observedcalculate_bmi_and_body_fat
    • First observedcalculate_tdee
    • First observedcheck_contrast
    • First observedcheck_strings_files
    • First observedconvert_heic_to_jpg_png
    • First observedconvert_video_to_gif
    • First observedcss_clamp_calculator
    • First observedestimate_ai_tokens
    • First observedestimate_window_light
    • First observedfind_nearest_passing_color
    • First observedgenerate_app_icon_set
    • First observedgenerate_favicon_set
    • First observedgoai_build_app_privacy_label
    • First observedimage_compress
    • First observedimage_compression_curve
    • First observedinspect_mobileprovision
    • First observedoklch_convert
    • First observedoklch_ramp
    • First observedplant_watering_calendar
    • First observedproject_weight_goal_date
    • First observedrecipe_convert_ingredient
    • First observedrecipe_scale
    • First observedrecommend_ai_model
    • First observedrender_app_store_screenshot
    • First observedresize_images
    • First observedstrip_image_metadata

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    31 deterministic utility tools over one public endpoint: image conversion and lossless EXIF/metadata stripping, App Store screenshot/icon/favicon generation, WCAG and OKLCH colour maths, .strings and provisioning-profile checks, plus TDEE, recipe and plant calculators. No API key, no account, no model calls.
    ISC
  • A
    license
    Not graded
    quality
    B
    maintenance
    21 tools for things AI is bad at — deterministic math, cryptographic randomness, date arithmetic, hashing, encoding, unit conversion, and more.
    171 npm
    9
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    23 developer & data API tools for AI agents - IP/DNS/WHOIS/SSL lookups, web scraping & screenshots, text AI (summarize, translate, sentiment, grammar, redact), and dev utilities (hash, UUID, QR, JWT, cron, IBAN/VAT/email validation, breach check).
    23
    69 PyPI
    MIT
Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources