mcp-openapi-proxy 0.3.1__tar.gz → 0.3.3__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.1 → mcp_openapi_proxy-0.3.3}/PKG-INFO +45 -1
  2. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/README.md +44 -0
  3. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy/server_fastmcp.py +51 -1
  4. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy/server_lowlevel.py +13 -8
  5. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy.egg-info/PKG-INFO +45 -1
  6. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/pyproject.toml +1 -1
  7. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/LICENSE +0 -0
  8. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy/__init__.py +0 -0
  9. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy/handlers.py +0 -0
  10. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy/logging_setup.py +0 -0
  11. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy/openapi.py +0 -0
  12. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy/types.py +0 -0
  13. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy/utils.py +0 -0
  14. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy.egg-info/SOURCES.txt +0 -0
  15. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy.egg-info/dependency_links.txt +0 -0
  16. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy.egg-info/entry_points.txt +0 -0
  17. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy.egg-info/requires.txt +0 -0
  18. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/mcp_openapi_proxy.egg-info/top_level.txt +0 -0
  19. {mcp_openapi_proxy-0.3.1 → mcp_openapi_proxy-0.3.3}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-openapi-proxy
3
- Version: 0.3.1
3
+ Version: 0.3.3
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
@@ -170,6 +170,7 @@ 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 | 6 | `get_config` (200) + `call_service` (light.turn_on, 200) | `SERVER_URL_OVERRIDE` + `EXTRA_HEADERS` (`Authorization: Bearer ${HA_TOKEN}`) |
173
174
 
174
175
  ### Client matrix
175
176
 
@@ -935,6 +936,49 @@ Tips: set `IGNORE_SSL_TOOLS=true` only if your host serves a self-signed/mismatc
935
936
 
936
937
  </details>
937
938
 
939
+ <details>
940
+ <summary><b>Home Assistant Example</b> — control your smart home; <code>call_service</code> needs the >= 0.3.3 path-param body fix</summary>
941
+
942
+ Exposes a small generic slice of the Home Assistant REST API: `get_config`, `list_states`,
943
+ `get_state`, `list_services`, `call_service`, `get_history`.
944
+
945
+ #### 1. Verify the OpenAPI specification
946
+
947
+ ```bash
948
+ curl https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json
949
+ ```
950
+
951
+ #### 2. Configure mcp-openapi-proxy for Home Assistant
952
+
953
+ ```json
954
+ {
955
+ "mcpServers": {
956
+ "homeassistant": {
957
+ "command": "uvx",
958
+ "args": ["mcp-openapi-proxy"],
959
+ "env": {
960
+ "OPENAPI_SPEC_URL": "https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json",
961
+ "SERVER_URL_OVERRIDE": "http://homeassistant.local:8123",
962
+ "EXTRA_HEADERS": "Authorization: Bearer ${HA_TOKEN}"
963
+ }
964
+ }
965
+ }
966
+ }
967
+ ```
968
+
969
+ Key configuration points:
970
+ - `SERVER_URL_OVERRIDE` — your instance base URL, e.g. `http://homeassistant.local:8123` or `http://<ha-host>:8123`.
971
+ - `HA_TOKEN` — a Home Assistant **long-lived access token** (Profile → Long-Lived Access Tokens), passed via `EXTRA_HEADERS`. Never commit the token; keep it in your environment.
972
+ - `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.
973
+
974
+ #### 3. Testing
975
+
976
+ `get_config` should return your HA configuration (200). `call_service` for `light/turn_on`
977
+ with body `{"entity_id": "light.kitchen"}` should return 200 — the path params land in the URL
978
+ (`/api/services/light/turn_on`), not the body.
979
+
980
+ </details>
981
+
938
982
  ## Troubleshooting
939
983
 
940
984
  ### JSON-RPC Testing
@@ -148,6 +148,7 @@ 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 | 6 | `get_config` (200) + `call_service` (light.turn_on, 200) | `SERVER_URL_OVERRIDE` + `EXTRA_HEADERS` (`Authorization: Bearer ${HA_TOKEN}`) |
151
152
 
152
153
  ### Client matrix
153
154
 
@@ -913,6 +914,49 @@ Tips: set `IGNORE_SSL_TOOLS=true` only if your host serves a self-signed/mismatc
913
914
 
914
915
  </details>
915
916
 
917
+ <details>
918
+ <summary><b>Home Assistant Example</b> — control your smart home; <code>call_service</code> needs the >= 0.3.3 path-param body fix</summary>
919
+
920
+ Exposes a small generic slice of the Home Assistant REST API: `get_config`, `list_states`,
921
+ `get_state`, `list_services`, `call_service`, `get_history`.
922
+
923
+ #### 1. Verify the OpenAPI specification
924
+
925
+ ```bash
926
+ curl https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json
927
+ ```
928
+
929
+ #### 2. Configure mcp-openapi-proxy for Home Assistant
930
+
931
+ ```json
932
+ {
933
+ "mcpServers": {
934
+ "homeassistant": {
935
+ "command": "uvx",
936
+ "args": ["mcp-openapi-proxy"],
937
+ "env": {
938
+ "OPENAPI_SPEC_URL": "https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json",
939
+ "SERVER_URL_OVERRIDE": "http://homeassistant.local:8123",
940
+ "EXTRA_HEADERS": "Authorization: Bearer ${HA_TOKEN}"
941
+ }
942
+ }
943
+ }
944
+ }
945
+ ```
946
+
947
+ Key configuration points:
948
+ - `SERVER_URL_OVERRIDE` — your instance base URL, e.g. `http://homeassistant.local:8123` or `http://<ha-host>:8123`.
949
+ - `HA_TOKEN` — a Home Assistant **long-lived access token** (Profile → Long-Lived Access Tokens), passed via `EXTRA_HEADERS`. Never commit the token; keep it in your environment.
950
+ - `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.
951
+
952
+ #### 3. Testing
953
+
954
+ `get_config` should return your HA configuration (200). `call_service` for `light/turn_on`
955
+ with body `{"entity_id": "light.kitchen"}` should return 200 — the path params land in the URL
956
+ (`/api/services/light/turn_on`), not the body.
957
+
958
+ </details>
959
+
916
960
  ## Troubleshooting
917
961
 
918
962
  ### JSON-RPC Testing
@@ -36,6 +36,55 @@ spec = None # Global spec for resources
36
36
  # callable via call_function.
37
37
  _FUNCTION_OPERATIONS: Dict[str, Dict] = {}
38
38
 
39
+
40
+ # --- Native MCP prompts & resources for FastMCP/simple mode ---
41
+ # Previously simple mode advertised the prompts/resources capabilities (FastMCP
42
+ # does so automatically) but registered none, so prompts/list & resources/list
43
+ # came back EMPTY. Register them natively here for parity with the low-level
44
+ # server (same names/content): prompts summarize_spec + whimsical_blog, and the
45
+ # spec_file resource.
46
+
47
+ def _spec_as_json() -> str:
48
+ """Serialize the OpenAPI spec for the spec_file resource.
49
+
50
+ Serves the in-memory spec that run_simple_server preloads once; only
51
+ fetches as a lazy fallback. This avoids a live network re-fetch on every
52
+ resources/read, respecting the live-first-once spec lifecycle (#28).
53
+ Uses default=str so YAML datetime example values don't crash serialization.
54
+ """
55
+ global spec
56
+ if spec is None:
57
+ spec_url = os.environ.get("OPENAPI_SPEC_URL")
58
+ if not spec_url:
59
+ return json.dumps({"error": "OPENAPI_SPEC_URL is not configured"})
60
+ spec = fetch_openapi_spec(spec_url)
61
+ if not spec:
62
+ return json.dumps({"error": "Failed to fetch OpenAPI spec"})
63
+ if isinstance(spec, str):
64
+ return spec
65
+ return json.dumps(spec, indent=2, default=str)
66
+
67
+
68
+ @mcp.resource("file:///openapi_spec.json", name="spec_file",
69
+ description="The raw OpenAPI specification JSON", mime_type="application/json")
70
+ def _spec_file_resource() -> str:
71
+ """Serve the configured OpenAPI spec as a native MCP resource."""
72
+ return _spec_as_json()
73
+
74
+
75
+ @mcp.prompt(name="summarize_spec", description="Summarizes the OpenAPI specification")
76
+ def _summarize_spec_prompt() -> str:
77
+ return ("This OpenAPI spec defines endpoints, parameters, and responses—a "
78
+ "blueprint for developers to integrate effectively.")
79
+
80
+
81
+ @mcp.prompt(name="whimsical_blog", description="A whimsical WordPress blog-post starter inspired by this API")
82
+ def _whimsical_blog_prompt() -> str:
83
+ return ("Once upon a JSON, in a land of tilde keys and sticky semicolons, a pet AI "
84
+ "chatbot discovered it could whisper to WordPress through a magic OpenAPI proxy. "
85
+ "✨ Write the next whimsical chapter.")
86
+
87
+
39
88
  @mcp.tool()
40
89
  def list_functions(*, env_key: str = "OPENAPI_SPEC_URL") -> str:
41
90
  """Lists available functions derived from the OpenAPI specification."""
@@ -243,7 +292,8 @@ def call_function(*, function_name: str, parameters: Optional[Dict] = None, env_
243
292
  spec_local = json.loads(spec_local)
244
293
  if spec_local is None:
245
294
  return json.dumps({"error": "Failed to fetch OpenAPI spec"})
246
- return json.dumps(spec_local, indent=2)
295
+ # default=str: YAML datetime example values aren't JSON-serializable
296
+ return json.dumps(spec_local, indent=2, default=str)
247
297
  if function_name == "list_prompts":
248
298
  return json.dumps([{"name": "summarize_spec", "description": "Summarizes the purpose of the OpenAPI specification", "arguments": []}])
249
299
  if function_name == "get_prompt":
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mcp-openapi-proxy
3
- Version: 0.3.1
3
+ Version: 0.3.3
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
@@ -170,6 +170,7 @@ 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 | 6 | `get_config` (200) + `call_service` (light.turn_on, 200) | `SERVER_URL_OVERRIDE` + `EXTRA_HEADERS` (`Authorization: Bearer ${HA_TOKEN}`) |
173
174
 
174
175
  ### Client matrix
175
176
 
@@ -935,6 +936,49 @@ Tips: set `IGNORE_SSL_TOOLS=true` only if your host serves a self-signed/mismatc
935
936
 
936
937
  </details>
937
938
 
939
+ <details>
940
+ <summary><b>Home Assistant Example</b> — control your smart home; <code>call_service</code> needs the >= 0.3.3 path-param body fix</summary>
941
+
942
+ Exposes a small generic slice of the Home Assistant REST API: `get_config`, `list_states`,
943
+ `get_state`, `list_services`, `call_service`, `get_history`.
944
+
945
+ #### 1. Verify the OpenAPI specification
946
+
947
+ ```bash
948
+ curl https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json
949
+ ```
950
+
951
+ #### 2. Configure mcp-openapi-proxy for Home Assistant
952
+
953
+ ```json
954
+ {
955
+ "mcpServers": {
956
+ "homeassistant": {
957
+ "command": "uvx",
958
+ "args": ["mcp-openapi-proxy"],
959
+ "env": {
960
+ "OPENAPI_SPEC_URL": "https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/refs/heads/main/examples/homeassistant.openapi.json",
961
+ "SERVER_URL_OVERRIDE": "http://homeassistant.local:8123",
962
+ "EXTRA_HEADERS": "Authorization: Bearer ${HA_TOKEN}"
963
+ }
964
+ }
965
+ }
966
+ }
967
+ ```
968
+
969
+ Key configuration points:
970
+ - `SERVER_URL_OVERRIDE` — your instance base URL, e.g. `http://homeassistant.local:8123` or `http://<ha-host>:8123`.
971
+ - `HA_TOKEN` — a Home Assistant **long-lived access token** (Profile → Long-Lived Access Tokens), passed via `EXTRA_HEADERS`. Never commit the token; keep it in your environment.
972
+ - `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.
973
+
974
+ #### 3. Testing
975
+
976
+ `get_config` should return your HA configuration (200). `call_service` for `light/turn_on`
977
+ with body `{"entity_id": "light.kitchen"}` should return 200 — the path params land in the URL
978
+ (`/api/services/light/turn_on`), not the body.
979
+
980
+ </details>
981
+
938
982
  ## Troubleshooting
939
983
 
940
984
  ### JSON-RPC Testing
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "mcp-openapi-proxy"
7
7
  requires-python = ">=3.10"
8
- version = "0.3.1"
8
+ version = "0.3.3"
9
9
  description = "MCP server for exposing OpenAPI specifications as MCP tools."
10
10
  readme = "README.md"
11
11
  authors = [