wheel-size-mcp 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. wheel_size_mcp-0.4.0/.claude/skills/sync-tools-inventory/SKILL.md +141 -0
  2. wheel_size_mcp-0.4.0/.github/workflows/ci.yml +28 -0
  3. wheel_size_mcp-0.4.0/.gitignore +37 -0
  4. wheel_size_mcp-0.4.0/LICENSE +21 -0
  5. wheel_size_mcp-0.4.0/PKG-INFO +286 -0
  6. wheel_size_mcp-0.4.0/README.md +261 -0
  7. wheel_size_mcp-0.4.0/docs/tools-inventory.md +752 -0
  8. wheel_size_mcp-0.4.0/evals/__init__.py +0 -0
  9. wheel_size_mcp-0.4.0/evals/grading.py +128 -0
  10. wheel_size_mcp-0.4.0/evals/run_evals.py +214 -0
  11. wheel_size_mcp-0.4.0/manifest.json +31 -0
  12. wheel_size_mcp-0.4.0/pyproject.toml +66 -0
  13. wheel_size_mcp-0.4.0/server.json +30 -0
  14. wheel_size_mcp-0.4.0/src/ws_mcp/__init__.py +3 -0
  15. wheel_size_mcp-0.4.0/src/ws_mcp/client.py +151 -0
  16. wheel_size_mcp-0.4.0/src/ws_mcp/prompts.py +160 -0
  17. wheel_size_mcp-0.4.0/src/ws_mcp/response.py +242 -0
  18. wheel_size_mcp-0.4.0/src/ws_mcp/server.py +146 -0
  19. wheel_size_mcp-0.4.0/src/ws_mcp/slugify.py +23 -0
  20. wheel_size_mcp-0.4.0/src/ws_mcp/tools/__init__.py +1 -0
  21. wheel_size_mcp-0.4.0/src/ws_mcp/tools/_annotations.py +35 -0
  22. wheel_size_mcp-0.4.0/src/ws_mcp/tools/catalog.py +271 -0
  23. wheel_size_mcp-0.4.0/src/ws_mcp/tools/classified.py +348 -0
  24. wheel_size_mcp-0.4.0/src/ws_mcp/tools/search.py +554 -0
  25. wheel_size_mcp-0.4.0/src/ws_mcp/tools/utility.py +135 -0
  26. wheel_size_mcp-0.4.0/tests/__init__.py +0 -0
  27. wheel_size_mcp-0.4.0/tests/conftest.py +48 -0
  28. wheel_size_mcp-0.4.0/tests/test_classified_drilldown.py +133 -0
  29. wheel_size_mcp-0.4.0/tests/test_client_mock.py +158 -0
  30. wheel_size_mcp-0.4.0/tests/test_eval_grading.py +134 -0
  31. wheel_size_mcp-0.4.0/tests/test_field_coverage.py +79 -0
  32. wheel_size_mcp-0.4.0/tests/test_fitment_check.py +151 -0
  33. wheel_size_mcp-0.4.0/tests/test_hf_tire.py +98 -0
  34. wheel_size_mcp-0.4.0/tests/test_integration.py +519 -0
  35. wheel_size_mcp-0.4.0/tests/test_inventory_sync.py +159 -0
  36. wheel_size_mcp-0.4.0/tests/test_p5_params.py +189 -0
  37. wheel_size_mcp-0.4.0/tests/test_questions.json +688 -0
  38. wheel_size_mcp-0.4.0/tests/test_response.py +239 -0
  39. wheel_size_mcp-0.4.0/tests/test_response_mappers.py +59 -0
  40. wheel_size_mcp-0.4.0/tests/test_search_validation.py +45 -0
  41. wheel_size_mcp-0.4.0/tests/test_spec_validation.py +79 -0
  42. wheel_size_mcp-0.4.0/tests/test_token_budget.py +115 -0
  43. wheel_size_mcp-0.4.0/uv.lock +1554 -0
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: sync-tools-inventory
3
+ description: "Sync tool descriptions from docs/tools-inventory.md to Python source files in src/ws_mcp/tools/. Use after editing the inventory doc to push changes to code."
4
+ disable-model-invocation: true
5
+ user-invocable: true
6
+ allowed-tools: Read, Edit, Grep, Glob, Bash(git diff *), Bash(uv run ruff check *), Bash(uv run pytest *)
7
+ effort: high
8
+ ---
9
+
10
+ # Sync Tools Inventory → Code
11
+
12
+ Synchronize tool docstrings and parameter descriptions from `docs/tools-inventory.md` (the single source of truth) into the Python tool source files.
13
+
14
+ ## Step 0: Drift Safety Check — ALWAYS RUN FIRST
15
+
16
+ ```bash
17
+ uv run pytest tests/test_inventory_sync.py -q
18
+ ```
19
+
20
+ This test compares both sides (docstrings + parameter descriptions, whitespace-normalized).
21
+
22
+ - **All green** → doc and code are already in sync. Nothing to do; report that and stop.
23
+ - **Failures** → read each reported diff and classify it:
24
+ - The doc side contains the edits the user just made → this is the change set to push to code. Proceed.
25
+ - The CODE side contains content absent from the doc, in sections the user did NOT just edit → **code is newer than the doc (reverse drift). STOP.** Do not overwrite it. Report the diff and ask the user to back-port code → doc first (or confirm the overwrite explicitly).
26
+
27
+ Never skip this step: a doc→code sync over a stale doc silently destroys newer docstrings, and `ruff` will not catch it.
28
+
29
+ ## Step 1: Read Both Sides
30
+
31
+ Read `docs/tools-inventory.md` and all four tool source files:
32
+
33
+ | Inventory Section | Source File |
34
+ |---|---|
35
+ | `## Catalog` | `src/ws_mcp/tools/catalog.py` |
36
+ | `## Search` | `src/ws_mcp/tools/search.py` |
37
+ | `## Classified` | `src/ws_mcp/tools/classified.py` |
38
+ | `## Utility` | `src/ws_mcp/tools/utility.py` |
39
+
40
+ ## Step 2: Parse Inventory Format
41
+
42
+ Each tool in inventory.md follows this structure:
43
+
44
+ ```markdown
45
+ ### `tool_name`
46
+
47
+ **API**: `GET /v2/endpoint/`
48
+
49
+ **Docstring**:
50
+ > Line 1 of docstring
51
+ > Line 2 of docstring
52
+ > ...
53
+
54
+ **Parameters**:
55
+ | Parameter | Type | Description |
56
+ |----------|-----|----------|
57
+ | `param_name` | `type` | Description text |
58
+ ```
59
+
60
+ Format details:
61
+
62
+ - **Heading suffixes**: a heading may carry a note after the closing backtick — e.g. `` ### `check_rim_fitment_for_vehicle` (composite) ``. The tool name is only what is inside the backticks.
63
+ - **No parameters**: `list_regions` has `**Parameters**: none` instead of a table.
64
+ - **Escaped pipes**: cells may contain `\|` (e.g. type `` `"concise" \| "full"` ``) — treat `\|` as a literal `|` inside the cell, not a column separator.
65
+ - **Shared geometric block**: the `## Classified` section opens with a shared parameter table (marked "shared geometric parameters"). Tool tables reference it with the row `| *+ shared geometric parameters* | | see block above |` instead of repeating those rows. When comparing a tool's parameters, expand the shared block into its table.
66
+ - **API lines** may have suffixes too (`+ MCP-side year filtering`) — informational only, not synced.
67
+
68
+ Extract for each tool:
69
+ - **Tool name**: from the backticks in the `### ` heading
70
+ - **Docstring lines**: all `> ` prefixed lines under **Docstring** (strip the `> ` prefix; a bare `>` is a blank line; indentation after `> ` is preserved)
71
+ - **Parameter names and descriptions**: from the "Parameter" and "Description" columns (strip backticks from names)
72
+
73
+ ## Step 3: Compare and Update Docstrings
74
+
75
+ For each tool, find the function in the source file by matching `async def tool_name(`.
76
+
77
+ **Docstring update rule**: Replace the entire `"""..."""` block under the function signature with the content from inventory. Preserve the indentation level (8 spaces for tool functions).
78
+
79
+ Format:
80
+ ```python
81
+ async def tool_name(...) -> dict:
82
+ """First line of docstring.
83
+
84
+ Remaining lines of docstring.
85
+ Preserving blank lines from the inventory.
86
+ """
87
+ ```
88
+
89
+ - The first `> ` line becomes the first line of the docstring (on same line as `"""`)
90
+ - Subsequent `> ` lines follow with 8-space indentation
91
+ - Empty `> ` lines (just `>`) become blank lines in the docstring
92
+ - Close with `"""` on its own line at 8-space indent
93
+
94
+ ## Step 4: Compare and Update Parameter Descriptions
95
+
96
+ For each parameter in the inventory table, find the matching `Field(description="...")` and update the description string. The Description column maps to the code **verbatim** — do not add or drop range/default annotations.
97
+
98
+ **Where the Field lives depends on the module:**
99
+
100
+ - `catalog.py`, `search.py`, `utility.py`: inline in the function signature — `param: Annotated[type, Field(description="...")]`.
101
+ - `classified.py`: mostly in **module-level Annotated aliases** at the top of the file (`_BoltPattern`, `_RimDiameter`, `_Cb`, `_Sort`, …) shared by several tools. The doc's shared geometric block maps to these aliases. Editing an alias changes every tool that uses it — that is intended. If the doc asks for different descriptions of the same shared parameter in different tools, report a WARNING instead of editing (per-tool divergence requires restructuring the aliases first).
102
+
103
+ **Update rule**: Replace only the `description="old text"` value inside `Field(...)`. Do NOT change the type annotation, default value, or other Field arguments (ge, le, etc.). Unescape `\|` → `|` when writing into Python strings.
104
+
105
+ ## Step 5: Detect Structural Changes
106
+
107
+ Compare the set of parameter names in inventory (with the shared block expanded) vs code for each tool.
108
+
109
+ - **New parameter in inventory but not in code**: Report as WARNING — cannot auto-add because type annotation, default value, and Field constraints are needed. List the parameter name and suggest the user add it manually.
110
+ - **Parameter in code but removed from inventory**: Report as WARNING — do not auto-remove. List the parameter and ask the user to confirm deletion.
111
+
112
+ ## Step 6: Validate
113
+
114
+ After all edits:
115
+
116
+ 1. `uv run ruff check src/ws_mcp/tools/` — no syntax/lint errors
117
+ 2. `uv run pytest tests/test_inventory_sync.py -q` — MUST be green now: doc and code are byte-consistent again
118
+ 3. Show a summary of all changes made:
119
+ - Which tools had docstring updates
120
+ - Which parameters had description updates
121
+ - Any structural warnings (added/removed params)
122
+
123
+ ## Step 7: Cross-File Consistency Check
124
+
125
+ Tool behavior and parameter contracts are also duplicated in two places this skill does NOT edit:
126
+
127
+ - `src/ws_mcp/server.py` — the FastMCP `instructions` block (workflows, critical rules, parameter requirements)
128
+ - `README.md` — the "Available Tools" table
129
+
130
+ After a sync, grep both for statements about the tools whose descriptions changed. If a synced change contradicts them (e.g. a parameter requirement changed), report the exact lines as WARNINGS for the user to update manually. Do not auto-edit these files.
131
+
132
+ ## Important Rules
133
+
134
+ - NEVER proceed past Step 0 when code-side content is newer than the doc
135
+ - NEVER change function signatures, return types, or logic — only docstrings and Field descriptions
136
+ - NEVER change Field constraints (ge, le, etc.) — only the description string
137
+ - NEVER change type annotations — only descriptions
138
+ - Preserve existing indentation style in each file
139
+ - If a tool exists in code but NOT in inventory — skip it, report as info
140
+ - If a tool exists in inventory but NOT in code — report as WARNING
141
+ - Use the Edit tool for all changes (not Write) to preserve file content
@@ -0,0 +1,28 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ["3.12", "3.13"]
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: astral-sh/setup-uv@v5
18
+ with:
19
+ python-version: ${{ matrix.python-version }}
20
+
21
+ - name: Install dependencies
22
+ run: uv sync --dev
23
+
24
+ - name: Lint
25
+ run: uv run ruff check .
26
+
27
+ - name: Unit tests
28
+ run: uv run pytest -m "not integration"
@@ -0,0 +1,37 @@
1
+ # Python bytecode
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # Build / packaging
7
+ *.egg-info/
8
+ *.egg
9
+ .eggs/
10
+ dist/
11
+ build/
12
+
13
+ # Virtual environments
14
+ .venv/
15
+ venv/
16
+
17
+ # Environment variables
18
+ .env
19
+
20
+ # Tool caches
21
+ .pytest_cache/
22
+ .ruff_cache/
23
+ .playwright-mcp/
24
+
25
+ # AI assistant configs
26
+ CLAUDE.md
27
+ .mcp.json
28
+ claudedocs/
29
+ .claude/settings.local.json
30
+ .codex-review/
31
+
32
+ # OS files
33
+ .DS_Store
34
+
35
+ # Symlinks to external repos (local dev convenience — never publish)
36
+ # NB: no trailing slash — must match the SYMLINK, not just a directory
37
+ /development
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WHEEL SIZE KZ LLP
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,286 @@
1
+ Metadata-Version: 2.4
2
+ Name: wheel-size-mcp
3
+ Version: 0.4.0
4
+ Summary: MCP server for Wheel Fitment API — enables Claude Code and LLM agents to query vehicle wheel/tire compatibility data
5
+ Project-URL: Homepage, https://www.wheel-size.com
6
+ Project-URL: Repository, https://github.com/driveate/wheel-size-mcp
7
+ Project-URL: Issues, https://github.com/driveate/wheel-size-mcp/issues
8
+ Project-URL: Documentation, https://developer.wheel-size.com
9
+ Project-URL: API Reference, https://api.wheel-size.com/v2/openapi/
10
+ Author-email: WHEEL SIZE KZ LLP <info@wheel-size.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: claude-code,fastmcp,mcp,wheel-fitment,wheel-size
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Libraries
21
+ Requires-Python: >=3.12
22
+ Requires-Dist: fastmcp>=3.4.1
23
+ Requires-Dist: httpx>=0.27
24
+ Description-Content-Type: text/markdown
25
+
26
+ # wheel-size-mcp
27
+
28
+ <!-- mcp-name: io.github.driveate/wheel-size-mcp -->
29
+
30
+ The **official** MCP server for the [Wheel Fitment API](https://api.wheel-size.com/v2/openapi/) — built and maintained by [Wheel-Size.com](https://www.wheel-size.com), the API provider. Gives LLM agents access to vehicle wheel and tire compatibility data.
31
+
32
+ Ask your AI assistant things like:
33
+ - "What are the OEM wheel specs for a 2024 Toyota Camry?"
34
+ - "Which vehicles fit 5x114.3 18x8 ET35 rims?"
35
+ - "Calculate plus-size options for 225/50R17 on 7Jx17 ET40"
36
+ - "Generate a product card for this wheel showing all compatible vehicles"
37
+
38
+ ## Quick Start
39
+
40
+ ### 1. Get an API key
41
+
42
+ Sign up at [developer.wheel-size.com](https://developer.wheel-size.com) and copy your API key.
43
+
44
+ ### 2. Set the API key in your shell
45
+
46
+ Add to your `~/.zshrc` (or `~/.bashrc`):
47
+
48
+ ```bash
49
+ export WHEELSIZE_API_KEY="your-api-key-here"
50
+ ```
51
+
52
+ Then reload your shell: `source ~/.zshrc`
53
+
54
+ ### 3. Add to your AI client
55
+
56
+ Choose your client below — each config block is copy-paste ready.
57
+
58
+ #### Claude Code
59
+
60
+ ```bash
61
+ claude mcp add wheel-size-api -- uvx wheel-size-mcp
62
+ ```
63
+
64
+ Or add to `.mcp.json` in your project root:
65
+
66
+ ```json
67
+ {
68
+ "mcpServers": {
69
+ "wheel-size-api": {
70
+ "command": "uvx",
71
+ "args": ["wheel-size-mcp"],
72
+ "env": {
73
+ "WHEELSIZE_API_KEY": "${WHEELSIZE_API_KEY}"
74
+ }
75
+ }
76
+ }
77
+ }
78
+ ```
79
+
80
+ #### Claude Desktop
81
+
82
+ Add to `claude_desktop_config.json` (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, `%APPDATA%\Claude\claude_desktop_config.json` on Windows):
83
+
84
+ ```json
85
+ {
86
+ "mcpServers": {
87
+ "wheel-size-api": {
88
+ "command": "uvx",
89
+ "args": ["wheel-size-mcp"],
90
+ "env": {
91
+ "WHEELSIZE_API_KEY": "your-api-key-here"
92
+ }
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ #### Cursor
99
+
100
+ Add to `.cursor/mcp.json` in your project root:
101
+
102
+ ```json
103
+ {
104
+ "mcpServers": {
105
+ "wheel-size-api": {
106
+ "command": "uvx",
107
+ "args": ["wheel-size-mcp"],
108
+ "env": {
109
+ "WHEELSIZE_API_KEY": "your-api-key-here"
110
+ }
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ #### Windsurf
117
+
118
+ Add to `~/.codeium/windsurf/mcp_config.json`:
119
+
120
+ ```json
121
+ {
122
+ "mcpServers": {
123
+ "wheel-size-api": {
124
+ "command": "uvx",
125
+ "args": ["wheel-size-mcp"],
126
+ "env": {
127
+ "WHEELSIZE_API_KEY": "your-api-key-here"
128
+ }
129
+ }
130
+ }
131
+ }
132
+ ```
133
+
134
+ #### Zed
135
+
136
+ Add to your Zed `settings.json` (Cmd+, → Open Settings):
137
+
138
+ ```json
139
+ {
140
+ "context_servers": {
141
+ "wheel-size-api": {
142
+ "command": {
143
+ "path": "uvx",
144
+ "args": ["wheel-size-mcp"],
145
+ "env": {
146
+ "WHEELSIZE_API_KEY": "your-api-key-here"
147
+ }
148
+ }
149
+ }
150
+ }
151
+ }
152
+ ```
153
+
154
+ ### 4. Restart your client
155
+
156
+ The MCP server starts automatically when the client launches.
157
+
158
+ ## Remote Server (Streamable HTTP)
159
+
160
+ Besides stdio, the server can run as a standalone HTTP service — useful for hosting one shared instance instead of installing Python on every machine:
161
+
162
+ ```bash
163
+ wheel-size-mcp --transport http --port 8000
164
+ ```
165
+
166
+ The MCP endpoint is served at `http://127.0.0.1:8000/mcp/`. Point HTTP-capable clients at it:
167
+
168
+ ```json
169
+ {
170
+ "mcpServers": {
171
+ "wheel-size-api": {
172
+ "url": "http://127.0.0.1:8000/mcp/"
173
+ }
174
+ }
175
+ }
176
+ ```
177
+
178
+ **Security**: the server binds to `127.0.0.1` by default. The `WHEELSIZE_API_KEY` lives on the server side, so anyone who can reach the port consumes your API quota — expose it beyond localhost (`--host 0.0.0.0`) only behind a reverse proxy that handles authentication.
179
+
180
+ ## Available Tools (21)
181
+
182
+ ### Catalog — vehicle lookup
183
+
184
+ | Tool | Description |
185
+ |------|-------------|
186
+ | `ws_list_makes` | List all manufacturers. **Start here.** |
187
+ | `ws_list_models` | Models for a make (e.g. Toyota → Camry, Corolla…). |
188
+ | `ws_list_years` | Available years for a make/model. |
189
+ | `ws_list_generations` | Generations for a make/model (alternative to years). |
190
+ | `ws_list_modifications` | Trims for a specific vehicle (e.g. 2.0i, 3.0 V6…). |
191
+ | `ws_list_regions` | Market regions (USDM, EUDM, JDM…). |
192
+
193
+ ### Search — fitment data
194
+
195
+ | Tool | Description |
196
+ |------|-------------|
197
+ | `ws_search_by_vehicle` | OEM wheel/tire specs for a vehicle. Requires `modification` or `region`, plus `year` or `generation` (unless `modification` is given). |
198
+ | `ws_search_by_rim` | Find vehicles compatible with a rim (exact specs or min/max ranges). |
199
+ | `ws_search_by_tire` | Find vehicles by metric tire size, with speed/load/staggered filters and refinement facets. |
200
+ | `ws_search_by_hf_tire` | Find vehicles by high-flotation (LT) inch size (e.g. 31x10.50R15). |
201
+ | `ws_check_rim_fitment_for_vehicle` | "Will these rims fit my 2020 Civic?" — one-call fitment check. |
202
+ | `ws_check_tire_fitment_for_vehicle` | Same for a metric tire size. |
203
+ | `ws_check_hf_tire_fitment_for_vehicle` | Same for a high-flotation tire size. |
204
+ | `ws_calculate_upsteps` | Plus/minus sizing calculator with width/diameter tolerances. |
205
+
206
+ ### Classified — product cards for e-commerce
207
+
208
+ | Tool | Description |
209
+ |------|-------------|
210
+ | `ws_find_tires_for_rim` | Compatible tire sizes for a rim spec. |
211
+ | `ws_find_vehicles_for_rim` | Vehicles that fit a given rim (geometric 2D filtering). |
212
+ | `ws_find_vehicle_modifications_for_rim` | Drill down into trims for a specific generation. |
213
+ | `ws_find_vehicles_for_tire` | Vehicles that use a specific tire size. |
214
+ | `ws_find_vehicles_for_package` | Vehicles compatible with a rim + tire combo. |
215
+ | `ws_find_vehicle_modifications_for_package` | Drill down into trims for a rim + tire package. |
216
+
217
+ ### Utility
218
+
219
+ | Tool | Description |
220
+ |------|-------------|
221
+ | `ws_get_spec_metadata` | Computed geometry, population stats, and intelligence hints for any spec. |
222
+
223
+ ## MCP Prompts
224
+
225
+ Pre-built workflow prompts that guide LLM agents through multi-step operations:
226
+
227
+ | Prompt | Description |
228
+ |--------|-------------|
229
+ | `vehicle_fitment_lookup` | Complete catalog→search chain for a vehicle description |
230
+ | `rim_compatibility_check` | Metadata→classified flow for rim compatibility |
231
+ | `product_card_generation` | E-commerce product card workflow for wheels/packages |
232
+
233
+ ## Environment Variables
234
+
235
+ | Variable | Required | Default | Description |
236
+ |----------|----------|---------|-------------|
237
+ | `WHEELSIZE_API_KEY` | **Yes** | — | API key from [developer.wheel-size.com](https://developer.wheel-size.com) |
238
+ | `API_BASE_URL` | No | `https://api.wheel-size.com` | API base URL |
239
+ | `API_HOST_HEADER` | No | — | Host header override (only needed for local Docker routing) |
240
+ | `MCP_TRANSPORT` | No | `stdio` | `stdio` or `http` (same as `--transport`) |
241
+ | `MCP_HOST` | No | `127.0.0.1` | Bind address for http transport (same as `--host`) |
242
+ | `MCP_PORT` | No | `8000` | Port for http transport (same as `--port`) |
243
+
244
+ ## API Terms of Service
245
+
246
+ Search tools (`ws_search_by_vehicle`, `ws_search_by_rim`, `ws_search_by_tire`, `ws_search_by_hf_tire`, and the `ws_check_*_fitment_for_vehicle` checks) **must be initiated by real users** per [API Terms of Usage](https://developer.wheel-size.com/api-tos). Do not call in autonomous agent loops. Catalog, classified, utility tools and `ws_calculate_upsteps` have no such restriction.
247
+
248
+ ## Evals
249
+
250
+ [`tests/test_questions.json`](tests/test_questions.json) contains 89 natural-language questions across 12 categories (catalog navigation, fitment lookups, reverse searches, fitment checks, upstep calculation, e-commerce product cards, spec metadata, multi-step workflows, edge cases, tool selection). Each entry includes `expected_tools`, optional `expected_params` / `expected_params_search`, and a free-text `tests` note.
251
+
252
+ `evals/run_evals.py` feeds these questions to a real Claude model with the MCP tools attached, records which tools it calls with which parameters, and grades them against the expectations — catching regressions in tool descriptions and server instructions:
253
+
254
+ ```bash
255
+ # needs ANTHROPIC_API_KEY and a reachable Wheel Fitment API; costs money
256
+ uv sync --group evals
257
+ uv run --group evals python evals/run_evals.py # all questions
258
+ uv run --group evals python evals/run_evals.py -n 10 # smoke run
259
+ uv run --group evals python evals/run_evals.py --category catalog_flow
260
+ uv run --group evals python evals/run_evals.py --json report.json --min-pass 0.8
261
+ ```
262
+
263
+ Grading is deterministic (no LLM judge): every expected tool must be called (multiset — repeats counted, extra navigation calls allowed), and some single call must carry the expected parameters. Questions without machine-checkable expectations are reported as SKIP and excluded from the pass rate. The default model is pinned (`claude-sonnet-5`) so pass-rate history stays comparable; override with `--model`.
264
+
265
+ The eval runner is **not** part of pytest or CI — it bills the Anthropic API. The grading logic itself is unit-tested in CI (`tests/test_eval_grading.py`). ToS note: every question simulates a user-initiated request, so the search-tool restriction is respected.
266
+
267
+ ## Development
268
+
269
+ ```bash
270
+ # Install dev dependencies
271
+ uv sync --dev
272
+
273
+ # Unit tests (no API needed — this is what CI runs)
274
+ uv run pytest -m "not integration"
275
+
276
+ # Full test suite (requires a private API instance, see note below)
277
+ uv run pytest
278
+
279
+ # Lint
280
+ uv run ruff check .
281
+
282
+ # Run server (stdio)
283
+ wheel-size-mcp
284
+ ```
285
+
286
+ **Note on tests**: integration tests run against a private test instance of the API and auto-skip when it is unreachable. External contributors should rely on the unit suite (`pytest -m "not integration"`), which mocks all HTTP and is what CI runs on every push and pull request.