Complete tool inventory
Exact tool names, descriptions, schemas, output paths, limits, annotations, and coverage.
discovery/templates/01-tool-inventory.mdView source on GitHub Filled by prompt P0.2 · Read-only source audit · Feeds report §2, §5
Scope and contract notes
The repository has 16 unique MCP tool names. The primary server registers them as async Python functions with bare @mcp.tool() decorators in src/opticat_mcp/server.py; the AgentCore wrapper imports that server and invokes its main() function (deployment/agentcore/agentcore_wrapper.py:31-34). A legacy Lambda handler imports and registers the same 16 functions in its TOOLS and TOOL_SCHEMAS dictionaries (deployment/lambda/lambda_handler.py:14-54).
The dependency is not locked: pyproject.toml:8-11 requires bare mcp, while AgentCore requires mcp>=1.10.0 (deployment/agentcore/requirements-agentcore.txt:1-4). To make the generated schema concrete, this audit loaded the source in an isolated environment with the latest compatible v1 release, mcp==1.29.0. Under that SDK:
-
The exact tool names are the Python function names.
-
The full function docstring is the model-visible tool description.
-
The raw string metadata in annotations such as
Annotated[int, "Vehicle year"]is not emitted as a JSON Schemadescription. The tables therefore distinguish the actual model-visible description (N/A — none emitted) from the verbatim source metadata. -
Every tool has the same declared output schema:
{"type":"object","properties":{"result":{"type":"string","title":"Result"}},"required":["result"]}. -
Every successful tool invocation is serialized to the model as:
{ "content": [ { "type": "text", "text": "<the exact Python string returned by the tool>" } ], "structuredContent": { "result": "<the exact same Python string>" }, "isError": false } -
Strings beginning with
Error:,API Error:, or❌are ordinary successful string returns and therefore still haveisError: false. An uncaught exception, including final credential lookup failure fromget_api_key()(src/opticat_mcp/server.py:22-49), is handled by the SDK as a protocol-level tool error instead. -
No tool passes an
annotations=argument to@mcp.tool(); all generated annotations arenull, includingreadOnlyHint.
All upstream calls are JSON POSTs to https://webservice.opticatonline.com/autocare/v1/services/Catalog.jsonEndpoint, with X-Api-Key and Content-Type: application/json, through a fresh httpx.AsyncClient(timeout=30.0) (src/opticat_mcp/server.py:19,53-66). In the tool blocks below, “operation” means the top-level key in that endpoint’s JSON request.
Tools
Tool: search_parts_by_vehicle
-
Registration and return path:
src/opticat_mcp/server.py:95-204; normal return at:199. -
Description (VERBATIM, full):
Search for parts that fit a specific vehicle by year, make, model.
Use cases:
- "brake pads for a 97 Mustang"
- "spark plugs for my 2020 Buick"
- "O2 sensor for my 2017 F150"
- "AC compressor for a 2010 Toyota Sequoia"
Returns: List of matching parts with brand codes, part numbers, and descriptions.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)yearinteger Yes N/A N/A — none emitted Vehicle year (e.g., 2007)makestring Yes N/A N/A — none emitted Vehicle make (e.g., Ford, Toyota)modelstring Yes N/A N/A — none emitted Vehicle model (e.g., Mustang, Camry)part_typestring No nullN/A — none emitted Optional: Part type to filter (e.g., 'brake pad', 'oil filter') -
Output shape (representative; formatter at
src/opticat_mcp/server.py:169-199):{ "content": [{"type": "text", "text": "Found 2 parts for 2007 Ford Mustang\n\n• Example Brand P-1\n Brand Code: EXMP\n Engine Oil Filter\n Spin-on oil filter\n\n"}], "structuredContent": {"result": "Found 2 parts for 2007 Ford Mustang\n\n• Example Brand P-1\n Brand Code: EXMP\n Engine Oil Filter\n Spin-on oil filter\n\n"}, "isError": false } -
Upstream endpoint(s) + fixed params: Three calls to the common endpoint: (1)
getAutoCareVehicleResultswithyears=str(year)andmakeFacets.enabled="true"(:122-128); (2) the same operation withyears,makeIds, andbaseVehicleFacets.enabled="true"(:136-143); (3)getAutoCareSearchResultswith resolvedbaseVehicleId, fixedbaseVehicleRegionId=1,perPage=20,includeParts=true, andacesAttributeFacets.enabled=true, plus optionalsearchQuery=part_type(:150-164). The facet helper injects string-valuedpageandperPage(:69-93). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: Make and model lookup each scan at most five 100-item facet pages (
:69,74-77); matching is first case-insensitive substring match (:85-87). At most five multi-option facets and five options per facet are rendered (:173-182). Only the first ten parts are rendered (:188), with brand name, part number, brand code, part type, and the firstSHOdescription (:188-197). The resolvedbaseVehicleId, paging controls, configuration selections, asset fields, and other PIES fields are omitted.
Tool: get_vehicle_years
-
Registration and return path:
src/opticat_mcp/server.py:206-222; normal return at:219. -
Description (VERBATIM, full):
Get all available vehicle years in the catalog
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source metadata N/A — tool accepts no parameters N/A N/A N/A N/A N/A -
Output shape (representative;
src/opticat_mcp/server.py:217-219):{ "content": [{"type": "text", "text": "Available years:\n2025 (1234 vehicles)\n2024 (1200 vehicles)"}], "structuredContent": {"result": "Available years:\n2025 (1234 vehicles)\n2024 (1200 vehicles)"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCareVehicleResultswithyearFacets.enabled="true"and stringperPage="50"(src/opticat_mcp/server.py:212-217). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: Requests one page of at most 50 year facets; no pagination (
:212-219). Renders onlyyearandcount.
Tool: get_vehicle_makes
-
Registration and return path:
src/opticat_mcp/server.py:224-265; normal return at:262. -
Description (VERBATIM, full):
Get all available makes for a specific year
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)yearinteger Yes N/A N/A — none emitted Vehicle year -
Output shape (representative;
src/opticat_mcp/server.py:261-262):{ "content": [{"type": "text", "text": "Makes for 2025:\nFord (ID: 54, 120 models)\nToyota (ID: 76, 98 models)"}], "structuredContent": {"result": "Makes for 2025:\nFord (ID: 54, 120 models)\nToyota (ID: 76, 98 models)"}, "isError": false } -
Upstream endpoint(s) + fixed params: Repeated
getAutoCareVehicleResultscalls withyears=str(year)andmakeFacets={enabled:"true", page:str(page), perPage:"100"}(src/opticat_mcp/server.py:232-259). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: Up to ten pages / 1,000 returned facet entries (
:232-259). Renders onlymakeName,makeId, andcount(:261).
Tool: get_vehicle_models
-
Registration and return path:
src/opticat_mcp/server.py:267-289; normal return at:286. -
Description (VERBATIM, full):
Get all available models for a specific year and make
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)yearinteger Yes N/A N/A — none emitted Vehicle yearmake_idinteger Yes N/A N/A — none emitted Make ID from get_vehicle_makes -
Output shape (representative;
src/opticat_mcp/server.py:283-286):{ "content": [{"type": "text", "text": "Models for 2025:\nMustang (Base Vehicle ID: 12345)\nF-150 (Base Vehicle ID: 67890)"}], "structuredContent": {"result": "Models for 2025:\nMustang (Base Vehicle ID: 12345)\nF-150 (Base Vehicle ID: 67890)"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCareVehicleResultswithyears=str(year),makeIds=str(make_id), andbaseVehicleFacets={enabled:"true", perPage:"100"}(src/opticat_mcp/server.py:276-283). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: One page, requested at 100 facets; no pagination (
:276-286). Renders onlymodelNameandbaseVehicleId.
Tool: get_part_details
-
Registration and return path:
src/opticat_mcp/server.py:291-356; normal return at:353. -
Description (VERBATIM, full):
Get summary of part details (brand, type, first 3 descriptions, first 5 attributes).
For complete data, use these dedicated tools:
- get_part_attributes() - ALL technical specifications
- get_part_descriptions() - ALL product descriptions
- get_part_cross_references() - ALL cross-references
- get_part_supersession() - ALL supersession info
Returns: Quick overview of the part.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)brand_codestring Yes N/A N/A — none emitted Brand code (e.g., BBCW, DJVT)part_numberstring Yes N/A N/A — none emitted Part number -
Output shape (representative;
src/opticat_mcp/server.py:321-353):{ "content": [{"type": "text", "text": "Example Brand P-1\nBrand Code: EXMP\nPart Type: Engine Oil Filter\n\nDescriptions:\n Short Description: Spin-on oil filter\n\nAttributes (1 total, showing first 5):\n Height: 3.5\n\nCross-References: 1 found (use get_part_cross_references for details)\nSupersession: Replaces 1 older parts, Replaced by 0 newer parts (use get_part_supersession for details)\n"}], "structuredContent": {"result": "Example Brand P-1\nBrand Code: EXMP\nPart Type: Engine Oil Filter\n\nDescriptions:\n Short Description: Spin-on oil filter\n\nAttributes (1 total, showing first 5):\n Height: 3.5\n\nCross-References: 1 found (use get_part_cross_references for details)\nSupersession: Replaces 1 older parts, Replaced by 0 newer parts (use get_part_supersession for details)\n"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCarePartDetailswithbrandCode=brand_codeandpartNumber=part_number(src/opticat_mcp/server.py:309-316). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: Requires response
status == 200(:318-319). Renders identity/type, first three descriptions, each truncated to 100 characters (:326-332), first five product attributes without units (:334-340), and only counts for interchanges andreplaces/replacedBy(:342-351). It discards all other PIES fields, includingdigitalAssets, direct image URLs,extendedInformation, and lifecycle/LIF values.
Tool: get_part_applications
-
Registration and return path:
src/opticat_mcp/server.py:358-401; normal return at:398. -
Description (VERBATIM, full):
Get ACES fitment data showing what vehicles a specific part fits.
Use cases:
- "What cars does Bosch 9619 fit?"
- "What engines does a PH12060 fit?"
- "Show me all vehicles that use this part"
Returns: List of vehicle configurations with years, makes, models, and application details.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)brand_codestring Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)part_numberstring Yes N/A N/A — none emitted Part numberper_pageinteger No 10N/A — none emitted Results per page (default: 10) -
Output shape (representative;
src/opticat_mcp/server.py:385-398):{ "content": [{"type": "text", "text": "Part fits 1 vehicle configurations\n\n• 2015 Ford F-150\n Engine Oil Filter\n Qty: 1\n\n"}], "structuredContent": {"result": "Part fits 1 vehicle configurations\n\n• 2015 Ford F-150\n Engine Oil Filter\n Qty: 1\n\n"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCarePartApplicationswithbrandCode,partNumber, fixedincludeResults=true, and caller-controlledperPage(src/opticat_mcp/server.py:376-385). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: Regardless of requested
per_page, only the first ten returned applications are rendered (:390). No page parameter or follow-up pagination is exposed. OnlybaseVehicleName,partTypeName, and optionalqtyare rendered (:391-396); application qualifiers, IDs, notes, attributes, and pagination metadata are omitted.
Tool: search_parts
-
Registration and return path:
src/opticat_mcp/server.py:403-467; normal return at:460. -
Description (VERBATIM, full):
General part search with filters for brand, part type, vehicle, etc.
IMPORTANT FOR RETAILER BRANDS (NAPA, AutoZone, O'Reilly, Advance Auto):
- These brands are NOT manufacturers and won't be found directly
- Retailer parts appear in cross-references of manufacturer parts
- If user provides retailer brand + part number, ask for part type first
- Then search with part number + part_type_ids filter
- Use get_part_cross_references on results to find retailer equivalents
Use cases:
- "NAPA Gold 7042" → Ask: "What type of part?" → search_parts("7042", part_type_ids=5340)
- "PH12060" → search_parts("PH12060") → manufacturer part found directly
- "Bosch oil filter" → search_parts("oil filter", brand_codes="BBHK")
Returns: Matching parts with brand codes, part numbers, and descriptions.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)search_querystring No nullN/A — none emitted Part number or keyword searchbrand_codesstring No nullN/A — none emitted Optional: Brand code filterbase_vehicle_idinteger No nullN/A — none emitted Optional: Base vehicle IDpart_type_idsinteger No nullN/A — none emitted Optional: Part type IDper_pageinteger No 15N/A — none emitted Results per page (default: 15)The generated schema makes the first four properties optional with
default: null, but their JSON Schema types do not includenull; omission is valid, while an explicit JSONnullmay fail schema validation. -
Output shape (representative;
src/opticat_mcp/server.py:444-460):{ "content": [{"type": "text", "text": "Found 1 parts\n\n• Example Brand P-1\n Brand Code: EXMP\n Engine Oil Filter\n Spin-on oil filter\n\n"}], "structuredContent": {"result": "Found 1 parts\n\n• Example Brand P-1\n Brand Code: EXMP\n Engine Oil Filter\n Spin-on oil filter\n\n"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCareSearchResultswith fixedincludeParts=trueandperPage=min(per_page,20); optionalsearchQuery,brandCodes=[brand_codes],baseVehicleId, andpartTypeIds=[part_type_ids]are added only when truthy (src/opticat_mcp/server.py:430-444). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: Upstream request is capped at 20 even if a larger
per_pageis supplied (:433); only the first ten returned parts are rendered (:449). No page is exposed. Output is limited to brand name, part number, brand code, part type, and firstSHOdescription (:449-458).
Tool: get_brand_codes
-
Registration and return path:
src/opticat_mcp/server.py:469-502; normal return at:499. -
Description (VERBATIM, full):
Get list of valid brand codes with names. Essential for using other tools that require brand codes.
Use cases:
- Find the correct brand code for "Bosch" → returns BBHK
- Find the correct brand code for "FRAM" → returns BCWZ
- List all available brands
Returns: Brand names, codes, and part counts.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)search_querystring No nullN/A — none emitted Optional: Filter brands by name -
Output shape (representative;
src/opticat_mcp/server.py:492-499):{ "content": [{"type": "text", "text": "Found 1 brands\n\n• Bosch (Code: BBHK) - 1234 parts\n"}], "structuredContent": {"result": "Found 1 brands\n\n• Bosch (Code: BBHK) - 1234 parts\n"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCareSearchResultswithbrandFacets={enabled:true, perPage:100}and optionalsearchQuery(src/opticat_mcp/server.py:483-492). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: Requests one page of 100 facets, renders only the first 50 (
:486,496), and exposes only brand name, brand code, and count. No paging.
Tool: discover_part_types
-
Registration and return path:
src/opticat_mcp/server.py:504-558; normal return at:555. -
Description (VERBATIM, full):
Discover possible part types for a search query. CRITICAL for retailer brand queries.
WORKFLOW FOR RETAILER BRANDS (NAPA, AutoZone, O'Reilly, Advance Auto):
- User asks: "I have NAPA 7042, what's the Bendix equivalent?"
- DON'T ask user for part type - they might not know!
- Call discover_part_types("7042") FIRST
- Present findings to user: "I found that 7042 matches Engine Oil Filter (72 parts). Is this correct?"
- Wait for user confirmation before proceeding
- If confirmed, call search_parts("7042", part_type_ids=5340)
- Then get_part_cross_references on results
- Find NAPA and target brand in cross-references
IMPORTANT: Always present the discovered part type(s) to the user and ask for confirmation before proceeding with the search. If multiple part types are found, list the top 3-5 and ask which one is correct.
Use cases:
- "NAPA Gold 7042" → discover_part_types("7042") → "Found Engine Oil Filter (72 parts). Is this correct?"
- "PH12060" → discover_part_types("PH12060") → "Found Engine Oil Filter (156 parts). Is this correct?"
- "AC Delco 41-962" → discover_part_types("41-962") → "Found Spark Plug (8 parts). Is this correct?"
Returns: List of part types that match the search query with counts.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)search_querystring Yes N/A N/A — none emitted Part number or keyword to discover part types for -
Output shape (representative;
src/opticat_mcp/server.py:542-555):{ "content": [{"type": "text", "text": "Found 1 part types for '7042':\n\n• Engine Oil Filter (ID: 5340) - 72 parts\n"}], "structuredContent": {"result": "Found 1 part types for '7042':\n\n• Engine Oil Filter (ID: 5340) - 72 parts\n"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCareSearchResultswithsearchQuery=search_queryandpartTypeFacets={enabled:true, perPage:50}(src/opticat_mcp/server.py:534-542). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: One page requested at 50; first 20 types rendered (
:538,549), with a count of additional types only if the one returned page contains more than 20 (:552-553). Only part type name, ID, and count are exposed.
Tool: get_vehicles_by_vin
-
Registration and return path:
src/opticat_mcp/server.py:560-610; normal return at:607. -
Description (VERBATIM, full):
Decode VIN and return matching vehicles with year, make, model, and base vehicle information.
Use cases:
- "What kind of car is this VIN?"
- "Decode VIN 1FAHP3F29CL123456"
- "What engine did the car with this VIN come with?"
Note: VIN decode requires subscription upgrade. Contact OptiCat if not enabled.
Returns: Vehicle details including years, makes, models, and base vehicle IDs for parts lookup.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)vinstring Yes N/A N/A — none emitted Vehicle Identification Number (VIN) -
Output shape (representative;
src/opticat_mcp/server.py:577-607):{ "content": [{"type": "text", "text": "VIN: 1FAHP3F29CL123456\nMatching Base Vehicles: 1\n\nYears: 2012\nMakes: Ford\nModels: Focus\n\nBase Vehicles:\n• 2012 Ford Focus (Base Vehicle ID: 12345, Region: USA)\n"}], "structuredContent": {"result": "VIN: 1FAHP3F29CL123456\nMatching Base Vehicles: 1\n\nYears: 2012\nMakes: Ford\nModels: Focus\n\nBase Vehicles:\n• 2012 Ford Focus (Base Vehicle ID: 12345, Region: USA)\n"}, "isError": false }Subscription-disabled representative return (
src/opticat_mcp/server.py:580-582) is a normal text result:{ "content": [{"type": "text", "text": "❌ VIN Decode Not Enabled\n\nVIN: 1FAHP3F29CL123456\n\nError: VIN decode is not enabled for this API account\n\nTo enable VIN decode functionality, please contact OptiCat support to upgrade your subscription."}], "structuredContent": {"result": "❌ VIN Decode Not Enabled\n\nVIN: 1FAHP3F29CL123456\n\nError: VIN decode is not enabled for this API account\n\nTo enable VIN decode functionality, please contact OptiCat support to upgrade your subscription."}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCareVehiclesByVINwith onlyvin=vin(src/opticat_mcp/server.py:576-578). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: No local VIN length/check-digit validation. All returned matching years, makes, and models are printed, but only the first ten matching base vehicles (
:587-605). Output exposes year/make/model/base vehicle ID/region, not engine, trim, transmission, or other configuration details.
Tool: get_vehicle_assets
-
Registration and return path:
src/opticat_mcp/server.py:612-646; normal return at:643. -
Description (VERBATIM, full):
Get vehicle assets and specifications for a specific base vehicle.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)base_vehicle_idinteger Yes N/A N/A — none emitted Base vehicle ID from vehicle searchbase_vehicle_region_idinteger No 1N/A — none emitted Region ID (1=USA, 2=CAN, 3=MEX) -
Output shape (representative;
src/opticat_mcp/server.py:630-643):{ "content": [{"type": "text", "text": "Found 1 assets for base vehicle 12345\n\n• Example Brand - Vehicle Photo\n Type: Image\n Applications: Front, Exterior\n\n"}], "structuredContent": {"result": "Found 1 assets for base vehicle 12345\n\n• Example Brand - Vehicle Photo\n Type: Image\n Applications: Front, Exterior\n\n"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCareVehicleAssetswithbaseVehicleId, caller/defaultbaseVehicleRegionId, fixedincludeAssets=true, andperPage=20(src/opticat_mcp/server.py:620-630). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: Only first ten returned assets (
:635) and first three application-summary strings (:639-640). Renders only brand name, asset name, derived type name, and application summary (:634-641). It omits asset identifiers, links, files, and URLs. This is an ACES vehicle asset tool, not a PIES part-image tool.
Tool: get_part_cross_references
-
Registration and return path:
src/opticat_mcp/server.py:648-710; normal return at:707. -
Description (VERBATIM, full):
Get cross-reference information (OE equivalents, aftermarket equivalents) for a specific part.
IMPORTANT: This is where retailer brand parts (NAPA, AutoZone, etc.) are found! Retailer parts appear in the partInterchanges array of manufacturer parts.
Workflow for retailer brand queries:
- User asks: "I have NAPA Gold 7042, what's the Bendix equivalent?"
- Ask user for part type: "What type of part is NAPA Gold 7042?"
- Search with part type: search_parts("7042", part_type_ids=5340)
- Get cross-references for found parts: get_part_cross_references(brand_code, part_number)
- Look for NAPA in the cross-references list
- Look for target brand (Bendix) in the same list
Use cases:
- "I have NAPA 7042, what's the Bendix equivalent?" → Find manufacturer part with NAPA 7042 in cross-refs
- "What's the Bosch version of AC Delco 41-962?" → Get AC Delco cross-refs, find Bosch
- "What is the OE part equivalent for PH12060?" → Get FRAM cross-refs, filter for OE brands
- "Show me all interchangeable parts" → Returns complete cross-reference list
Returns: List of equivalent parts from other brands including retailer brands (NAPA, AutoZone, etc).
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)brand_codestring Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)part_numberstring Yes N/A N/A — none emitted Part number -
Output shape (representative;
src/opticat_mcp/server.py:684-707):{ "content": [{"type": "text", "text": "Example Brand P-1\nEngine Oil Filter\n\nCross-References (1 found):\n\n• NAPA 7042\n Type: Interchange\n Brand Code: NAPA\n Notes: Equivalent\n\n"}], "structuredContent": {"result": "Example Brand P-1\nEngine Oil Filter\n\nCross-References (1 found):\n\n• NAPA 7042\n Type: Interchange\n Brand Code: NAPA\n Notes: Equivalent\n\n"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCarePartDetailswithbrandCodeandpartNumber(src/opticat_mcp/server.py:677-684), then readspiesItem.partInterchanges(:689-698). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: No local cap on returned
partInterchanges. Each item is reduced to brand name, part number, interchange type name/code, brand code, and optional notes (:698-705). It requires a canonical source brand/part pair, has no target-brand filter, and does not recursively verify or expand results.
Tool: get_part_supersession
-
Registration and return path:
src/opticat_mcp/server.py:712-767; normal return at:764. -
Description (VERBATIM, full):
Get supersession information (what this part replaces, what replaces this part) for discontinued or updated parts.
Use cases:
- "My manual says AC Delco 41-962 but I can't find it"
- "Is there a newer version of this part?"
- "What part replaced this discontinued one?"
- "Show me the supersession chain"
Returns: List of older parts this replaces and newer parts that replace this one.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)brand_codestring Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)part_numberstring Yes N/A N/A — none emitted Part number -
Output shape (representative;
src/opticat_mcp/server.py:737-764):{ "content": [{"type": "text", "text": "Example Brand P-1\nEngine Oil Filter\n\nThis part REPLACES (1 older parts):\n • Example Brand OLD-1\n\nThis part is REPLACED BY (1 newer parts):\n • Example Brand NEW-1\n\n"}], "structuredContent": {"result": "Example Brand P-1\nEngine Oil Filter\n\nThis part REPLACES (1 older parts):\n • Example Brand OLD-1\n\nThis part is REPLACED BY (1 newer parts):\n • Example Brand NEW-1\n\n"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCarePartDetailswithbrandCodeandpartNumber(src/opticat_mcp/server.py:730-737), then readspiesItem.replacesandpiesItem.replacedBy(:742-747). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: No local cap, but only one-hop arrays from the one details response are read; the function does not recursively walk a “chain.” Each entry is reduced to brand name and part number (
:752-762). It does not readextendedInformationor its lifecycleLIFrecords.
Tool: check_part_fitment_for_vehicle
-
Registration and return path:
src/opticat_mcp/server.py:769-850; normal return at:847, negative return at:832. -
Description (VERBATIM, full):
Check if a specific part fits a specific vehicle. Returns yes/no with application details.
Use cases:
- "Does this part fit my 2010 Camry?"
- "Will FRAM PH12060 work on my 2015 F-150?"
- "Check if Bosch 9619 fits a 2018 Accord"
Returns: Clear yes/no answer with matching configurations and application notes.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)brand_codestring Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)part_numberstring Yes N/A N/A — none emitted Part numberyearinteger Yes N/A N/A — none emitted Vehicle yearmakestring Yes N/A N/A — none emitted Vehicle makemodelstring Yes N/A N/A — none emitted Vehicle model -
Output shape (representative positive result;
src/opticat_mcp/server.py:828-847):{ "content": [{"type": "text", "text": "✅ EXMP P-1 FITS 2015 Ford F-150\n\nFound 1 matching configurations:\n\n• Engine Oil Filter\n Quantity: 1\n Note: With standard engine\n\n"}], "structuredContent": {"result": "✅ EXMP P-1 FITS 2015 Ford F-150\n\nFound 1 matching configurations:\n\n• Engine Oil Filter\n Quantity: 1\n Note: With standard engine\n\n"}, "isError": false }The exact negative string is
❌ {brand_code} {part_number} does NOT fit {year} {make} {model}(:830-832), wrapped in the same MCP envelope withisError:false. -
Upstream endpoint(s) + fixed params: Three calls: (1)
getAutoCareVehicleResultswithyears=str(year)andmakeFacets={enabled:true, perPage:100}(src/opticat_mcp/server.py:790-799); (2) the same operation withyears, resolvedmakeIds, andbaseVehicleFacets={enabled:true, perPage:100}(:804-813); (3)getAutoCarePartApplicationswithbrandCode,partNumber,baseVehicleIds=[resolved ID], fixedincludeResults=true, andperPage=5(:818-828). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: Make and model resolution each inspect only one 100-entry facet page and select the first case-insensitive substring match (
:797-813). Application request and rendered details are capped at five (:825,837); notes are capped at two per application (:842-844). Yes/no is based solely ontotal != 0for the resolved base vehicle, so it does not prove fitment for a specific engine/trim/transmission configuration. Rendered application fields are only part type, quantity, and notes.
Tool: get_part_attributes
-
Registration and return path:
src/opticat_mcp/server.py:852-898; normal return at:895. -
Description (VERBATIM, full):
Get ALL product attributes for a specific part (dimensions, specifications, technical details).
Use when you need complete technical specifications beyond the summary in get_part_details.
Returns: Full list of PAdb attributes with values and units of measure.
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)brand_codestring Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)part_numberstring Yes N/A N/A — none emitted Part number -
Output shape (representative;
src/opticat_mcp/server.py:873-895):{ "content": [{"type": "text", "text": "Example Brand P-1\nPart Type: Engine Oil Filter\n\nProduct Attributes (1 total):\n\n• Height: 3.5 Inch\n"}], "structuredContent": {"result": "Example Brand P-1\nPart Type: Engine Oil Filter\n\nProduct Attributes (1 total):\n\n• Height: 3.5 Inch\n"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCarePartDetailswithbrandCodeandpartNumber(src/opticat_mcp/server.py:866-873), then readspiesItem.productAttributes(:878-879). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: No local item cap or string truncation. Every returned attribute is reduced to PAdb name or custom name, value, and optional unit name (
:888-893). Other attribute metadata and all non-attribute PIES fields are omitted.
Tool: get_part_descriptions
-
Registration and return path:
src/opticat_mcp/server.py:900-944; normal return at:941. -
Description (VERBATIM, full):
Get ALL descriptions for a specific part (marketing copy, technical descriptions, features).
Use when you need complete product descriptions beyond the summary in get_part_details.
Returns: Full list of all description types (short, long, marketing, etc).
-
Input schema:
Param Type Req Default Description emitted to model (verbatim) Source Annotatedmetadata (verbatim; not emitted)brand_codestring Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)part_numberstring Yes N/A N/A — none emitted Part number -
Output shape (representative;
src/opticat_mcp/server.py:921-941):{ "content": [{"type": "text", "text": "Example Brand P-1\nPart Type: Engine Oil Filter\n\nDescriptions (1 total):\n\n• Short Description:\n Spin-on oil filter\n\n"}], "structuredContent": {"result": "Example Brand P-1\nPart Type: Engine Oil Filter\n\nDescriptions (1 total):\n\n• Short Description:\n Spin-on oil filter\n\n"}, "isError": false } -
Upstream endpoint(s) + fixed params:
getAutoCarePartDetailswithbrandCodeandpartNumber(src/opticat_mcp/server.py:914-921), then readspiesItem.descriptions(:926-927). -
Annotations: None; generated value is
null. -
Hardcoded limits / whitelist: No local item cap or truncation. Every returned description is reduced to description type name/code and value (
:936-939); other description metadata and all non-description PIES fields are omitted.
Alternate in-repo registrations and schema drift
The legacy Lambda handler exposes the same 16 names, but tools/list uses independently hand-written schemas (deployment/lambda/lambda_handler.py:53-242) rather than the FastMCP-generated schemas above. Its exact descriptions are:
| Tool | Legacy Lambda description (verbatim) |
|---|---|
search_parts_by_vehicle |
Search for parts that fit a specific vehicle by year, make, model |
get_vehicle_years |
Get all available vehicle years |
get_vehicle_makes |
Get all available makes for a specific year |
get_vehicle_models |
Get all available models for a specific year and make |
get_part_details |
Get detailed PIES information for a specific part |
get_part_applications |
Get ACES fitment data for a specific part |
search_parts |
General part search with filters |
get_brand_codes |
Get list of valid brand codes with names |
discover_part_types |
Discover possible part types for a search query. CRITICAL for retailer brand queries. |
get_vehicles_by_vin |
Get vehicle information by VIN |
get_vehicle_assets |
Get vehicle images and assets |
get_part_cross_references |
Get cross-reference parts (equivalent parts from other brands) |
get_part_supersession |
Get supersession information (replacement parts) |
check_part_fitment_for_vehicle |
Check if a specific part fits a vehicle |
get_part_attributes |
Get detailed attributes for a part |
get_part_descriptions |
Get marketing descriptions for a part |
Important Lambda schema differences:
- Many properties omit descriptions altogether; where present, they are the shorter strings at
deployment/lambda/lambda_handler.py:54-240, not theAnnotatedstrings in the source functions. - The Lambda schema for
get_vehicle_assetsomits the optionalbase_vehicle_region_idparameter (:168-177), even though the callable supports it. - Lambda
tools/callserializes every tool return only as{"content":[{"type":"text","text":"<string>"}]}(:299-305); it does not includestructuredContent,result, orisError. The full JSON-RPC response is then JSON-encoded into the Lambda proxy response body (:320-326). deployment/lambda/tool_schema.json:4-155is a separate static AgentCore schema containing only seven tools. It is not the Lambda handler’stools/listregistry and omits all nine later tools, including VIN, cross-reference, supersession, fitment verification, attributes, and descriptions.
Lookup-type coverage
| Lookup type | Coverage | Tool(s) | Evidence |
|---|---|---|---|
| Vehicle → part | Partially covered | search_parts_by_vehicle; alternatively get_vehicle_years → get_vehicle_makes → get_vehicle_models → search_parts |
The direct tool resolves year/make/model to a base vehicle and searches parts (src/opticat_mcp/server.py:120-164), but picks first substring matches, returns only ten parts, does not expose the resolved ID, and cannot accept engine/trim/transmission qualifiers (:169-199). |
| VIN decode | Partially covered | get_vehicles_by_vin |
Registered at src/opticat_mcp/server.py:560; invokes getAutoCareVehiclesByVIN at :576-578. It is account/subscription-dependent and may return the explicit 403 message at :580-582; it does not decode detailed configuration or chain directly to parts. |
| Part → fitment | Fully covered at base-vehicle/application-list level; configuration-level coverage is partial | get_part_applications, check_part_fitment_for_vehicle |
One lists part applications (src/opticat_mcp/server.py:358-398); the other accepts part + year/make/model and returns explicit yes/no (:769-847). Both truncate results, and the verifier resolves only a base vehicle rather than engine/trim-specific configuration. |
| Interchange / cross-reference | Partially covered | discover_part_types, search_parts, get_part_cross_references |
The details endpoint’s partInterchanges array is read and returned (src/opticat_mcp/server.py:677-707). Retailer-number-first lookup depends on heuristic discovery/search, requires a canonical result first, and has no target-brand filter or recursive verification. |
| Supersession | Partially covered | get_part_supersession; summary count in get_part_details |
replaces and replacedBy are explicitly read (src/opticat_mcp/server.py:746-747; counts also at :347-351), but only one hop is returned and lifecycle extendedInformation/LIF is never read. |
Key resolutions
| Question | Answer | Evidence (file:line) |
|---|---|---|
| VIN tool exists? Registered? Disabled how? | Yes. get_vehicles_by_vin is registered with @mcp.tool() and actively calls the VIN operation. It is not commented out, feature-flagged, or code-disabled. The only disabling mechanism is upstream account entitlement: a response body with status == 403 becomes a “VIN Decode Not Enabled” text result. It is also in the Lambda TOOLS and TOOL_SCHEMAS registries, but absent from the stale seven-tool static schema. |
src/opticat_mcp/server.py:560-582; deployment/lambda/lambda_handler.py:24,44,157-166; omission visible in complete deployment/lambda/tool_schema.json:4-155. |
| Any code path reads supersession/lifecycle fields? | Supersession: yes. Lifecycle: no. get_part_details reads replaces/replacedBy to count them, and get_part_supersession reads and returns those arrays. No source code reads extendedInformation, expiCode, or LIF, even though the bundled API manual shows lifecycle records there. |
src/opticat_mcp/server.py:347-351,746-762; lifecycle examples in data/OptiCat OnLine Web Services Manual.txt:641-675,1245-1294; no source-code match for extendedInformation, expiCode, or LIF. |
| Fitment-verification tool exists? | Yes. check_part_fitment_for_vehicle(brand_code, part_number, year, make, model) resolves a base vehicle, calls getAutoCarePartApplications filtered by that ID, and returns explicit FITS / does NOT fit. Its answer is base-vehicle-level, not engine/trim-specific. |
Registration/signature at src/opticat_mcp/server.py:769-786; filtered call at :818-828; yes/no branches at :830-847. |
| Image chain break (CC-01) — exact line | The April outcome is confirmed (the model cannot retrieve a part image end-to-end), but the stated asset-ID mechanism is refuted. There is no registered get_part_assets tool and no tool accepts an asset-link ID. getAutoCarePartDetails returns piesItem; the bundled OptiCat sample shows piesItem.digitalAssets already containing direct imageURL50 through imageURL1600 URLs. get_part_details reads the full piesItem, constructs a text whitelist that never reads digitalAssets, and the chain breaks when it returns that URL-free string at src/opticat_mcp/server.py:353. The only asset tool is get_vehicle_assets, which is for a base vehicle and itself returns no URL. |
April observation: discovery/templates/opticat-eval-cases-v1.yaml:188-196. Part-details fetch/whitelist/return: src/opticat_mcp/server.py:309-353. Direct part-image URLs in API response: data/OptiCat OnLine Web Services Manual.txt:783-800. Only vehicle-assets registration and URL-free formatter: src/opticat_mcp/server.py:612-643. Complete primary registry: src/opticat_mcp/server.py:95-944; complete Lambda registry: deployment/lambda/lambda_handler.py:34-50. |
Part-image chain, end to end
- The model can obtain a canonical
brand_codeandpart_numberfromsearch_partsorsearch_parts_by_vehicle(src/opticat_mcp/server.py:188-197,449-458). - It can call
get_part_details(brand_code, part_number), which invokesgetAutoCarePartDetailsand obtains the completepiesItem(:291-321). - Per the bundled OptiCat Web Services Manual, that
piesItemmay containdigitalAssetsentries with direct size-specific image URLs; no second asset-ID request is shown or required (data/OptiCat OnLine Web Services Manual.txt:783-800). - The formatter outputs only identity, descriptions, attributes, cross-reference count, and supersession count (
src/opticat_mcp/server.py:322-351), then returns the reduced string at:353. This is the exact break. - No
get_part_assetstool exists to recover the omitted fields.get_vehicle_assetsis not a fallback: it calls the differentgetAutoCareVehicleAssetsoperation with abaseVehicleId(:621-627) and emits no usable URL (:634-643).
Therefore CC-01 correctly identifies a composition failure, but “get_part_assets needs an asset link ID that get_part_details never returns” does not describe this repository. The current defect is simpler: direct part-image URLs are present upstream but are discarded from every model-visible return.