Opticat item search MCP reviewPhase 1 discovery, validation, and path forward
Library contents
All library documents

Complete tool inventory

Exact tool names, descriptions, schemas, output paths, limits, annotations, and coverage.

mdCurrent678 lines · 27 min read
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 Schema description. 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 have isError: false. An uncaught exception, including final credential lookup failure from get_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 are null, including readOnlyHint.

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 Annotated metadata (verbatim; not emitted)
    year integer Yes N/A N/A — none emitted Vehicle year (e.g., 2007)
    make string Yes N/A N/A — none emitted Vehicle make (e.g., Ford, Toyota)
    model string Yes N/A N/A — none emitted Vehicle model (e.g., Mustang, Camry)
    part_type string No null N/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) getAutoCareVehicleResults with years=str(year) and makeFacets.enabled="true" (:122-128); (2) the same operation with years, makeIds, and baseVehicleFacets.enabled="true" (:136-143); (3) getAutoCareSearchResults with resolved baseVehicleId, fixed baseVehicleRegionId=1, perPage=20, includeParts=true, and acesAttributeFacets.enabled=true, plus optional searchQuery=part_type (:150-164). The facet helper injects string-valued page and perPage (: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 first SHO description (:188-197). The resolved baseVehicleId, 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: getAutoCareVehicleResults with yearFacets.enabled="true" and string perPage="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 only year and count.

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 Annotated metadata (verbatim; not emitted)
    year integer 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 getAutoCareVehicleResults calls with years=str(year) and makeFacets={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 only makeName, makeId, and count (: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 Annotated metadata (verbatim; not emitted)
    year integer Yes N/A N/A — none emitted Vehicle year
    make_id integer 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: getAutoCareVehicleResults with years=str(year), makeIds=str(make_id), and baseVehicleFacets={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 only modelName and baseVehicleId.

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 Annotated metadata (verbatim; not emitted)
    brand_code string Yes N/A N/A — none emitted Brand code (e.g., BBCW, DJVT)
    part_number string 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: getAutoCarePartDetails with brandCode=brand_code and partNumber=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 and replaces/replacedBy (:342-351). It discards all other PIES fields, including digitalAssets, 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 Annotated metadata (verbatim; not emitted)
    brand_code string Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)
    part_number string Yes N/A N/A — none emitted Part number
    per_page integer No 10 N/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: getAutoCarePartApplications with brandCode, partNumber, fixed includeResults=true, and caller-controlled perPage (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. Only baseVehicleName, partTypeName, and optional qty are 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 Annotated metadata (verbatim; not emitted)
    search_query string No null N/A — none emitted Part number or keyword search
    brand_codes string No null N/A — none emitted Optional: Brand code filter
    base_vehicle_id integer No null N/A — none emitted Optional: Base vehicle ID
    part_type_ids integer No null N/A — none emitted Optional: Part type ID
    per_page integer No 15 N/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 include null; omission is valid, while an explicit JSON null may 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: getAutoCareSearchResults with fixed includeParts=true and perPage=min(per_page,20); optional searchQuery, brandCodes=[brand_codes], baseVehicleId, and partTypeIds=[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_page is 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 first SHO description (: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 Annotated metadata (verbatim; not emitted)
    search_query string No null N/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: getAutoCareSearchResults with brandFacets={enabled:true, perPage:100} and optional searchQuery (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):

    1. User asks: "I have NAPA 7042, what's the Bendix equivalent?"
    2. DON'T ask user for part type - they might not know!
    3. Call discover_part_types("7042") FIRST
    4. Present findings to user: "I found that 7042 matches Engine Oil Filter (72 parts). Is this correct?"
    5. Wait for user confirmation before proceeding
    6. If confirmed, call search_parts("7042", part_type_ids=5340)
    7. Then get_part_cross_references on results
    8. 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 Annotated metadata (verbatim; not emitted)
    search_query string 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: getAutoCareSearchResults with searchQuery=search_query and partTypeFacets={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 Annotated metadata (verbatim; not emitted)
    vin string 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: getAutoCareVehiclesByVIN with only vin=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 Annotated metadata (verbatim; not emitted)
    base_vehicle_id integer Yes N/A N/A — none emitted Base vehicle ID from vehicle search
    base_vehicle_region_id integer No 1 N/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: getAutoCareVehicleAssets with baseVehicleId, caller/default baseVehicleRegionId, fixed includeAssets=true, and perPage=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:

    1. User asks: "I have NAPA Gold 7042, what's the Bendix equivalent?"
    2. Ask user for part type: "What type of part is NAPA Gold 7042?"
    3. Search with part type: search_parts("7042", part_type_ids=5340)
    4. Get cross-references for found parts: get_part_cross_references(brand_code, part_number)
    5. Look for NAPA in the cross-references list
    6. 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 Annotated metadata (verbatim; not emitted)
    brand_code string Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)
    part_number string 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: getAutoCarePartDetails with brandCode and partNumber (src/opticat_mcp/server.py:677-684), then reads piesItem.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 Annotated metadata (verbatim; not emitted)
    brand_code string Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)
    part_number string 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: getAutoCarePartDetails with brandCode and partNumber (src/opticat_mcp/server.py:730-737), then reads piesItem.replaces and piesItem.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 read extendedInformation or its lifecycle LIF records.

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 Annotated metadata (verbatim; not emitted)
    brand_code string Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)
    part_number string Yes N/A N/A — none emitted Part number
    year integer Yes N/A N/A — none emitted Vehicle year
    make string Yes N/A N/A — none emitted Vehicle make
    model string 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 with isError:false.

  • Upstream endpoint(s) + fixed params: Three calls: (1) getAutoCareVehicleResults with years=str(year) and makeFacets={enabled:true, perPage:100} (src/opticat_mcp/server.py:790-799); (2) the same operation with years, resolved makeIds, and baseVehicleFacets={enabled:true, perPage:100} (:804-813); (3) getAutoCarePartApplications with brandCode, partNumber, baseVehicleIds=[resolved ID], fixed includeResults=true, and perPage=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 on total != 0 for 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 Annotated metadata (verbatim; not emitted)
    brand_code string Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)
    part_number string 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: getAutoCarePartDetails with brandCode and partNumber (src/opticat_mcp/server.py:866-873), then reads piesItem.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 Annotated metadata (verbatim; not emitted)
    brand_code string Yes N/A N/A — none emitted Brand code (use get_brand_codes to find)
    part_number string 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: getAutoCarePartDetails with brandCode and partNumber (src/opticat_mcp/server.py:914-921), then reads piesItem.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 the Annotated strings in the source functions.
  • The Lambda schema for get_vehicle_assets omits the optional base_vehicle_region_id parameter (:168-177), even though the callable supports it.
  • Lambda tools/call serializes every tool return only as {"content":[{"type":"text","text":"<string>"}]} (:299-305); it does not include structuredContent, result, or isError. The full JSON-RPC response is then JSON-encoded into the Lambda proxy response body (:320-326).
  • deployment/lambda/tool_schema.json:4-155 is a separate static AgentCore schema containing only seven tools. It is not the Lambda handler’s tools/list registry 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_yearsget_vehicle_makesget_vehicle_modelssearch_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

  1. The model can obtain a canonical brand_code and part_number from search_parts or search_parts_by_vehicle (src/opticat_mcp/server.py:188-197,449-458).
  2. It can call get_part_details(brand_code, part_number), which invokes getAutoCarePartDetails and obtains the complete piesItem (:291-321).
  3. Per the bundled OptiCat Web Services Manual, that piesItem may contain digitalAssets entries with direct size-specific image URLs; no second asset-ID request is shown or required (data/OptiCat OnLine Web Services Manual.txt:783-800).
  4. 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.
  5. No get_part_assets tool exists to recover the omitted fields. get_vehicle_assets is not a fallback: it calls the different getAutoCareVehicleAssets operation with a baseVehicleId (: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.

View Demo