mcp-openapi-proxy 0.3.2__tar.gz → 0.3.4__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 (19) hide show
  1. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/PKG-INFO +61 -6
  2. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/README.md +59 -4
  3. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy/server_lowlevel.py +13 -8
  4. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy.egg-info/PKG-INFO +61 -6
  5. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy.egg-info/requires.txt +1 -1
  6. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/pyproject.toml +5 -2
  7. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/LICENSE +0 -0
  8. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy/__init__.py +0 -0
  9. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy/handlers.py +0 -0
  10. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy/logging_setup.py +0 -0
  11. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy/openapi.py +0 -0
  12. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy/server_fastmcp.py +0 -0
  13. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy/types.py +0 -0
  14. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy/utils.py +0 -0
  15. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy.egg-info/SOURCES.txt +0 -0
  16. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy.egg-info/dependency_links.txt +0 -0
  17. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy.egg-info/entry_points.txt +0 -0
  18. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/mcp_openapi_proxy.egg-info/top_level.txt +0 -0
  19. {mcp_openapi_proxy-0.3.2 → mcp_openapi_proxy-0.3.4}/setup.cfg +0 -0
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-openapi-proxy
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: MCP server for exposing OpenAPI specifications as MCP tools.
5
5
  Author-email: Matthew Hand <11550632+matthewhand@users.noreply.github.com>
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
- Requires-Dist: mcp[cli]>=1.2.0
9
+ Requires-Dist: mcp[cli]<2,>=1.2.0
10
10
  Requires-Dist: python-dotenv>=1.0.1
11
11
  Requires-Dist: requests>=2.25.0
12
12
  Requires-Dist: fastapi>=0.100.0
@@ -170,13 +170,14 @@ The example configurations below were exercised against the live APIs, and the p
170
170
  | flyio | 34–35 | apps + machine health | `API_KEY` |
171
171
  | slack | 7 (exact dot-path whitelist until #27 fix) | `auth.test` + `postMessage` | `API_KEY` |
172
172
  | netbox | 9 (whitelist `/ipam/ip-addresses`) | IPAM write + read | `API_KEY` + `API_AUTH_TYPE=Token` |
173
+ | homeassistant | 21 | `get_config` (200) + `call_service` (light.turn_on, 200) | `SERVER_URL_OVERRIDE` + `API_KEY` (`${HA_TOKEN}`, sent as Bearer) |
173
174
 
174
175
  ### Client matrix
175
176
 
176
177
  | Agent CLI | Model used (live test) | MCP attach mechanism | Tool calls | Prompts/Resources surfaced to model? |
177
178
  |---|---|---|---|---|
178
179
  | **opencode** | (CLI default) | `~/.config/opencode/opencode.json` `mcp` | ✅ native | **prompts: ✅ (slash) · resources: ✅** — most complete ‖ |
179
- | Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native | prompts: ❌ (no prompt meta-tools) · resources: ✅ (`read_mcp_resource`) ‖ |
180
+ | Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native | prompts: ❌ low-level · ✅ fastmcp (via `call_function`→`get_prompt`) · resources: ✅ (`read_mcp_resource`) ‖ |
180
181
  | Kilocode | `kilo-auto/free` | global `settings/mcp_settings.json` | ✅ native | prompts: ❌ (no prompt mechanism) · resources: ✅ (`access_mcp_resource`) ‖ |
181
182
  | Qwen | `agent` group via local LiteLLM gateway | project `.qwen/settings.json` | ✅ native (live invoke auth-blocked) | prompts: ✅ (slash `/summarize_spec`) · resources: ❌ (no client support) ‖ |
182
183
  | Gemini | Google OAuth free tier (CLI default model) | project `.gemini/settings.json` `mcpServers` | ✅ native | prompts: interactive slash only · resources: interactive `@` only (neither reaches the model headless) ‖ |
@@ -188,11 +189,17 @@ The example configurations below were exercised against the live APIs, and the p
188
189
  > resources. **mcp-openapi-proxy serves all three, advertised by default since 0.3.0.**
189
190
  > Whether they reach the model is up to the *client*, and that varies:
190
191
  >
191
- > - **‖ re-verified 2026-06-14 against the published 0.3.0 release** with **no** flags set
192
- > (validating the default-on advertising), driving each **real client binary**.
192
+ > - **‖ re-verified 2026-06-15 against the published 0.3.3 release** with **no** flags set
193
+ > (validating the default-on advertising), driving each **real client binary**, in
194
+ > **both** server modes (low-level and FastMCP simple). Per-client results were the
195
+ > same across modes except where noted.
193
196
  > - **Tools** work on every client tested. **Prompts→model**: opencode & Qwen (slash
194
197
  > commands); Gemini interactive-only. **Resources→model**: opencode, Codex, Kilocode.
195
- > **opencode is the only client that surfaces all three.** Vibe is tools-only.
198
+ > **opencode is the only client that surfaces all three** in any mode.
199
+ > - **FastMCP simple mode exception:** a client with no native prompt surface can still
200
+ > reach prompts through the static `call_function`→`get_prompt` indirection. Observed
201
+ > with **Codex** (prompts ❌ in low-level, ✅ in FastMCP simple mode). Vibe stays
202
+ > tools-only regardless of mode.
196
203
  > - **‡ unknown** — Letta can't be exercised without standing up a Letta server + model;
197
204
  > left untested rather than guessed.
198
205
  > - Every cell on the proxy side was confirmed via a raw stdio handshake (`initialize`
@@ -935,6 +942,54 @@ Tips: set `IGNORE_SSL_TOOLS=true` only if your host serves a self-signed/mismatc
935
942
 
936
943
  </details>
937
944
 
945
+ <details>
946
+ <summary><b>Home Assistant Example</b> — control your smart home; <code>call_service</code> needs the >= 0.3.3 path-param body fix</summary>
947
+
948
+ Exposes a generic slice of the [Home Assistant REST API](https://developers.home-assistant.io/docs/api/rest/)
949
+ (21 operations) — read state (`get_config`, `list_states`, `get_state`, `list_services`,
950
+ `get_history`/`get_history_now`, `get_logbook`, `list_calendars`, `get_calendar_events`,
951
+ `get_camera_image`, `get_error_log`, `list_components`, `list_events`, `get_api_status`) and
952
+ act (`call_service`, `set_state`, `delete_state`, `fire_event`, `render_template`,
953
+ `check_config`, `handle_intent`). HA ships no official OpenAPI spec, so this is hand-rolled
954
+ from the official docs.
955
+
956
+ #### 1. Verify the OpenAPI specification
957
+
958
+ ```bash
959
+ curl https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json
960
+ ```
961
+
962
+ #### 2. Configure mcp-openapi-proxy for Home Assistant
963
+
964
+ ```json
965
+ {
966
+ "mcpServers": {
967
+ "homeassistant": {
968
+ "command": "uvx",
969
+ "args": ["mcp-openapi-proxy"],
970
+ "env": {
971
+ "OPENAPI_SPEC_URL": "https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json",
972
+ "SERVER_URL_OVERRIDE": "http://homeassistant.local:8123",
973
+ "API_KEY": "${HA_TOKEN}"
974
+ }
975
+ }
976
+ }
977
+ }
978
+ ```
979
+
980
+ Key configuration points:
981
+ - `SERVER_URL_OVERRIDE` — your instance base URL, e.g. `http://homeassistant.local:8123` or `http://<ha-host>:8123`.
982
+ - `HA_TOKEN` — a Home Assistant **long-lived access token** (Profile → Long-Lived Access Tokens), passed via `API_KEY`; the proxy sends it as `Authorization: Bearer <token>` (its default scheme), so no `EXTRA_HEADERS` needed. Never commit the token; keep it in your environment.
983
+ - `call_service` requires **mcp-openapi-proxy >= 0.3.3**: earlier versions leaked the `{domain}`/`{service}` path params into the JSON body, which Home Assistant rejects with HTTP 400.
984
+
985
+ #### 3. Testing
986
+
987
+ `get_config` should return your HA configuration (200). `call_service` for `light/turn_on`
988
+ with body `{"entity_id": "light.kitchen"}` should return 200 — the path params land in the URL
989
+ (`/api/services/light/turn_on`), not the body.
990
+
991
+ </details>
992
+
938
993
  ## Troubleshooting
939
994
 
940
995
  ### JSON-RPC Testing
@@ -148,13 +148,14 @@ The example configurations below were exercised against the live APIs, and the p
148
148
  | flyio | 34–35 | apps + machine health | `API_KEY` |
149
149
  | slack | 7 (exact dot-path whitelist until #27 fix) | `auth.test` + `postMessage` | `API_KEY` |
150
150
  | netbox | 9 (whitelist `/ipam/ip-addresses`) | IPAM write + read | `API_KEY` + `API_AUTH_TYPE=Token` |
151
+ | homeassistant | 21 | `get_config` (200) + `call_service` (light.turn_on, 200) | `SERVER_URL_OVERRIDE` + `API_KEY` (`${HA_TOKEN}`, sent as Bearer) |
151
152
 
152
153
  ### Client matrix
153
154
 
154
155
  | Agent CLI | Model used (live test) | MCP attach mechanism | Tool calls | Prompts/Resources surfaced to model? |
155
156
  |---|---|---|---|---|
156
157
  | **opencode** | (CLI default) | `~/.config/opencode/opencode.json` `mcp` | ✅ native | **prompts: ✅ (slash) · resources: ✅** — most complete ‖ |
157
- | Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native | prompts: ❌ (no prompt meta-tools) · resources: ✅ (`read_mcp_resource`) ‖ |
158
+ | Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native | prompts: ❌ low-level · ✅ fastmcp (via `call_function`→`get_prompt`) · resources: ✅ (`read_mcp_resource`) ‖ |
158
159
  | Kilocode | `kilo-auto/free` | global `settings/mcp_settings.json` | ✅ native | prompts: ❌ (no prompt mechanism) · resources: ✅ (`access_mcp_resource`) ‖ |
159
160
  | Qwen | `agent` group via local LiteLLM gateway | project `.qwen/settings.json` | ✅ native (live invoke auth-blocked) | prompts: ✅ (slash `/summarize_spec`) · resources: ❌ (no client support) ‖ |
160
161
  | Gemini | Google OAuth free tier (CLI default model) | project `.gemini/settings.json` `mcpServers` | ✅ native | prompts: interactive slash only · resources: interactive `@` only (neither reaches the model headless) ‖ |
@@ -166,11 +167,17 @@ The example configurations below were exercised against the live APIs, and the p
166
167
  > resources. **mcp-openapi-proxy serves all three, advertised by default since 0.3.0.**
167
168
  > Whether they reach the model is up to the *client*, and that varies:
168
169
  >
169
- > - **‖ re-verified 2026-06-14 against the published 0.3.0 release** with **no** flags set
170
- > (validating the default-on advertising), driving each **real client binary**.
170
+ > - **‖ re-verified 2026-06-15 against the published 0.3.3 release** with **no** flags set
171
+ > (validating the default-on advertising), driving each **real client binary**, in
172
+ > **both** server modes (low-level and FastMCP simple). Per-client results were the
173
+ > same across modes except where noted.
171
174
  > - **Tools** work on every client tested. **Prompts→model**: opencode & Qwen (slash
172
175
  > commands); Gemini interactive-only. **Resources→model**: opencode, Codex, Kilocode.
173
- > **opencode is the only client that surfaces all three.** Vibe is tools-only.
176
+ > **opencode is the only client that surfaces all three** in any mode.
177
+ > - **FastMCP simple mode exception:** a client with no native prompt surface can still
178
+ > reach prompts through the static `call_function`→`get_prompt` indirection. Observed
179
+ > with **Codex** (prompts ❌ in low-level, ✅ in FastMCP simple mode). Vibe stays
180
+ > tools-only regardless of mode.
174
181
  > - **‡ unknown** — Letta can't be exercised without standing up a Letta server + model;
175
182
  > left untested rather than guessed.
176
183
  > - Every cell on the proxy side was confirmed via a raw stdio handshake (`initialize`
@@ -913,6 +920,54 @@ Tips: set `IGNORE_SSL_TOOLS=true` only if your host serves a self-signed/mismatc
913
920
 
914
921
  </details>
915
922
 
923
+ <details>
924
+ <summary><b>Home Assistant Example</b> — control your smart home; <code>call_service</code> needs the >= 0.3.3 path-param body fix</summary>
925
+
926
+ Exposes a generic slice of the [Home Assistant REST API](https://developers.home-assistant.io/docs/api/rest/)
927
+ (21 operations) — read state (`get_config`, `list_states`, `get_state`, `list_services`,
928
+ `get_history`/`get_history_now`, `get_logbook`, `list_calendars`, `get_calendar_events`,
929
+ `get_camera_image`, `get_error_log`, `list_components`, `list_events`, `get_api_status`) and
930
+ act (`call_service`, `set_state`, `delete_state`, `fire_event`, `render_template`,
931
+ `check_config`, `handle_intent`). HA ships no official OpenAPI spec, so this is hand-rolled
932
+ from the official docs.
933
+
934
+ #### 1. Verify the OpenAPI specification
935
+
936
+ ```bash
937
+ curl https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json
938
+ ```
939
+
940
+ #### 2. Configure mcp-openapi-proxy for Home Assistant
941
+
942
+ ```json
943
+ {
944
+ "mcpServers": {
945
+ "homeassistant": {
946
+ "command": "uvx",
947
+ "args": ["mcp-openapi-proxy"],
948
+ "env": {
949
+ "OPENAPI_SPEC_URL": "https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json",
950
+ "SERVER_URL_OVERRIDE": "http://homeassistant.local:8123",
951
+ "API_KEY": "${HA_TOKEN}"
952
+ }
953
+ }
954
+ }
955
+ }
956
+ ```
957
+
958
+ Key configuration points:
959
+ - `SERVER_URL_OVERRIDE` — your instance base URL, e.g. `http://homeassistant.local:8123` or `http://<ha-host>:8123`.
960
+ - `HA_TOKEN` — a Home Assistant **long-lived access token** (Profile → Long-Lived Access Tokens), passed via `API_KEY`; the proxy sends it as `Authorization: Bearer <token>` (its default scheme), so no `EXTRA_HEADERS` needed. Never commit the token; keep it in your environment.
961
+ - `call_service` requires **mcp-openapi-proxy >= 0.3.3**: earlier versions leaked the `{domain}`/`{service}` path params into the JSON body, which Home Assistant rejects with HTTP 400.
962
+
963
+ #### 3. Testing
964
+
965
+ `get_config` should return your HA configuration (200). `call_service` for `light/turn_on`
966
+ with body `{"entity_id": "light.kitchen"}` should return 200 — the path params land in the URL
967
+ (`/api/services/light/turn_on`), not the body.
968
+
969
+ </details>
970
+
916
971
  ## Troubleshooting
917
972
 
918
973
  ### JSON-RPC Testing
@@ -249,14 +249,19 @@ async def dispatcher_handler(request: types.CallToolRequest) -> types.CallToolRe
249
249
  try:
250
250
  path = path.format(**parameters)
251
251
  logger.debug(f"Substituted path using format(): {path}")
252
- if method == "GET":
253
- placeholder_keys = [
254
- seg.strip("{}")
255
- for seg in operation_details["original_path"].split("/")
256
- if seg.startswith("{") and seg.endswith("}")
257
- ]
258
- for key in placeholder_keys:
259
- parameters.pop(key, None)
252
+ # Path placeholders are now substituted into the URL, so drop them from
253
+ # `parameters` for ALL methods. Previously this ran only for GET, so on
254
+ # POST/PUT/PATCH/DELETE the path params leaked into request_body and
255
+ # strict APIs rejected the unexpected fields -- e.g. Home Assistant
256
+ # POST /api/services/{domain}/{service} returned HTTP 400 because the
257
+ # body carried domain/service.
258
+ placeholder_keys = [
259
+ seg.strip("{}")
260
+ for seg in operation_details["original_path"].split("/")
261
+ if seg.startswith("{") and seg.endswith("}")
262
+ ]
263
+ for key in placeholder_keys:
264
+ parameters.pop(key, None)
260
265
  except KeyError as e:
261
266
  logger.error(f"Missing parameter for substitution: {e}")
262
267
  return types.CallToolResult(
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-openapi-proxy
3
- Version: 0.3.2
3
+ Version: 0.3.4
4
4
  Summary: MCP server for exposing OpenAPI specifications as MCP tools.
5
5
  Author-email: Matthew Hand <11550632+matthewhand@users.noreply.github.com>
6
6
  Requires-Python: >=3.10
7
7
  Description-Content-Type: text/markdown
8
8
  License-File: LICENSE
9
- Requires-Dist: mcp[cli]>=1.2.0
9
+ Requires-Dist: mcp[cli]<2,>=1.2.0
10
10
  Requires-Dist: python-dotenv>=1.0.1
11
11
  Requires-Dist: requests>=2.25.0
12
12
  Requires-Dist: fastapi>=0.100.0
@@ -170,13 +170,14 @@ The example configurations below were exercised against the live APIs, and the p
170
170
  | flyio | 34–35 | apps + machine health | `API_KEY` |
171
171
  | slack | 7 (exact dot-path whitelist until #27 fix) | `auth.test` + `postMessage` | `API_KEY` |
172
172
  | netbox | 9 (whitelist `/ipam/ip-addresses`) | IPAM write + read | `API_KEY` + `API_AUTH_TYPE=Token` |
173
+ | homeassistant | 21 | `get_config` (200) + `call_service` (light.turn_on, 200) | `SERVER_URL_OVERRIDE` + `API_KEY` (`${HA_TOKEN}`, sent as Bearer) |
173
174
 
174
175
  ### Client matrix
175
176
 
176
177
  | Agent CLI | Model used (live test) | MCP attach mechanism | Tool calls | Prompts/Resources surfaced to model? |
177
178
  |---|---|---|---|---|
178
179
  | **opencode** | (CLI default) | `~/.config/opencode/opencode.json` `mcp` | ✅ native | **prompts: ✅ (slash) · resources: ✅** — most complete ‖ |
179
- | Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native | prompts: ❌ (no prompt meta-tools) · resources: ✅ (`read_mcp_resource`) ‖ |
180
+ | Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native | prompts: ❌ low-level · ✅ fastmcp (via `call_function`→`get_prompt`) · resources: ✅ (`read_mcp_resource`) ‖ |
180
181
  | Kilocode | `kilo-auto/free` | global `settings/mcp_settings.json` | ✅ native | prompts: ❌ (no prompt mechanism) · resources: ✅ (`access_mcp_resource`) ‖ |
181
182
  | Qwen | `agent` group via local LiteLLM gateway | project `.qwen/settings.json` | ✅ native (live invoke auth-blocked) | prompts: ✅ (slash `/summarize_spec`) · resources: ❌ (no client support) ‖ |
182
183
  | Gemini | Google OAuth free tier (CLI default model) | project `.gemini/settings.json` `mcpServers` | ✅ native | prompts: interactive slash only · resources: interactive `@` only (neither reaches the model headless) ‖ |
@@ -188,11 +189,17 @@ The example configurations below were exercised against the live APIs, and the p
188
189
  > resources. **mcp-openapi-proxy serves all three, advertised by default since 0.3.0.**
189
190
  > Whether they reach the model is up to the *client*, and that varies:
190
191
  >
191
- > - **‖ re-verified 2026-06-14 against the published 0.3.0 release** with **no** flags set
192
- > (validating the default-on advertising), driving each **real client binary**.
192
+ > - **‖ re-verified 2026-06-15 against the published 0.3.3 release** with **no** flags set
193
+ > (validating the default-on advertising), driving each **real client binary**, in
194
+ > **both** server modes (low-level and FastMCP simple). Per-client results were the
195
+ > same across modes except where noted.
193
196
  > - **Tools** work on every client tested. **Prompts→model**: opencode & Qwen (slash
194
197
  > commands); Gemini interactive-only. **Resources→model**: opencode, Codex, Kilocode.
195
- > **opencode is the only client that surfaces all three.** Vibe is tools-only.
198
+ > **opencode is the only client that surfaces all three** in any mode.
199
+ > - **FastMCP simple mode exception:** a client with no native prompt surface can still
200
+ > reach prompts through the static `call_function`→`get_prompt` indirection. Observed
201
+ > with **Codex** (prompts ❌ in low-level, ✅ in FastMCP simple mode). Vibe stays
202
+ > tools-only regardless of mode.
196
203
  > - **‡ unknown** — Letta can't be exercised without standing up a Letta server + model;
197
204
  > left untested rather than guessed.
198
205
  > - Every cell on the proxy side was confirmed via a raw stdio handshake (`initialize`
@@ -935,6 +942,54 @@ Tips: set `IGNORE_SSL_TOOLS=true` only if your host serves a self-signed/mismatc
935
942
 
936
943
  </details>
937
944
 
945
+ <details>
946
+ <summary><b>Home Assistant Example</b> — control your smart home; <code>call_service</code> needs the >= 0.3.3 path-param body fix</summary>
947
+
948
+ Exposes a generic slice of the [Home Assistant REST API](https://developers.home-assistant.io/docs/api/rest/)
949
+ (21 operations) — read state (`get_config`, `list_states`, `get_state`, `list_services`,
950
+ `get_history`/`get_history_now`, `get_logbook`, `list_calendars`, `get_calendar_events`,
951
+ `get_camera_image`, `get_error_log`, `list_components`, `list_events`, `get_api_status`) and
952
+ act (`call_service`, `set_state`, `delete_state`, `fire_event`, `render_template`,
953
+ `check_config`, `handle_intent`). HA ships no official OpenAPI spec, so this is hand-rolled
954
+ from the official docs.
955
+
956
+ #### 1. Verify the OpenAPI specification
957
+
958
+ ```bash
959
+ curl https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json
960
+ ```
961
+
962
+ #### 2. Configure mcp-openapi-proxy for Home Assistant
963
+
964
+ ```json
965
+ {
966
+ "mcpServers": {
967
+ "homeassistant": {
968
+ "command": "uvx",
969
+ "args": ["mcp-openapi-proxy"],
970
+ "env": {
971
+ "OPENAPI_SPEC_URL": "https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json",
972
+ "SERVER_URL_OVERRIDE": "http://homeassistant.local:8123",
973
+ "API_KEY": "${HA_TOKEN}"
974
+ }
975
+ }
976
+ }
977
+ }
978
+ ```
979
+
980
+ Key configuration points:
981
+ - `SERVER_URL_OVERRIDE` — your instance base URL, e.g. `http://homeassistant.local:8123` or `http://<ha-host>:8123`.
982
+ - `HA_TOKEN` — a Home Assistant **long-lived access token** (Profile → Long-Lived Access Tokens), passed via `API_KEY`; the proxy sends it as `Authorization: Bearer <token>` (its default scheme), so no `EXTRA_HEADERS` needed. Never commit the token; keep it in your environment.
983
+ - `call_service` requires **mcp-openapi-proxy >= 0.3.3**: earlier versions leaked the `{domain}`/`{service}` path params into the JSON body, which Home Assistant rejects with HTTP 400.
984
+
985
+ #### 3. Testing
986
+
987
+ `get_config` should return your HA configuration (200). `call_service` for `light/turn_on`
988
+ with body `{"entity_id": "light.kitchen"}` should return 200 — the path params land in the URL
989
+ (`/api/services/light/turn_on`), not the body.
990
+
991
+ </details>
992
+
938
993
  ## Troubleshooting
939
994
 
940
995
  ### JSON-RPC Testing
@@ -1,4 +1,4 @@
1
- mcp[cli]>=1.2.0
1
+ mcp[cli]<2,>=1.2.0
2
2
  python-dotenv>=1.0.1
3
3
  requests>=2.25.0
4
4
  fastapi>=0.100.0
@@ -5,14 +5,17 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "mcp-openapi-proxy"
7
7
  requires-python = ">=3.10"
8
- version = "0.3.2"
8
+ version = "0.3.4"
9
9
  description = "MCP server for exposing OpenAPI specifications as MCP tools."
10
10
  readme = "README.md"
11
11
  authors = [
12
12
  { name = "Matthew Hand", email = "11550632+matthewhand@users.noreply.github.com" }
13
13
  ]
14
14
  dependencies = [
15
- "mcp[cli]>=1.2.0",
15
+ # mcp 2.x is a breaking rewrite (FastMCP renamed to MCPServer, Resource.uri type
16
+ # change, etc.) that crashes both server modes at import. Pin to 1.x; see
17
+ # tests/unit/test_mcp_version_pin.py before relaxing this bound.
18
+ "mcp[cli]>=1.2.0,<2",
16
19
  "python-dotenv>=1.0.1",
17
20
  "requests>=2.25.0",
18
21
  "fastapi>=0.100.0", # For OpenAPI parsing utils if used later, and data validation