wunder-mobility-mcp 0.1.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.
- wunder_mobility_mcp-0.1.0/LICENSE +21 -0
- wunder_mobility_mcp-0.1.0/PKG-INFO +243 -0
- wunder_mobility_mcp-0.1.0/README.md +214 -0
- wunder_mobility_mcp-0.1.0/pyproject.toml +50 -0
- wunder_mobility_mcp-0.1.0/setup.cfg +4 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mcp/__init__.py +0 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mcp/app.py +28 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mcp/catalog.py +109 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mcp/docs_links.py +77 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mcp/docs_source.py +112 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mcp/openapi.py +97 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mcp/search.py +125 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mcp/server.py +153 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mcp/stdio.py +19 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mobility_mcp.egg-info/PKG-INFO +243 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mobility_mcp.egg-info/SOURCES.txt +24 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mobility_mcp.egg-info/dependency_links.txt +1 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mobility_mcp.egg-info/entry_points.txt +2 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mobility_mcp.egg-info/requires.txt +11 -0
- wunder_mobility_mcp-0.1.0/src/wunder_mobility_mcp.egg-info/top_level.txt +1 -0
- wunder_mobility_mcp-0.1.0/tests/test_catalog.py +47 -0
- wunder_mobility_mcp-0.1.0/tests/test_docs_links.py +33 -0
- wunder_mobility_mcp-0.1.0/tests/test_docs_source.py +14 -0
- wunder_mobility_mcp-0.1.0/tests/test_mcp_tools.py +122 -0
- wunder_mobility_mcp-0.1.0/tests/test_openapi.py +31 -0
- wunder_mobility_mcp-0.1.0/tests/test_search.py +94 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Wunder Mobility GmbH
|
|
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,243 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: wunder-mobility-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server exposing the Wunder Mobility public API documentation
|
|
5
|
+
Author: Wunder Mobility
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Wunder-Mobility/public-api-mcp
|
|
8
|
+
Project-URL: Documentation, https://docs.wundermobility.services
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/Wunder-Mobility/public-api-mcp/issues
|
|
10
|
+
Keywords: mcp,wunder,mobility,api,documentation
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.12
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: certifi>=2024.0.0
|
|
19
|
+
Requires-Dist: fastmcp>=3.3.1
|
|
20
|
+
Requires-Dist: platformdirs>=4.0.0
|
|
21
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
22
|
+
Requires-Dist: rank-bm25>=0.2.2
|
|
23
|
+
Requires-Dist: uvicorn[standard]>=0.32.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=8.3.0; extra == "dev"
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.24.0; extra == "dev"
|
|
27
|
+
Requires-Dist: httpx>=0.27.0; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# Wunder Mobility MCP
|
|
31
|
+
|
|
32
|
+
> **Give your AI coding assistant live knowledge of the Wunder Mobility API.**
|
|
33
|
+
|
|
34
|
+
This is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that puts the full Wunder Mobility public API documentation — every guide, every endpoint, every request/response schema — directly inside your AI assistant.
|
|
35
|
+
|
|
36
|
+
Instead of copy-pasting docs or hoping your assistant's training data is up to date, you can just ask:
|
|
37
|
+
|
|
38
|
+
> *"What endpoints do I need to build a payment flow for my users?"*
|
|
39
|
+
> *"Show me the full spec for creating a booking."*
|
|
40
|
+
> *"How does vehicle discovery work in the User API?"*
|
|
41
|
+
|
|
42
|
+
…and get back ranked, cited answers with deep-links straight to [docs.wundermobility.services](https://docs.wundermobility.services/).
|
|
43
|
+
|
|
44
|
+
Works with **Claude Code**, **Cursor**, **Windsurf**, **VS Code with Copilot**, and any other MCP-compatible client.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## What it does
|
|
49
|
+
|
|
50
|
+
The MCP server exposes four tools your assistant can call automatically:
|
|
51
|
+
|
|
52
|
+
| Tool | What your assistant uses it for |
|
|
53
|
+
|---|---|
|
|
54
|
+
| `search_api_docs(query)` | *"Find the right endpoint or guide for this flow"* — full-text search across all guides and OpenAPI specs. |
|
|
55
|
+
| `list_endpoints(api, tag?)` | *"What endpoints exist in the User API under Payments?"* — browse by API namespace and tag. |
|
|
56
|
+
| `get_endpoint_spec(operation_id)` | *"Give me the exact request/response schema for this endpoint."* — returns the raw OpenAPI operation object. |
|
|
57
|
+
| `get_guide(slug)` | *"Explain how authentication works."* — returns the full markdown guide. |
|
|
58
|
+
|
|
59
|
+
Every result includes a Stoplight deep-link so you (or your assistant) can open the same content in a browser.
|
|
60
|
+
|
|
61
|
+
Docs are pulled **live from GitHub** on every startup and cached locally — so your assistant always has the current API, not a stale snapshot.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
### Option A — `uvx` (recommended, no cloning needed)
|
|
68
|
+
|
|
69
|
+
[`uvx`](https://docs.astral.sh/uv/) runs the package directly from PyPI — no Python environment setup, no cloning.
|
|
70
|
+
|
|
71
|
+
**Install `uv` if you don't have it:**
|
|
72
|
+
```bash
|
|
73
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux
|
|
74
|
+
# or: brew install uv
|
|
75
|
+
# or: winget install astral-sh.uv # Windows
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Claude Code:**
|
|
79
|
+
```bash
|
|
80
|
+
claude mcp add wunder-api uvx wunder-mobility-mcp
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Cursor** — add to your MCP settings (`~/.cursor/mcp.json` or via *Settings → MCP*):
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"mcpServers": {
|
|
87
|
+
"wunder-api": {
|
|
88
|
+
"command": "uvx",
|
|
89
|
+
"args": ["wunder-mobility-mcp"]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Windsurf** — add to `~/.codeium/windsurf/mcp_config.json`:
|
|
96
|
+
```json
|
|
97
|
+
{
|
|
98
|
+
"mcpServers": {
|
|
99
|
+
"wunder-api": {
|
|
100
|
+
"command": "uvx",
|
|
101
|
+
"args": ["wunder-mobility-mcp"]
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**Any client that accepts a command:** use `uvx` as the command and `["wunder-mobility-mcp"]` as the args.
|
|
108
|
+
|
|
109
|
+
On first start, the server fetches the latest docs from GitHub (~5–10s) and caches them locally. Every subsequent start is instant.
|
|
110
|
+
|
|
111
|
+
### Option B — clone and run
|
|
112
|
+
|
|
113
|
+
If you'd rather run from source:
|
|
114
|
+
|
|
115
|
+
**Prerequisites:** Python 3.12+, git.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git clone https://github.com/Wunder-Mobility/public-api-mcp.git
|
|
119
|
+
cd public-api-mcp
|
|
120
|
+
make install
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Claude Code:**
|
|
124
|
+
```bash
|
|
125
|
+
claude mcp add wunder-api "$(pwd)/.venv/bin/wunder-mobility-mcp"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**Cursor:**
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"mcpServers": {
|
|
132
|
+
"wunder-api": {
|
|
133
|
+
"command": "/path/to/public-api-mcp/.venv/bin/wunder-mobility-mcp"
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Replace `/path/to/public-api-mcp` with your actual clone path (`pwd` inside the repo).
|
|
140
|
+
|
|
141
|
+
### Option C — hosted URL (no local install at all)
|
|
142
|
+
|
|
143
|
+
Point your client at our hosted server — nothing to install or run locally.
|
|
144
|
+
|
|
145
|
+
**Claude Code:**
|
|
146
|
+
```bash
|
|
147
|
+
claude mcp add wunder-api --transport http https://mcp.wundermobility.services/mcp
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Cursor / Windsurf / other clients:**
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"mcpServers": {
|
|
154
|
+
"wunder-api": {
|
|
155
|
+
"url": "https://mcp.wundermobility.services/mcp"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Verify it's working
|
|
164
|
+
|
|
165
|
+
Once connected, open your assistant and try:
|
|
166
|
+
|
|
167
|
+
- *"Search the Wunder API docs for payment flow"*
|
|
168
|
+
- *"List all endpoints in the Wunder operations API"*
|
|
169
|
+
- *"Get the guide for user authentication"*
|
|
170
|
+
|
|
171
|
+
You should see the tools being called and results coming back with Stoplight links.
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## How docs stay current
|
|
176
|
+
|
|
177
|
+
The docs live in [`Wunder-Mobility/public-api-docs`](https://github.com/Wunder-Mobility/public-api-docs) — a public GitHub repo. On every server startup, this MCP downloads the latest tarball from that repo and caches it locally. No manual sync, no stale snapshots.
|
|
178
|
+
|
|
179
|
+
**To get the latest docs locally:** restart your MCP server (or restart your editor). The cache lives at:
|
|
180
|
+
|
|
181
|
+
| OS | Cache path |
|
|
182
|
+
|---|---|
|
|
183
|
+
| macOS | `~/Library/Caches/wunder-mobility-mcp/public-api-docs` |
|
|
184
|
+
| Linux | `~/.cache/wunder-mobility-mcp/public-api-docs` |
|
|
185
|
+
| Windows | `%LOCALAPPDATA%\wunder-mobility-mcp\Cache\public-api-docs` |
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Development
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
make install # create .venv, install package + dev deps
|
|
193
|
+
make test # run the test suite (requires network on first run)
|
|
194
|
+
make run # start HTTP server on http://localhost:8080/mcp
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
> `make test` fetches live docs from GitHub on the first run (~10s). Subsequent runs use the local cache.
|
|
198
|
+
|
|
199
|
+
### Project structure
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
src/wunder_mcp/
|
|
203
|
+
docs_source.py # fetches & caches the docs tarball from GitHub
|
|
204
|
+
catalog.py # builds the in-memory guide + endpoint catalog
|
|
205
|
+
docs_links.py # parses toc.json and builds Stoplight deep-link URLs
|
|
206
|
+
openapi.py # loads and indexes OpenAPI specs
|
|
207
|
+
search.py # BM25 search index
|
|
208
|
+
server.py # MCP tool definitions (FastMCP)
|
|
209
|
+
app.py # HTTP entrypoint (uvicorn)
|
|
210
|
+
stdio.py # stdio entrypoint
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### Publishing a new version
|
|
214
|
+
|
|
215
|
+
1. Bump `version` in `pyproject.toml`
|
|
216
|
+
2. Commit and tag: `git tag v0.x.x && git push --tags`
|
|
217
|
+
3. The `publish.yml` workflow builds and uploads to PyPI automatically via trusted publishing
|
|
218
|
+
|
|
219
|
+
**One-time PyPI setup** (before the first publish): go to [pypi.org](https://pypi.org) → your project → Publishing → add a trusted publisher with owner `Wunder-Mobility`, repo `public-api-mcp`, workflow `publish.yml`, environment `pypi`.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Deploying your own hosted instance
|
|
224
|
+
|
|
225
|
+
The server runs on AWS Lambda behind a Function URL. See `template.yaml` for the SAM definition.
|
|
226
|
+
|
|
227
|
+
**Prerequisites:** AWS CLI, SAM CLI (`brew install aws-sam-cli`), Python 3.12.
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
sam build
|
|
231
|
+
sam deploy --guided # first time; saves config to samconfig.toml
|
|
232
|
+
sam build && sam deploy # subsequent deploys
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Doc changes don't require a redeploy — the Lambda fetches the latest docs on every cold start.
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Roadmap
|
|
240
|
+
|
|
241
|
+
- **v0.1 (current):** read-only docs search, public and unauthenticated, docs fetched live on startup.
|
|
242
|
+
- **v0.2:** per-tenant API key auth + usage telemetry.
|
|
243
|
+
- **v1.0:** tools that call the live Wunder API directly (e.g. `create_test_booking`), gated by a customer API key.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# Wunder Mobility MCP
|
|
2
|
+
|
|
3
|
+
> **Give your AI coding assistant live knowledge of the Wunder Mobility API.**
|
|
4
|
+
|
|
5
|
+
This is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that puts the full Wunder Mobility public API documentation — every guide, every endpoint, every request/response schema — directly inside your AI assistant.
|
|
6
|
+
|
|
7
|
+
Instead of copy-pasting docs or hoping your assistant's training data is up to date, you can just ask:
|
|
8
|
+
|
|
9
|
+
> *"What endpoints do I need to build a payment flow for my users?"*
|
|
10
|
+
> *"Show me the full spec for creating a booking."*
|
|
11
|
+
> *"How does vehicle discovery work in the User API?"*
|
|
12
|
+
|
|
13
|
+
…and get back ranked, cited answers with deep-links straight to [docs.wundermobility.services](https://docs.wundermobility.services/).
|
|
14
|
+
|
|
15
|
+
Works with **Claude Code**, **Cursor**, **Windsurf**, **VS Code with Copilot**, and any other MCP-compatible client.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
The MCP server exposes four tools your assistant can call automatically:
|
|
22
|
+
|
|
23
|
+
| Tool | What your assistant uses it for |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `search_api_docs(query)` | *"Find the right endpoint or guide for this flow"* — full-text search across all guides and OpenAPI specs. |
|
|
26
|
+
| `list_endpoints(api, tag?)` | *"What endpoints exist in the User API under Payments?"* — browse by API namespace and tag. |
|
|
27
|
+
| `get_endpoint_spec(operation_id)` | *"Give me the exact request/response schema for this endpoint."* — returns the raw OpenAPI operation object. |
|
|
28
|
+
| `get_guide(slug)` | *"Explain how authentication works."* — returns the full markdown guide. |
|
|
29
|
+
|
|
30
|
+
Every result includes a Stoplight deep-link so you (or your assistant) can open the same content in a browser.
|
|
31
|
+
|
|
32
|
+
Docs are pulled **live from GitHub** on every startup and cached locally — so your assistant always has the current API, not a stale snapshot.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Install
|
|
37
|
+
|
|
38
|
+
### Option A — `uvx` (recommended, no cloning needed)
|
|
39
|
+
|
|
40
|
+
[`uvx`](https://docs.astral.sh/uv/) runs the package directly from PyPI — no Python environment setup, no cloning.
|
|
41
|
+
|
|
42
|
+
**Install `uv` if you don't have it:**
|
|
43
|
+
```bash
|
|
44
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh # macOS / Linux
|
|
45
|
+
# or: brew install uv
|
|
46
|
+
# or: winget install astral-sh.uv # Windows
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Claude Code:**
|
|
50
|
+
```bash
|
|
51
|
+
claude mcp add wunder-api uvx wunder-mobility-mcp
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Cursor** — add to your MCP settings (`~/.cursor/mcp.json` or via *Settings → MCP*):
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"mcpServers": {
|
|
58
|
+
"wunder-api": {
|
|
59
|
+
"command": "uvx",
|
|
60
|
+
"args": ["wunder-mobility-mcp"]
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Windsurf** — add to `~/.codeium/windsurf/mcp_config.json`:
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"mcpServers": {
|
|
70
|
+
"wunder-api": {
|
|
71
|
+
"command": "uvx",
|
|
72
|
+
"args": ["wunder-mobility-mcp"]
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
**Any client that accepts a command:** use `uvx` as the command and `["wunder-mobility-mcp"]` as the args.
|
|
79
|
+
|
|
80
|
+
On first start, the server fetches the latest docs from GitHub (~5–10s) and caches them locally. Every subsequent start is instant.
|
|
81
|
+
|
|
82
|
+
### Option B — clone and run
|
|
83
|
+
|
|
84
|
+
If you'd rather run from source:
|
|
85
|
+
|
|
86
|
+
**Prerequisites:** Python 3.12+, git.
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
git clone https://github.com/Wunder-Mobility/public-api-mcp.git
|
|
90
|
+
cd public-api-mcp
|
|
91
|
+
make install
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Claude Code:**
|
|
95
|
+
```bash
|
|
96
|
+
claude mcp add wunder-api "$(pwd)/.venv/bin/wunder-mobility-mcp"
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**Cursor:**
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"mcpServers": {
|
|
103
|
+
"wunder-api": {
|
|
104
|
+
"command": "/path/to/public-api-mcp/.venv/bin/wunder-mobility-mcp"
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Replace `/path/to/public-api-mcp` with your actual clone path (`pwd` inside the repo).
|
|
111
|
+
|
|
112
|
+
### Option C — hosted URL (no local install at all)
|
|
113
|
+
|
|
114
|
+
Point your client at our hosted server — nothing to install or run locally.
|
|
115
|
+
|
|
116
|
+
**Claude Code:**
|
|
117
|
+
```bash
|
|
118
|
+
claude mcp add wunder-api --transport http https://mcp.wundermobility.services/mcp
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Cursor / Windsurf / other clients:**
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"mcpServers": {
|
|
125
|
+
"wunder-api": {
|
|
126
|
+
"url": "https://mcp.wundermobility.services/mcp"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Verify it's working
|
|
135
|
+
|
|
136
|
+
Once connected, open your assistant and try:
|
|
137
|
+
|
|
138
|
+
- *"Search the Wunder API docs for payment flow"*
|
|
139
|
+
- *"List all endpoints in the Wunder operations API"*
|
|
140
|
+
- *"Get the guide for user authentication"*
|
|
141
|
+
|
|
142
|
+
You should see the tools being called and results coming back with Stoplight links.
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## How docs stay current
|
|
147
|
+
|
|
148
|
+
The docs live in [`Wunder-Mobility/public-api-docs`](https://github.com/Wunder-Mobility/public-api-docs) — a public GitHub repo. On every server startup, this MCP downloads the latest tarball from that repo and caches it locally. No manual sync, no stale snapshots.
|
|
149
|
+
|
|
150
|
+
**To get the latest docs locally:** restart your MCP server (or restart your editor). The cache lives at:
|
|
151
|
+
|
|
152
|
+
| OS | Cache path |
|
|
153
|
+
|---|---|
|
|
154
|
+
| macOS | `~/Library/Caches/wunder-mobility-mcp/public-api-docs` |
|
|
155
|
+
| Linux | `~/.cache/wunder-mobility-mcp/public-api-docs` |
|
|
156
|
+
| Windows | `%LOCALAPPDATA%\wunder-mobility-mcp\Cache\public-api-docs` |
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Development
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
make install # create .venv, install package + dev deps
|
|
164
|
+
make test # run the test suite (requires network on first run)
|
|
165
|
+
make run # start HTTP server on http://localhost:8080/mcp
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> `make test` fetches live docs from GitHub on the first run (~10s). Subsequent runs use the local cache.
|
|
169
|
+
|
|
170
|
+
### Project structure
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
src/wunder_mcp/
|
|
174
|
+
docs_source.py # fetches & caches the docs tarball from GitHub
|
|
175
|
+
catalog.py # builds the in-memory guide + endpoint catalog
|
|
176
|
+
docs_links.py # parses toc.json and builds Stoplight deep-link URLs
|
|
177
|
+
openapi.py # loads and indexes OpenAPI specs
|
|
178
|
+
search.py # BM25 search index
|
|
179
|
+
server.py # MCP tool definitions (FastMCP)
|
|
180
|
+
app.py # HTTP entrypoint (uvicorn)
|
|
181
|
+
stdio.py # stdio entrypoint
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Publishing a new version
|
|
185
|
+
|
|
186
|
+
1. Bump `version` in `pyproject.toml`
|
|
187
|
+
2. Commit and tag: `git tag v0.x.x && git push --tags`
|
|
188
|
+
3. The `publish.yml` workflow builds and uploads to PyPI automatically via trusted publishing
|
|
189
|
+
|
|
190
|
+
**One-time PyPI setup** (before the first publish): go to [pypi.org](https://pypi.org) → your project → Publishing → add a trusted publisher with owner `Wunder-Mobility`, repo `public-api-mcp`, workflow `publish.yml`, environment `pypi`.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Deploying your own hosted instance
|
|
195
|
+
|
|
196
|
+
The server runs on AWS Lambda behind a Function URL. See `template.yaml` for the SAM definition.
|
|
197
|
+
|
|
198
|
+
**Prerequisites:** AWS CLI, SAM CLI (`brew install aws-sam-cli`), Python 3.12.
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
sam build
|
|
202
|
+
sam deploy --guided # first time; saves config to samconfig.toml
|
|
203
|
+
sam build && sam deploy # subsequent deploys
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Doc changes don't require a redeploy — the Lambda fetches the latest docs on every cold start.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Roadmap
|
|
211
|
+
|
|
212
|
+
- **v0.1 (current):** read-only docs search, public and unauthenticated, docs fetched live on startup.
|
|
213
|
+
- **v0.2:** per-tenant API key auth + usage telemetry.
|
|
214
|
+
- **v1.0:** tools that call the live Wunder API directly (e.g. `create_test_booking`), gated by a customer API key.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "wunder-mobility-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server exposing the Wunder Mobility public API documentation"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
authors = [{ name = "Wunder Mobility" }]
|
|
8
|
+
license = { text = "MIT" }
|
|
9
|
+
keywords = ["mcp", "wunder", "mobility", "api", "documentation"]
|
|
10
|
+
classifiers = [
|
|
11
|
+
"Development Status :: 4 - Beta",
|
|
12
|
+
"Intended Audience :: Developers",
|
|
13
|
+
"License :: OSI Approved :: MIT License",
|
|
14
|
+
"Programming Language :: Python :: 3.12",
|
|
15
|
+
]
|
|
16
|
+
dependencies = [
|
|
17
|
+
"certifi>=2024.0.0",
|
|
18
|
+
"fastmcp>=3.3.1",
|
|
19
|
+
"platformdirs>=4.0.0",
|
|
20
|
+
"pyyaml>=6.0.2",
|
|
21
|
+
"rank-bm25>=0.2.2",
|
|
22
|
+
"uvicorn[standard]>=0.32.0",
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/Wunder-Mobility/public-api-mcp"
|
|
27
|
+
Documentation = "https://docs.wundermobility.services"
|
|
28
|
+
"Bug Tracker" = "https://github.com/Wunder-Mobility/public-api-mcp/issues"
|
|
29
|
+
|
|
30
|
+
[project.optional-dependencies]
|
|
31
|
+
dev = [
|
|
32
|
+
"pytest>=8.3.0",
|
|
33
|
+
"pytest-asyncio>=0.24.0",
|
|
34
|
+
"httpx>=0.27.0",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[project.scripts]
|
|
38
|
+
wunder-mobility-mcp = "wunder_mcp.stdio:main"
|
|
39
|
+
|
|
40
|
+
[build-system]
|
|
41
|
+
requires = ["setuptools>=68"]
|
|
42
|
+
build-backend = "setuptools.build_meta"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["src"]
|
|
46
|
+
|
|
47
|
+
[tool.pytest.ini_options]
|
|
48
|
+
asyncio_mode = "auto"
|
|
49
|
+
testpaths = ["tests"]
|
|
50
|
+
pythonpath = ["src"]
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Entrypoint for both local dev (uvicorn) and Lambda (via Lambda Web Adapter).
|
|
2
|
+
|
|
3
|
+
Local dev: `python -m wunder_mcp.app` (serves on http://localhost:8080/mcp)
|
|
4
|
+
Lambda: Lambda Web Adapter forwards Function URL invocations to this same
|
|
5
|
+
uvicorn process inside the container.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
import uvicorn
|
|
13
|
+
|
|
14
|
+
from .server import build_server
|
|
15
|
+
|
|
16
|
+
_server = build_server()
|
|
17
|
+
# ASGI app for HTTP/SSE transport at path /mcp.
|
|
18
|
+
app = _server.http_app(path="/mcp")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main() -> None:
|
|
22
|
+
host = os.environ.get("HOST", "0.0.0.0")
|
|
23
|
+
port = int(os.environ.get("PORT", "8080"))
|
|
24
|
+
uvicorn.run(app, host=host, port=port, log_level=os.environ.get("LOG_LEVEL", "info"))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == "__main__":
|
|
28
|
+
main()
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Builds the in-memory catalog of guides + endpoints, ready to serve.
|
|
2
|
+
|
|
3
|
+
A Catalog is loaded once at module init and reused across all requests.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .docs_links import GuideEntry, endpoint_url, load_toc, to_kebab
|
|
13
|
+
from .openapi import Operation, load_operations
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Guide:
|
|
18
|
+
"""One markdown guide with its rendered Stoplight URL and full body."""
|
|
19
|
+
|
|
20
|
+
title: str
|
|
21
|
+
slug: str
|
|
22
|
+
file_stem: str # filename without extension
|
|
23
|
+
body: str
|
|
24
|
+
url: str
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def search_text(self) -> str:
|
|
28
|
+
return f"{self.title}\n{self.body}"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class Catalog:
|
|
33
|
+
"""The full set of guides and endpoints, indexed for lookup."""
|
|
34
|
+
|
|
35
|
+
guides: list[Guide] = field(default_factory=list)
|
|
36
|
+
operations: list[Operation] = field(default_factory=list)
|
|
37
|
+
_guides_by_slug: dict[str, Guide] = field(default_factory=dict)
|
|
38
|
+
_guides_by_stem: dict[str, Guide] = field(default_factory=dict)
|
|
39
|
+
_ops_by_id: dict[str, Operation] = field(default_factory=dict)
|
|
40
|
+
|
|
41
|
+
def __post_init__(self) -> None:
|
|
42
|
+
self._guides_by_slug = {g.slug: g for g in self.guides}
|
|
43
|
+
self._guides_by_stem = {g.file_stem: g for g in self.guides}
|
|
44
|
+
self._ops_by_id = {op.operation_id: op for op in self.operations}
|
|
45
|
+
|
|
46
|
+
def guide(self, ref: str) -> Guide | None:
|
|
47
|
+
return self._guides_by_slug.get(ref) or self._guides_by_stem.get(ref)
|
|
48
|
+
|
|
49
|
+
def operation(self, operation_id: str) -> Operation | None:
|
|
50
|
+
return self._ops_by_id.get(operation_id)
|
|
51
|
+
|
|
52
|
+
def operations_for(self, api: str | None = None, tag: str | None = None) -> list[Operation]:
|
|
53
|
+
results = self.operations
|
|
54
|
+
if api:
|
|
55
|
+
results = [op for op in results if op.api == api]
|
|
56
|
+
if tag:
|
|
57
|
+
results = [op for op in results if tag in op.tags]
|
|
58
|
+
return results
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
_HEADING_RE = re.compile(r"^# (.+)$", re.MULTILINE)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _extract_title(file_stem: str, body: str) -> str:
|
|
65
|
+
match = _HEADING_RE.search(body)
|
|
66
|
+
if match:
|
|
67
|
+
return match.group(1).strip()
|
|
68
|
+
# Fall back to the file stem in Title Case.
|
|
69
|
+
return file_stem.replace("-", " ").title()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def load_catalog(vendor_dir: Path) -> Catalog:
|
|
73
|
+
docs_dir = vendor_dir / "docs"
|
|
74
|
+
ref_dir = vendor_dir / "reference"
|
|
75
|
+
toc_path = vendor_dir / "toc.json"
|
|
76
|
+
|
|
77
|
+
if not docs_dir.exists() or not ref_dir.exists() or not toc_path.exists():
|
|
78
|
+
raise FileNotFoundError(
|
|
79
|
+
f"Docs not found in {vendor_dir}. Check your network connection or WUNDER_MCP_CACHE_DIR."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
toc_entries: list[GuideEntry] = load_toc(toc_path)
|
|
83
|
+
guides: list[Guide] = []
|
|
84
|
+
seen: set[str] = set()
|
|
85
|
+
for entry in toc_entries:
|
|
86
|
+
md_path = docs_dir / entry.filename
|
|
87
|
+
if not md_path.exists() or entry.slug in seen:
|
|
88
|
+
continue
|
|
89
|
+
seen.add(entry.slug)
|
|
90
|
+
body = md_path.read_text()
|
|
91
|
+
guides.append(
|
|
92
|
+
Guide(
|
|
93
|
+
title=_extract_title(entry.file_stem, body) or entry.title,
|
|
94
|
+
slug=entry.slug,
|
|
95
|
+
file_stem=entry.file_stem,
|
|
96
|
+
body=body,
|
|
97
|
+
url=entry.url,
|
|
98
|
+
)
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
operations: list[Operation] = load_operations(ref_dir)
|
|
102
|
+
return Catalog(guides=guides, operations=operations)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def stoplight_endpoint_url(operation: Operation) -> str | None:
|
|
106
|
+
"""Return Stoplight URL for an endpoint, or None if the API namespace is unknown."""
|
|
107
|
+
if not operation.api:
|
|
108
|
+
return None
|
|
109
|
+
return endpoint_url(operation.api, to_kebab(operation.operation_id))
|