Skip to main content
Version: 3.1.0

inventory.search

An agent submits constraints and receives priced, paginated vehicle resultsOpen full-size diagram

One request shape across MCP, A2A and PAP. Search without sharing customer contact details. Results state what was filtered, what is known about each vehicle, and how to continue.

MCP toolAAP request → response
aap_inventory_searchinventory.search.request → inventory.search.response

Ask for constraints explicitly​

{
"type": "inventory.search.request",
"filters": {
"body": ["suv"],
"price_max": 35000,
"status": ["available"],
"features_all": ["adaptive_cruise_control", "heated_front_seats"],
"near": { "latitude": 37.7749, "longitude": -122.4194, "radius_mi": 50 }
},
"pagination": { "limit": 20 },
"sort": { "field": "distance_mi", "order": "asc" },
"privacy": { "anonymous": true }
}

The coordinates above are an illustrative San Francisco origin. A client resolves the user's place name before calling; the server MUST NOT guess a location from an ambiguous string.

ConstraintFieldsMeaning
Vehiclemake, model, trim, condition, vehicle_type, bodyArrays of accepted values; sale condition is new, used or cpo.
Powertraintransmission, fuel, driveline, charge_port, dc_fast_chargeThe listing must contain the requested value. Both true and false are constraints.
Rangesyear_min/max, price_min/max, electric_range_mi_min/max, displacement_cc_min/max, mileage_maxInclusive bounds. Inverted ranges are invalid. Price and odometer bounds use the effective query context below.
Equipmentfeatures_all, features_anyAdvertised feature codes with verified, vehicle-specific evidence.
Locationnear, rooftopsRadius around a point, or exact rooftop names on a dealer-scoped endpoint.
Availabilitystatusavailable, intransit, pending; no sold or out-of-stock units.
Identityvin, stockExact identifiers.
Appearanceexterior_color, interior_colorArrays of accepted color values.
KeywordsqueryExplicit keyword matching, described below.

Filter fields combine with AND. Values in an array combine with OR, except features_all, which requires every value. Empty arrays are invalid. Missing listing values never satisfy a constraint. Free-text array values match facet values without case sensitivity; schema enums, feature codes and rooftop names are exact. Providers MUST reject unsupported filters with UNSUPPORTED_FILTER, unsupported ordering with UNSUPPORTED_SORT, and unknown keys or invalid values with SCHEMA_VALIDATION_FAILED. They MUST NOT silently remove, broaden or reinterpret constraints.

Know what this endpoint supports​

Read params.skills["inventory.search"].capabilities on the AAP Agent Card extension, or tools/list → the search tool's _meta["autoagentprotocol.org/aap"].capabilities. Both follow inventory-capabilities.schema.json and MUST agree. PAP clients can follow their discovery document's Agent Card link.

The capabilities name the endpoint's scope (dealer or aggregate), supported filters, sort_fields, default_sort, default_limit, max_limit, and support for an offset entry point. An aggregate endpoint MUST reject rooftops: names are not globally unique. Aggregate results MUST include the owning dealer_id and a stable vehicle_id unique within that endpoint. That ID must resolve the same listing through inventory.vehicle; a dealer-local stock number alone is insufficient.

An aggregate endpoint MUST also expose dealer.information, accepting the listing's dealer_id and returning that actual seller's profile before consent.

A deployment advertising feature filters MUST also expose inventory.facets. Its features facet supplies the feature codes, labels and counts; include supported codes with zero matches when necessary to preserve the vocabulary. An unknown code produces UNSUPPORTED_FILTER with its request path and a recovery hint.

Currency and odometer context​

currency and mileage_unit are optional top-level request fields, separate from filters. The same contexts apply to inventory.facets.

{
"type": "inventory.search.request",
"currency": "CAD",
"mileage_unit": "km",
"filters": { "price_max": 40000, "mileage_max": 80000 },
"sort": { "field": "price", "order": "asc" }
}

The Agent Card and MCP search metadata advertise currencies, default_currency and default_mileage_unit. Omitted capability fields mean currencies: ["USD"], default_currency: "USD" and default_mileage_unit: "mi". A default currency MUST be supported. A Canadian dealer may advertise CAD/km defaults; an aggregate may support both currencies with USD/mi defaults. An omitted request context uses the advertised default. Callers can pass both fields explicitly to avoid relying on deployment defaults.

Services implementing only inventory.facets use the facets-only currency fallback instead of advertising an unimplemented search skill.

  • The effective currency selects one denomination for the entire matching set, including listings without a price. Compare and sort price fields only within it; never sort CAD and USD amounts as interchangeable numbers or invent an exchange rate. A schema-valid but unsupported currency produces SCHEMA_VALIDATION_FAILED at /currency.
  • mileage_unit defines the units of mileage_max, mileage sorting and mileage_range. Both mi and km contexts are supported. Convert known native readings using exactly 1 mi = 1.609344 km before filtering, sorting and range aggregation. Do not round before comparison. Missing odometers never satisfy a mileage bound and sort last in either direction.
  • Search results echo the effective context in data.currency and data.mileage_unit; embedded facets carry the same labels. These response fields may be omitted only for the original USD/mi meanings. CAD or km contexts MUST be explicit, including on empty pages.
  • Each returned Vehicle.currency and Vehicle.mileage_unit describes that vehicle's native numbers. Omitted vehicle labels always mean USD/mi and never inherit request, response, capability or country values. CAD listings MUST label currency: "CAD"; kilometre readings MUST label mileage_unit: "km".

Currency is not a country selector. Use geography for location; a Canadian vehicle may be priced in USD or have a miles odometer. Fields explicitly ending in _mi, including geographic radius and electric range, always remain miles. Cursor identity includes both effective contexts: changing either while continuing returns INVALID_CURSOR.

Equipment needs evidence​

Present and absent equipment require a source; unknown equipment never satisfies a feature filterOpen full-size diagram

features remains a readable equipment list. feature_evidence records a code, label, status, source and a short source excerpt for a specific vehicle. Only status: "present" satisfies feature filters. Every matching result MUST include evidence for all features_all codes and at least one matching features_any code.

A trim name or a model-year default is insufficient. Missing or unknown evidence does not prove presence or absence. A verified absent claim also needs a source. Clients SHOULD show the evidence when explaining why a vehicle matches.

Distance is to the vehicle's rooftop​

A geographic radius includes rooftops inside the boundary and reports actual distance to each matching listingOpen full-size diagram

near contains latitude, longitude and radius_mi. Use straight-line distance on a sphere with mean Earth radius 3958.7613 miles and the haversine formula. Coordinates are WGS84 latitude/longitude. Apply the inclusive radius using the unrounded distance; report distance_mi with precision of 0.001 miles or better. JSON numbers need no trailing zeros. This is not driving distance or a delivery promise.

Each result MUST include distance_mi when near is present. Listings without known physical rooftop coordinates do not match. sort.field: "distance_mi" requires near; a deployment default sort cannot depend on a missing origin.

Keywords do not invent filters​

Trim query, split on whitespace, and match each token case-insensitively as a substring in at least one of make, model, trim, vin, stock or features. All tokens must match somewhere in those fields. Quotes and operators have no special meaning. An all-whitespace query is invalid.

For “hybrid SUV under $35k,” send fuel, body and price_max. Do not rely on the string "hybrid SUV under 35k" to create them. The response echoes the effective query in applied_filters and lists its actual query_terms.

Read a result without guessing​

{
"type": "inventory.search.response",
"data": {
"total": 1,
"limit": 20,
"next_cursor": null,
"applied_filters": { "price_max": 35000 },
"sort": { "field": "price", "order": "asc" },
"vehicles": [{
"dealer_id": "demo-dealer",
"vehicle_id": "vehicle-1042",
"year": 2024,
"make": "Example Motors",
"model": "Tourer",
"condition": "used",
"status": "available",
"price": 32995,
"updated_at": "2026-10-24T10:00:00Z"
}]
}
}

This compact response illustrates a price-only request. The complete feature-and-radius request and matching response include evidence and distance.

Result fieldContract
applied_filtersEvery effective constraint, including any documented defaults. No hidden relaxation.
sortEffective field and direction. Missing values sort last in either direction.
totalExact count across all pages, or null when unavailable. Never a capped count or estimate.
limitEffective page size, from 1 through the advertised maximum, at most 100.
next_cursorOpaque next-page token; null only when the matching set is exhausted.
vehiclesCurrent listings with status, updated_at and at least one of vin, vehicle_id or stock.
facetsOptional counts and ranges across the full matching set, before pagination.

price includes mandatory dealer charges and excludes government-required consumer charges. Any fees are already included. An omitted price is unknown: do not reconstruct it from list_price and fees. See pricing.

available, intransit and pending are different states. A result is not a reservation. updated_at reflects the source's last relevant update; servers MUST NOT invent a fresh timestamp merely because an agent requested the listing.

Continue through the full inventory​

Keep query, filters, sort and page size fixed while following opaque cursors until next_cursor is nullOpen full-size diagram

For the first page, omit cursor and skip. Pass the returned next_cursor in pagination.cursor for the next page, keeping effective filters, sort and limit unchanged. If the first request omitted defaults, carry the returned applied_filters, sort and limit forward explicitly. Stop only when next_cursor is null. A page-size limit MUST NOT become a limit on the entire traversable matching set.

  • Cursor and skip are mutually exclusive. A deployment may accept skip as an advertised entry point; its response still supplies cursor continuation. If offset is false, a supplied skip returns SCHEMA_VALIDATION_FAILED at /pagination/skip, with a hint to start without an offset. A limit above the advertised max_limit also returns SCHEMA_VALIDATION_FAILED; it is never silently clamped.
  • Bind cursors to the endpoint tenant, authorization scope, normalized filters, sort and page size. Do not put customer information in a cursor. Verify scope on every request; a cursor is not authorization.
  • Use a stable identifier (dealer + vehicle for aggregate scope) to break sort ties, and preserve ordering for the cursor's lifetime. Document cursor expiry. An expired, altered or mismatched cursor returns INVALID_CURSOR with a hint to restart the search.
  • Concurrent changes may remove listings. Do not return stale or unavailable vehicles merely to fill a snapshot page. An empty page with a non-null cursor can occur; clients may continue. Clients SHOULD detect repeated cursors and bound retries.
  • privacy.anonymous: true means no customer contact information is attached. It does not disable server authentication or make a PAP session unidentifiable.

Cars, motorcycles and other inventory​

The same contract covers car, motorcycle, trailer, rv and other; omitted vehicle_type means car. Use body and displacement for motorcycles, and electric range, battery and charging fields for EVs of any vehicle type. Populate only relevant fields. See inventory.vehicle for the shared vehicle shape.