mcp-openapi-proxy 0.2.0__tar.gz → 0.3.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.
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/PKG-INFO +74 -12
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/README.md +73 -11
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy/server_lowlevel.py +9 -6
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy/utils.py +25 -3
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy.egg-info/PKG-INFO +74 -12
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/pyproject.toml +1 -1
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/LICENSE +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy/__init__.py +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy/handlers.py +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy/logging_setup.py +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy/openapi.py +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy/server_fastmcp.py +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy/types.py +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy.egg-info/SOURCES.txt +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy.egg-info/dependency_links.txt +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy.egg-info/entry_points.txt +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy.egg-info/requires.txt +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy.egg-info/top_level.txt +0 -0
- {mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-openapi-proxy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -31,10 +31,11 @@ Dynamic: license-file
|
|
|
31
31
|
- ✅ **Codex**, **Gemini**, **Qwen**, **Kilocode**, **opencode** — native tool calls over stdio
|
|
32
32
|
- ✅ **Vibe** — native discovery and read calls (writes were CLI-flaky, not a proxy issue)
|
|
33
33
|
- ✅ **Letta** — Cloud (via a remote streamable-HTTP MCP URL) and self-hosted (via stdio)
|
|
34
|
-
- ⚠️ **agy** — could not attach in headless mode; MCP tool enablement is interactive-only in agy (an agy limitation, not the proxy)
|
|
35
34
|
|
|
36
35
|
See the [client matrix](#client-matrix) for attach mechanisms, models, and exact results.
|
|
37
36
|
|
|
37
|
+
> 📄 Full write-up: [**Verification case study**](docs/verification-case-study.md) — what the proxy is, the API + client matrices, and every defect found & fixed.
|
|
38
|
+
|
|
38
39
|
**Prompts and resources are real now — including custom resources.** Both MCP surfaces are functional and tested: the `summarize_spec` / `whimsical_blog` prompts and the `spec_file` resource, plus a new `ADDITIONAL_RESOURCES` env var that serves your *own* use-case documents (e.g. a NetBox naming policy or an Asana project-layout guide) as MCP resources — see `examples/resources/`.
|
|
39
40
|
|
|
40
41
|
**Bug fixes (every one live-verified):**
|
|
@@ -63,7 +64,7 @@ Full environment-variable reference is in [Environment Variables](#environment-v
|
|
|
63
64
|
- [Environment Variables](#environment-variables)
|
|
64
65
|
- [Verified Clients & Live Results (2026-06-12)](#verified-clients--live-results-2026-06-12)
|
|
65
66
|
- [Tips](#tips)
|
|
66
|
-
- [Examples](#examples) — Glama, Fly.io, Render, Slack, GetZep, Virustotal, Notion, Asana, APIs.guru, NetBox, Box, WolframAlpha (collapsed)
|
|
67
|
+
- [Examples](#examples) — Glama, Fly.io, Render, Slack, GetZep, Virustotal, Notion, Asana, APIs.guru, NetBox, Box, WolframAlpha, WordPress (collapsed)
|
|
67
68
|
- [Troubleshooting](#troubleshooting)
|
|
68
69
|
- [License](#license)
|
|
69
70
|
|
|
@@ -174,15 +175,27 @@ The example configurations below were exercised against the live APIs, and the p
|
|
|
174
175
|
|
|
175
176
|
| Agent CLI | Model used (live test) | MCP attach mechanism | Tool calls | Prompts/Resources surfaced to model? |
|
|
176
177
|
|---|---|---|---|---|
|
|
177
|
-
| Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native |
|
|
178
|
-
| Gemini | Google OAuth free tier (CLI default model) | project `.gemini/settings.json` `mcpServers` | ✅ native |
|
|
179
|
-
| Qwen | `agent` group via local LiteLLM gateway | project `.qwen/settings.json` | ✅ native | ❌
|
|
180
|
-
| Kilocode | `kilo-auto/free` | global `settings/mcp_settings.json`, clean workspace | ✅ native | ❌ |
|
|
181
|
-
| opencode | `orchestration` group via local LiteLLM gateway | `~/.config/opencode/opencode.json` `mcp` | ✅ native |
|
|
182
|
-
| Vibe | `mistral-medium-3.5` | `~/.vibe/config.toml` `[[mcp_servers]]` | ✅ discovery + reads (writes flaky) | ❌ |
|
|
183
|
-
| agy | — | — | ❌ headless cannot enable MCP |
|
|
184
|
-
| letta cloud | Letta Cloud default | streamable-HTTP MCP URL (`/mcp add --transport http` + bearer) | ✅ remote (stdio rejected) |
|
|
185
|
-
| letta (self-hosted ≤0.11.x) | `agent` group via local LiteLLM gateway | stdio via `PUT /v1/tools/mcp/servers` | ✅ native |
|
|
178
|
+
| Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native | unknown ‡ |
|
|
179
|
+
| Gemini | Google OAuth free tier (CLI default model) | project `.gemini/settings.json` `mcpServers` | ✅ native | prompts: interactive slash only · resources: ❌ † |
|
|
180
|
+
| Qwen | `agent` group via local LiteLLM gateway | project `.qwen/settings.json` | ✅ native | prompts: ✅ (slash `/summarize_spec`) · resources: ❌ † |
|
|
181
|
+
| Kilocode | `kilo-auto/free` | global `settings/mcp_settings.json`, clean workspace | ✅ native | prompts: ❌ · resources: ✅ (`access_mcp_resource`) † |
|
|
182
|
+
| opencode | `orchestration` group via local LiteLLM gateway | `~/.config/opencode/opencode.json` `mcp` | ✅ native | unknown ‡ |
|
|
183
|
+
| Vibe | `mistral-medium-3.5` | `~/.vibe/config.toml` `[[mcp_servers]]` | ✅ discovery + reads (writes flaky) | prompts: ❌ · resources: ❌ (tools-only) † |
|
|
184
|
+
| agy | — | — | ❌ headless cannot enable MCP | n/a |
|
|
185
|
+
| letta cloud | Letta Cloud default | streamable-HTTP MCP URL (`/mcp add --transport http` + bearer) | ✅ remote (stdio rejected) | unknown ‡ |
|
|
186
|
+
| letta (self-hosted ≤0.11.x) | `agent` group via local LiteLLM gateway | stdio via `PUT /v1/tools/mcp/servers` | ✅ native | unknown ‡ |
|
|
187
|
+
|
|
188
|
+
> **Prompts/resources column — read this.** The original 0.2.0 sweep ran while the
|
|
189
|
+
> server advertised prompts/resources **only when `ENABLE_PROMPTS`/`ENABLE_RESOURCES`
|
|
190
|
+
> were set — they defaulted OFF.** Per the MCP spec a client won't call
|
|
191
|
+
> `prompts/list`/`resources/list` unless the capability is advertised, so those early
|
|
192
|
+
> results measured the *server's* default, not the clients. **All prior prompt/resource
|
|
193
|
+
> findings are therefore voided.** This release defaults advertising **on**.
|
|
194
|
+
>
|
|
195
|
+
> - **† re-verified 2026-06-14** with advertising on, against the real client binary. Genuine, uneven: **tools** everywhere; **prompts→model** Qwen (slash) & Gemini (interactive only); **resources→model** Kilocode. Vibe is tools-only (confirmed in source).
|
|
196
|
+
> - **‡ unknown** — not yet re-tested under advertising-on (opencode not installed here; Letta needs a running server + gateway; Codex parked). Prior ❌ values are not carried forward.
|
|
197
|
+
>
|
|
198
|
+
> Tool-calling (the `Tool calls` column) was unaffected by the advertising default and stands as originally verified.
|
|
186
199
|
|
|
187
200
|
Minimal sanitized configs per client (the no-auth Glama spec is used as the smallest working example; substitute your own spec URL and `$YOUR_KEY` as needed):
|
|
188
201
|
|
|
@@ -868,6 +881,55 @@ You can now use the MCP ecosystem to list and invoke WolframAlpha API tools. For
|
|
|
868
881
|
|
|
869
882
|
</details>
|
|
870
883
|
|
|
884
|
+
<details>
|
|
885
|
+
<summary><b>Letta (self-hosted + Cloud)</b> — agent platform; stdio for self-hosted, authenticated remote HTTP for Cloud</summary>
|
|
886
|
+
|
|
887
|
+
Letta runs agents on a server, so attachment differs by deployment:
|
|
888
|
+
|
|
889
|
+
- **Self-hosted Letta** accepts a **stdio** MCP server via `PUT /v1/tools/mcp/servers` — no network exposure.
|
|
890
|
+
- **Letta Cloud** rejects stdio and needs a **remote streamable-HTTP** MCP URL behind authentication.
|
|
891
|
+
|
|
892
|
+
Both were verified live (an agent autonomously called `get_v1_attributes` from the Glama spec through the proxy). Full setup for both paths, including the supergateway wrapper and the security note for the Cloud endpoint, is in [`examples/letta/README.md`](examples/letta/README.md).
|
|
893
|
+
|
|
894
|
+
</details>
|
|
895
|
+
|
|
896
|
+
<details>
|
|
897
|
+
<summary><b>WordPress Example</b> — publish to a real WordPress blog from any MCP agent</summary>
|
|
898
|
+
|
|
899
|
+
This example turns the WordPress REST API into MCP tools so an agent can **create, read, update, and trash blog posts**. It is the example behind a real-world demonstration: a fleet of agent CLIs published to the live blog at **[matthewhand.mywebcommunity.org](https://matthewhand.mywebcommunity.org/)** through this proxy — see the write-up [*“Turning Any API into Agent Tools”*](https://matthewhand.mywebcommunity.org/2026/06/12/turning-any-api-into-agent-tools-a-real-world-test-of-mcp-openapi-proxy/), itself posted via the proxy.
|
|
900
|
+
|
|
901
|
+
Example config — `examples/wordpress-claude_desktop_config.json`:
|
|
902
|
+
|
|
903
|
+
```json
|
|
904
|
+
{
|
|
905
|
+
"mcpServers": {
|
|
906
|
+
"wordpress": {
|
|
907
|
+
"command": "uvx",
|
|
908
|
+
"args": ["mcp-openapi-proxy"],
|
|
909
|
+
"env": {
|
|
910
|
+
"OPENAPI_SPEC_URL": "https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/main/examples/wordpress-openapi.json",
|
|
911
|
+
"SERVER_URL_OVERRIDE": "https://your-site.example.com/wp-json",
|
|
912
|
+
"EXTRA_HEADERS": "Authorization: Basic BASE64_OF_USERNAME_COLON_APPLICATION_PASSWORD",
|
|
913
|
+
"IGNORE_SSL_TOOLS": "false",
|
|
914
|
+
"TOOL_WHITELIST": "/wp/v2/posts"
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
Exposed tools (from the bundled `examples/wordpress-openapi.json`): `get_wp_v2_posts`, `post_wp_v2_posts` (create), `get_wp_v2_posts_by_id`, `post_wp_v2_posts_by_id` (update), and `delete_wp_v2_posts_by_id` (trash) — the full post lifecycle.
|
|
922
|
+
|
|
923
|
+
**Auth — use a WordPress *Application Password*, not your login password.** WordPress REST rejects login passwords for Basic auth. In `wp-admin → Users → (your user) → Application Passwords`, create one, then base64-encode `username:application-password` for the `EXTRA_HEADERS` value:
|
|
924
|
+
|
|
925
|
+
```bash
|
|
926
|
+
printf 'myuser:xxxx xxxx xxxx xxxx xxxx xxxx' | base64 -w0
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
Tips: set `IGNORE_SSL_TOOLS=true` only if your host serves a self-signed/mismatched cert; give each agent its own (revocable) application password; keep `TOOL_WHITELIST=/wp/v2/posts` so the agent can touch only posts.
|
|
930
|
+
|
|
931
|
+
</details>
|
|
932
|
+
|
|
871
933
|
## Troubleshooting
|
|
872
934
|
|
|
873
935
|
### JSON-RPC Testing
|
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
- ✅ **Codex**, **Gemini**, **Qwen**, **Kilocode**, **opencode** — native tool calls over stdio
|
|
10
10
|
- ✅ **Vibe** — native discovery and read calls (writes were CLI-flaky, not a proxy issue)
|
|
11
11
|
- ✅ **Letta** — Cloud (via a remote streamable-HTTP MCP URL) and self-hosted (via stdio)
|
|
12
|
-
- ⚠️ **agy** — could not attach in headless mode; MCP tool enablement is interactive-only in agy (an agy limitation, not the proxy)
|
|
13
12
|
|
|
14
13
|
See the [client matrix](#client-matrix) for attach mechanisms, models, and exact results.
|
|
15
14
|
|
|
15
|
+
> 📄 Full write-up: [**Verification case study**](docs/verification-case-study.md) — what the proxy is, the API + client matrices, and every defect found & fixed.
|
|
16
|
+
|
|
16
17
|
**Prompts and resources are real now — including custom resources.** Both MCP surfaces are functional and tested: the `summarize_spec` / `whimsical_blog` prompts and the `spec_file` resource, plus a new `ADDITIONAL_RESOURCES` env var that serves your *own* use-case documents (e.g. a NetBox naming policy or an Asana project-layout guide) as MCP resources — see `examples/resources/`.
|
|
17
18
|
|
|
18
19
|
**Bug fixes (every one live-verified):**
|
|
@@ -41,7 +42,7 @@ Full environment-variable reference is in [Environment Variables](#environment-v
|
|
|
41
42
|
- [Environment Variables](#environment-variables)
|
|
42
43
|
- [Verified Clients & Live Results (2026-06-12)](#verified-clients--live-results-2026-06-12)
|
|
43
44
|
- [Tips](#tips)
|
|
44
|
-
- [Examples](#examples) — Glama, Fly.io, Render, Slack, GetZep, Virustotal, Notion, Asana, APIs.guru, NetBox, Box, WolframAlpha (collapsed)
|
|
45
|
+
- [Examples](#examples) — Glama, Fly.io, Render, Slack, GetZep, Virustotal, Notion, Asana, APIs.guru, NetBox, Box, WolframAlpha, WordPress (collapsed)
|
|
45
46
|
- [Troubleshooting](#troubleshooting)
|
|
46
47
|
- [License](#license)
|
|
47
48
|
|
|
@@ -152,15 +153,27 @@ The example configurations below were exercised against the live APIs, and the p
|
|
|
152
153
|
|
|
153
154
|
| Agent CLI | Model used (live test) | MCP attach mechanism | Tool calls | Prompts/Resources surfaced to model? |
|
|
154
155
|
|---|---|---|---|---|
|
|
155
|
-
| Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native |
|
|
156
|
-
| Gemini | Google OAuth free tier (CLI default model) | project `.gemini/settings.json` `mcpServers` | ✅ native |
|
|
157
|
-
| Qwen | `agent` group via local LiteLLM gateway | project `.qwen/settings.json` | ✅ native | ❌
|
|
158
|
-
| Kilocode | `kilo-auto/free` | global `settings/mcp_settings.json`, clean workspace | ✅ native | ❌ |
|
|
159
|
-
| opencode | `orchestration` group via local LiteLLM gateway | `~/.config/opencode/opencode.json` `mcp` | ✅ native |
|
|
160
|
-
| Vibe | `mistral-medium-3.5` | `~/.vibe/config.toml` `[[mcp_servers]]` | ✅ discovery + reads (writes flaky) | ❌ |
|
|
161
|
-
| agy | — | — | ❌ headless cannot enable MCP |
|
|
162
|
-
| letta cloud | Letta Cloud default | streamable-HTTP MCP URL (`/mcp add --transport http` + bearer) | ✅ remote (stdio rejected) |
|
|
163
|
-
| letta (self-hosted ≤0.11.x) | `agent` group via local LiteLLM gateway | stdio via `PUT /v1/tools/mcp/servers` | ✅ native |
|
|
156
|
+
| Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native | unknown ‡ |
|
|
157
|
+
| Gemini | Google OAuth free tier (CLI default model) | project `.gemini/settings.json` `mcpServers` | ✅ native | prompts: interactive slash only · resources: ❌ † |
|
|
158
|
+
| Qwen | `agent` group via local LiteLLM gateway | project `.qwen/settings.json` | ✅ native | prompts: ✅ (slash `/summarize_spec`) · resources: ❌ † |
|
|
159
|
+
| Kilocode | `kilo-auto/free` | global `settings/mcp_settings.json`, clean workspace | ✅ native | prompts: ❌ · resources: ✅ (`access_mcp_resource`) † |
|
|
160
|
+
| opencode | `orchestration` group via local LiteLLM gateway | `~/.config/opencode/opencode.json` `mcp` | ✅ native | unknown ‡ |
|
|
161
|
+
| Vibe | `mistral-medium-3.5` | `~/.vibe/config.toml` `[[mcp_servers]]` | ✅ discovery + reads (writes flaky) | prompts: ❌ · resources: ❌ (tools-only) † |
|
|
162
|
+
| agy | — | — | ❌ headless cannot enable MCP | n/a |
|
|
163
|
+
| letta cloud | Letta Cloud default | streamable-HTTP MCP URL (`/mcp add --transport http` + bearer) | ✅ remote (stdio rejected) | unknown ‡ |
|
|
164
|
+
| letta (self-hosted ≤0.11.x) | `agent` group via local LiteLLM gateway | stdio via `PUT /v1/tools/mcp/servers` | ✅ native | unknown ‡ |
|
|
165
|
+
|
|
166
|
+
> **Prompts/resources column — read this.** The original 0.2.0 sweep ran while the
|
|
167
|
+
> server advertised prompts/resources **only when `ENABLE_PROMPTS`/`ENABLE_RESOURCES`
|
|
168
|
+
> were set — they defaulted OFF.** Per the MCP spec a client won't call
|
|
169
|
+
> `prompts/list`/`resources/list` unless the capability is advertised, so those early
|
|
170
|
+
> results measured the *server's* default, not the clients. **All prior prompt/resource
|
|
171
|
+
> findings are therefore voided.** This release defaults advertising **on**.
|
|
172
|
+
>
|
|
173
|
+
> - **† re-verified 2026-06-14** with advertising on, against the real client binary. Genuine, uneven: **tools** everywhere; **prompts→model** Qwen (slash) & Gemini (interactive only); **resources→model** Kilocode. Vibe is tools-only (confirmed in source).
|
|
174
|
+
> - **‡ unknown** — not yet re-tested under advertising-on (opencode not installed here; Letta needs a running server + gateway; Codex parked). Prior ❌ values are not carried forward.
|
|
175
|
+
>
|
|
176
|
+
> Tool-calling (the `Tool calls` column) was unaffected by the advertising default and stands as originally verified.
|
|
164
177
|
|
|
165
178
|
Minimal sanitized configs per client (the no-auth Glama spec is used as the smallest working example; substitute your own spec URL and `$YOUR_KEY` as needed):
|
|
166
179
|
|
|
@@ -846,6 +859,55 @@ You can now use the MCP ecosystem to list and invoke WolframAlpha API tools. For
|
|
|
846
859
|
|
|
847
860
|
</details>
|
|
848
861
|
|
|
862
|
+
<details>
|
|
863
|
+
<summary><b>Letta (self-hosted + Cloud)</b> — agent platform; stdio for self-hosted, authenticated remote HTTP for Cloud</summary>
|
|
864
|
+
|
|
865
|
+
Letta runs agents on a server, so attachment differs by deployment:
|
|
866
|
+
|
|
867
|
+
- **Self-hosted Letta** accepts a **stdio** MCP server via `PUT /v1/tools/mcp/servers` — no network exposure.
|
|
868
|
+
- **Letta Cloud** rejects stdio and needs a **remote streamable-HTTP** MCP URL behind authentication.
|
|
869
|
+
|
|
870
|
+
Both were verified live (an agent autonomously called `get_v1_attributes` from the Glama spec through the proxy). Full setup for both paths, including the supergateway wrapper and the security note for the Cloud endpoint, is in [`examples/letta/README.md`](examples/letta/README.md).
|
|
871
|
+
|
|
872
|
+
</details>
|
|
873
|
+
|
|
874
|
+
<details>
|
|
875
|
+
<summary><b>WordPress Example</b> — publish to a real WordPress blog from any MCP agent</summary>
|
|
876
|
+
|
|
877
|
+
This example turns the WordPress REST API into MCP tools so an agent can **create, read, update, and trash blog posts**. It is the example behind a real-world demonstration: a fleet of agent CLIs published to the live blog at **[matthewhand.mywebcommunity.org](https://matthewhand.mywebcommunity.org/)** through this proxy — see the write-up [*“Turning Any API into Agent Tools”*](https://matthewhand.mywebcommunity.org/2026/06/12/turning-any-api-into-agent-tools-a-real-world-test-of-mcp-openapi-proxy/), itself posted via the proxy.
|
|
878
|
+
|
|
879
|
+
Example config — `examples/wordpress-claude_desktop_config.json`:
|
|
880
|
+
|
|
881
|
+
```json
|
|
882
|
+
{
|
|
883
|
+
"mcpServers": {
|
|
884
|
+
"wordpress": {
|
|
885
|
+
"command": "uvx",
|
|
886
|
+
"args": ["mcp-openapi-proxy"],
|
|
887
|
+
"env": {
|
|
888
|
+
"OPENAPI_SPEC_URL": "https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/main/examples/wordpress-openapi.json",
|
|
889
|
+
"SERVER_URL_OVERRIDE": "https://your-site.example.com/wp-json",
|
|
890
|
+
"EXTRA_HEADERS": "Authorization: Basic BASE64_OF_USERNAME_COLON_APPLICATION_PASSWORD",
|
|
891
|
+
"IGNORE_SSL_TOOLS": "false",
|
|
892
|
+
"TOOL_WHITELIST": "/wp/v2/posts"
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
Exposed tools (from the bundled `examples/wordpress-openapi.json`): `get_wp_v2_posts`, `post_wp_v2_posts` (create), `get_wp_v2_posts_by_id`, `post_wp_v2_posts_by_id` (update), and `delete_wp_v2_posts_by_id` (trash) — the full post lifecycle.
|
|
900
|
+
|
|
901
|
+
**Auth — use a WordPress *Application Password*, not your login password.** WordPress REST rejects login passwords for Basic auth. In `wp-admin → Users → (your user) → Application Passwords`, create one, then base64-encode `username:application-password` for the `EXTRA_HEADERS` value:
|
|
902
|
+
|
|
903
|
+
```bash
|
|
904
|
+
printf 'myuser:xxxx xxxx xxxx xxxx xxxx xxxx' | base64 -w0
|
|
905
|
+
```
|
|
906
|
+
|
|
907
|
+
Tips: set `IGNORE_SSL_TOOLS=true` only if your host serves a self-signed/mismatched cert; give each agent its own (revocable) application password; keep `TOOL_WHITELIST=/wp/v2/posts` so the agent can touch only posts.
|
|
908
|
+
|
|
909
|
+
</details>
|
|
910
|
+
|
|
849
911
|
## Troubleshooting
|
|
850
912
|
|
|
851
913
|
### JSON-RPC Testing
|
|
@@ -14,8 +14,8 @@ Configuration is controlled via environment variables:
|
|
|
14
14
|
- CAPABILITIES_RESOURCES: Set to "true" to enable resources advertising (default: false).
|
|
15
15
|
- CAPABILITIES_PROMPTS: Set to "true" to enable prompts advertising (default: false).
|
|
16
16
|
- ENABLE_TOOLS: Set to "false" to disable tools functionality (default: true).
|
|
17
|
-
- ENABLE_RESOURCES: Set to "
|
|
18
|
-
- ENABLE_PROMPTS: Set to "
|
|
17
|
+
- ENABLE_RESOURCES: Set to "false" to disable resources functionality (default: true).
|
|
18
|
+
- ENABLE_PROMPTS: Set to "false" to disable prompts functionality (default: true).
|
|
19
19
|
"""
|
|
20
20
|
|
|
21
21
|
import os
|
|
@@ -53,10 +53,13 @@ CAPABILITIES_TOOLS = os.getenv("CAPABILITIES_TOOLS", "false").lower() == "true"
|
|
|
53
53
|
CAPABILITIES_RESOURCES = os.getenv("CAPABILITIES_RESOURCES", "false").lower() == "true"
|
|
54
54
|
CAPABILITIES_PROMPTS = os.getenv("CAPABILITIES_PROMPTS", "false").lower() == "true"
|
|
55
55
|
|
|
56
|
-
#
|
|
56
|
+
# Feature enablement (all on by default). Advertising a capability is
|
|
57
|
+
# required for clients to even attempt prompts/list & resources/list;
|
|
58
|
+
# defaulting these OFF made prompts/resources invisible to every client
|
|
59
|
+
# despite being implemented. Set to "false" to opt out.
|
|
57
60
|
ENABLE_TOOLS = os.getenv("ENABLE_TOOLS", "true").lower() == "true"
|
|
58
|
-
ENABLE_RESOURCES = os.getenv("ENABLE_RESOURCES", "
|
|
59
|
-
ENABLE_PROMPTS = os.getenv("ENABLE_PROMPTS", "
|
|
61
|
+
ENABLE_RESOURCES = os.getenv("ENABLE_RESOURCES", "true").lower() == "true"
|
|
62
|
+
ENABLE_PROMPTS = os.getenv("ENABLE_PROMPTS", "true").lower() == "true"
|
|
60
63
|
|
|
61
64
|
# Resource and prompt DEFINITIONS are always present so the feature is
|
|
62
65
|
# deterministically testable. Whether they are EXPOSED to clients is gated
|
|
@@ -408,7 +411,7 @@ async def read_resource(request: types.ReadResourceRequest) -> types.ReadResourc
|
|
|
408
411
|
]
|
|
409
412
|
)
|
|
410
413
|
logger.debug("Dumping spec to JSON...")
|
|
411
|
-
spec_json = json.dumps(spec_data, indent=2)
|
|
414
|
+
spec_json = json.dumps(spec_data, indent=2, default=str)
|
|
412
415
|
logger.debug(f"Forcing spec JSON return: {spec_json[:50]}...")
|
|
413
416
|
return types.ReadResourceResult(
|
|
414
417
|
contents=[
|
|
@@ -14,6 +14,28 @@ from mcp import types
|
|
|
14
14
|
# Import the configured logger
|
|
15
15
|
from .logging_setup import logger
|
|
16
16
|
|
|
17
|
+
|
|
18
|
+
class _NoTimestampSafeLoader(yaml.SafeLoader):
|
|
19
|
+
"""SafeLoader that keeps unquoted RFC3339 timestamps as plain strings.
|
|
20
|
+
|
|
21
|
+
PyYAML's SafeLoader auto-converts values like ``2015-02-22T20:00:45.000Z``
|
|
22
|
+
into Python ``datetime`` objects. Such objects are not JSON-serializable,
|
|
23
|
+
which crashed spec caching and ``resources/read`` for specs that carry
|
|
24
|
+
datetime example values (e.g. the apis.guru directory spec). Loading specs
|
|
25
|
+
with this loader keeps every scalar JSON-safe.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_NoTimestampSafeLoader.yaml_implicit_resolvers = {
|
|
30
|
+
key: [(tag, regexp) for tag, regexp in resolvers if tag != "tag:yaml.org,2002:timestamp"]
|
|
31
|
+
for key, resolvers in yaml.SafeLoader.yaml_implicit_resolvers.items()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def yaml_load_safe(content: str):
|
|
36
|
+
"""Parse YAML with timestamps preserved as strings (JSON-safe)."""
|
|
37
|
+
return yaml.load(content, Loader=_NoTimestampSafeLoader)
|
|
38
|
+
|
|
17
39
|
def setup_logging(debug: bool = False):
|
|
18
40
|
"""
|
|
19
41
|
Configure logging for the application.
|
|
@@ -196,7 +218,7 @@ def _spec_cache_store(url: str, spec: Dict) -> None:
|
|
|
196
218
|
try:
|
|
197
219
|
path = _spec_cache_path(url)
|
|
198
220
|
with open(path + ".tmp", "w") as f:
|
|
199
|
-
json.dump(spec, f)
|
|
221
|
+
json.dump(spec, f, default=str)
|
|
200
222
|
os.replace(path + ".tmp", path)
|
|
201
223
|
logger.debug(f"Cached spec for {url} at {path}")
|
|
202
224
|
except OSError as exc:
|
|
@@ -226,7 +248,7 @@ def fetch_openapi_spec(url: str, retries: int = 3) -> Optional[Dict]:
|
|
|
226
248
|
logger.debug(f"Using {spec_format.upper()} parser based on OPENAPI_SPEC_FORMAT env var")
|
|
227
249
|
if spec_format == "yaml":
|
|
228
250
|
try:
|
|
229
|
-
spec =
|
|
251
|
+
spec = yaml_load_safe(content)
|
|
230
252
|
logger.debug(f"Parsed as YAML from {url}")
|
|
231
253
|
except yaml.YAMLError as ye:
|
|
232
254
|
logger.error(f"YAML parsing failed: {ye}. Raw content: {content[:500]}...")
|
|
@@ -252,7 +274,7 @@ def fetch_openapi_spec(url: str, retries: int = 3) -> Optional[Dict]:
|
|
|
252
274
|
logger.debug(f"Parsed as JSON from {url}")
|
|
253
275
|
except json.JSONDecodeError:
|
|
254
276
|
try:
|
|
255
|
-
spec =
|
|
277
|
+
spec = yaml_load_safe(content)
|
|
256
278
|
logger.debug(f"Parsed as YAML from {url}")
|
|
257
279
|
except yaml.YAMLError as ye:
|
|
258
280
|
logger.error(f"YAML parsing failed: {ye}. Raw content: {content[:500]}...")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mcp-openapi-proxy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
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
|
|
@@ -31,10 +31,11 @@ Dynamic: license-file
|
|
|
31
31
|
- ✅ **Codex**, **Gemini**, **Qwen**, **Kilocode**, **opencode** — native tool calls over stdio
|
|
32
32
|
- ✅ **Vibe** — native discovery and read calls (writes were CLI-flaky, not a proxy issue)
|
|
33
33
|
- ✅ **Letta** — Cloud (via a remote streamable-HTTP MCP URL) and self-hosted (via stdio)
|
|
34
|
-
- ⚠️ **agy** — could not attach in headless mode; MCP tool enablement is interactive-only in agy (an agy limitation, not the proxy)
|
|
35
34
|
|
|
36
35
|
See the [client matrix](#client-matrix) for attach mechanisms, models, and exact results.
|
|
37
36
|
|
|
37
|
+
> 📄 Full write-up: [**Verification case study**](docs/verification-case-study.md) — what the proxy is, the API + client matrices, and every defect found & fixed.
|
|
38
|
+
|
|
38
39
|
**Prompts and resources are real now — including custom resources.** Both MCP surfaces are functional and tested: the `summarize_spec` / `whimsical_blog` prompts and the `spec_file` resource, plus a new `ADDITIONAL_RESOURCES` env var that serves your *own* use-case documents (e.g. a NetBox naming policy or an Asana project-layout guide) as MCP resources — see `examples/resources/`.
|
|
39
40
|
|
|
40
41
|
**Bug fixes (every one live-verified):**
|
|
@@ -63,7 +64,7 @@ Full environment-variable reference is in [Environment Variables](#environment-v
|
|
|
63
64
|
- [Environment Variables](#environment-variables)
|
|
64
65
|
- [Verified Clients & Live Results (2026-06-12)](#verified-clients--live-results-2026-06-12)
|
|
65
66
|
- [Tips](#tips)
|
|
66
|
-
- [Examples](#examples) — Glama, Fly.io, Render, Slack, GetZep, Virustotal, Notion, Asana, APIs.guru, NetBox, Box, WolframAlpha (collapsed)
|
|
67
|
+
- [Examples](#examples) — Glama, Fly.io, Render, Slack, GetZep, Virustotal, Notion, Asana, APIs.guru, NetBox, Box, WolframAlpha, WordPress (collapsed)
|
|
67
68
|
- [Troubleshooting](#troubleshooting)
|
|
68
69
|
- [License](#license)
|
|
69
70
|
|
|
@@ -174,15 +175,27 @@ The example configurations below were exercised against the live APIs, and the p
|
|
|
174
175
|
|
|
175
176
|
| Agent CLI | Model used (live test) | MCP attach mechanism | Tool calls | Prompts/Resources surfaced to model? |
|
|
176
177
|
|---|---|---|---|---|
|
|
177
|
-
| Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native |
|
|
178
|
-
| Gemini | Google OAuth free tier (CLI default model) | project `.gemini/settings.json` `mcpServers` | ✅ native |
|
|
179
|
-
| Qwen | `agent` group via local LiteLLM gateway | project `.qwen/settings.json` | ✅ native | ❌
|
|
180
|
-
| Kilocode | `kilo-auto/free` | global `settings/mcp_settings.json`, clean workspace | ✅ native | ❌ |
|
|
181
|
-
| opencode | `orchestration` group via local LiteLLM gateway | `~/.config/opencode/opencode.json` `mcp` | ✅ native |
|
|
182
|
-
| Vibe | `mistral-medium-3.5` | `~/.vibe/config.toml` `[[mcp_servers]]` | ✅ discovery + reads (writes flaky) | ❌ |
|
|
183
|
-
| agy | — | — | ❌ headless cannot enable MCP |
|
|
184
|
-
| letta cloud | Letta Cloud default | streamable-HTTP MCP URL (`/mcp add --transport http` + bearer) | ✅ remote (stdio rejected) |
|
|
185
|
-
| letta (self-hosted ≤0.11.x) | `agent` group via local LiteLLM gateway | stdio via `PUT /v1/tools/mcp/servers` | ✅ native |
|
|
178
|
+
| Codex | `gpt-5-codex` (OpenAI API) | `codex exec -c mcp_servers.*` | ✅ native | unknown ‡ |
|
|
179
|
+
| Gemini | Google OAuth free tier (CLI default model) | project `.gemini/settings.json` `mcpServers` | ✅ native | prompts: interactive slash only · resources: ❌ † |
|
|
180
|
+
| Qwen | `agent` group via local LiteLLM gateway | project `.qwen/settings.json` | ✅ native | prompts: ✅ (slash `/summarize_spec`) · resources: ❌ † |
|
|
181
|
+
| Kilocode | `kilo-auto/free` | global `settings/mcp_settings.json`, clean workspace | ✅ native | prompts: ❌ · resources: ✅ (`access_mcp_resource`) † |
|
|
182
|
+
| opencode | `orchestration` group via local LiteLLM gateway | `~/.config/opencode/opencode.json` `mcp` | ✅ native | unknown ‡ |
|
|
183
|
+
| Vibe | `mistral-medium-3.5` | `~/.vibe/config.toml` `[[mcp_servers]]` | ✅ discovery + reads (writes flaky) | prompts: ❌ · resources: ❌ (tools-only) † |
|
|
184
|
+
| agy | — | — | ❌ headless cannot enable MCP | n/a |
|
|
185
|
+
| letta cloud | Letta Cloud default | streamable-HTTP MCP URL (`/mcp add --transport http` + bearer) | ✅ remote (stdio rejected) | unknown ‡ |
|
|
186
|
+
| letta (self-hosted ≤0.11.x) | `agent` group via local LiteLLM gateway | stdio via `PUT /v1/tools/mcp/servers` | ✅ native | unknown ‡ |
|
|
187
|
+
|
|
188
|
+
> **Prompts/resources column — read this.** The original 0.2.0 sweep ran while the
|
|
189
|
+
> server advertised prompts/resources **only when `ENABLE_PROMPTS`/`ENABLE_RESOURCES`
|
|
190
|
+
> were set — they defaulted OFF.** Per the MCP spec a client won't call
|
|
191
|
+
> `prompts/list`/`resources/list` unless the capability is advertised, so those early
|
|
192
|
+
> results measured the *server's* default, not the clients. **All prior prompt/resource
|
|
193
|
+
> findings are therefore voided.** This release defaults advertising **on**.
|
|
194
|
+
>
|
|
195
|
+
> - **† re-verified 2026-06-14** with advertising on, against the real client binary. Genuine, uneven: **tools** everywhere; **prompts→model** Qwen (slash) & Gemini (interactive only); **resources→model** Kilocode. Vibe is tools-only (confirmed in source).
|
|
196
|
+
> - **‡ unknown** — not yet re-tested under advertising-on (opencode not installed here; Letta needs a running server + gateway; Codex parked). Prior ❌ values are not carried forward.
|
|
197
|
+
>
|
|
198
|
+
> Tool-calling (the `Tool calls` column) was unaffected by the advertising default and stands as originally verified.
|
|
186
199
|
|
|
187
200
|
Minimal sanitized configs per client (the no-auth Glama spec is used as the smallest working example; substitute your own spec URL and `$YOUR_KEY` as needed):
|
|
188
201
|
|
|
@@ -868,6 +881,55 @@ You can now use the MCP ecosystem to list and invoke WolframAlpha API tools. For
|
|
|
868
881
|
|
|
869
882
|
</details>
|
|
870
883
|
|
|
884
|
+
<details>
|
|
885
|
+
<summary><b>Letta (self-hosted + Cloud)</b> — agent platform; stdio for self-hosted, authenticated remote HTTP for Cloud</summary>
|
|
886
|
+
|
|
887
|
+
Letta runs agents on a server, so attachment differs by deployment:
|
|
888
|
+
|
|
889
|
+
- **Self-hosted Letta** accepts a **stdio** MCP server via `PUT /v1/tools/mcp/servers` — no network exposure.
|
|
890
|
+
- **Letta Cloud** rejects stdio and needs a **remote streamable-HTTP** MCP URL behind authentication.
|
|
891
|
+
|
|
892
|
+
Both were verified live (an agent autonomously called `get_v1_attributes` from the Glama spec through the proxy). Full setup for both paths, including the supergateway wrapper and the security note for the Cloud endpoint, is in [`examples/letta/README.md`](examples/letta/README.md).
|
|
893
|
+
|
|
894
|
+
</details>
|
|
895
|
+
|
|
896
|
+
<details>
|
|
897
|
+
<summary><b>WordPress Example</b> — publish to a real WordPress blog from any MCP agent</summary>
|
|
898
|
+
|
|
899
|
+
This example turns the WordPress REST API into MCP tools so an agent can **create, read, update, and trash blog posts**. It is the example behind a real-world demonstration: a fleet of agent CLIs published to the live blog at **[matthewhand.mywebcommunity.org](https://matthewhand.mywebcommunity.org/)** through this proxy — see the write-up [*“Turning Any API into Agent Tools”*](https://matthewhand.mywebcommunity.org/2026/06/12/turning-any-api-into-agent-tools-a-real-world-test-of-mcp-openapi-proxy/), itself posted via the proxy.
|
|
900
|
+
|
|
901
|
+
Example config — `examples/wordpress-claude_desktop_config.json`:
|
|
902
|
+
|
|
903
|
+
```json
|
|
904
|
+
{
|
|
905
|
+
"mcpServers": {
|
|
906
|
+
"wordpress": {
|
|
907
|
+
"command": "uvx",
|
|
908
|
+
"args": ["mcp-openapi-proxy"],
|
|
909
|
+
"env": {
|
|
910
|
+
"OPENAPI_SPEC_URL": "https://raw.githubusercontent.com/matthewhand/mcp-openapi-proxy/main/examples/wordpress-openapi.json",
|
|
911
|
+
"SERVER_URL_OVERRIDE": "https://your-site.example.com/wp-json",
|
|
912
|
+
"EXTRA_HEADERS": "Authorization: Basic BASE64_OF_USERNAME_COLON_APPLICATION_PASSWORD",
|
|
913
|
+
"IGNORE_SSL_TOOLS": "false",
|
|
914
|
+
"TOOL_WHITELIST": "/wp/v2/posts"
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
```
|
|
920
|
+
|
|
921
|
+
Exposed tools (from the bundled `examples/wordpress-openapi.json`): `get_wp_v2_posts`, `post_wp_v2_posts` (create), `get_wp_v2_posts_by_id`, `post_wp_v2_posts_by_id` (update), and `delete_wp_v2_posts_by_id` (trash) — the full post lifecycle.
|
|
922
|
+
|
|
923
|
+
**Auth — use a WordPress *Application Password*, not your login password.** WordPress REST rejects login passwords for Basic auth. In `wp-admin → Users → (your user) → Application Passwords`, create one, then base64-encode `username:application-password` for the `EXTRA_HEADERS` value:
|
|
924
|
+
|
|
925
|
+
```bash
|
|
926
|
+
printf 'myuser:xxxx xxxx xxxx xxxx xxxx xxxx' | base64 -w0
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
Tips: set `IGNORE_SSL_TOOLS=true` only if your host serves a self-signed/mismatched cert; give each agent its own (revocable) application password; keep `TOOL_WHITELIST=/wp/v2/posts` so the agent can touch only posts.
|
|
930
|
+
|
|
931
|
+
</details>
|
|
932
|
+
|
|
871
933
|
## Troubleshooting
|
|
872
934
|
|
|
873
935
|
### 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.
|
|
8
|
+
version = "0.3.0"
|
|
9
9
|
description = "MCP server for exposing OpenAPI specifications as MCP tools."
|
|
10
10
|
readme = "README.md"
|
|
11
11
|
authors = [
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
{mcp_openapi_proxy-0.2.0 → mcp_openapi_proxy-0.3.0}/mcp_openapi_proxy.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|