MyNextAdventure MCP Server
# MyNextAdventure MCP server
A remote [Model Context Protocol](https://modelcontextprotocol.io) server that lets an AI
assistant plan trips in [MyNextAdventure](https://mynextadventure.cloud) — create a trip,
lay out the itinerary, propose flights and hotels, add things to do, and share the result.
It runs as a stateless Cloudflare Worker and is a thin, authenticated proxy in front of the
MyNextAdventure public API: it holds no trip data of its own, and forwards every call to the
API under the caller's own API key.
- **Production:** `https://mcp.mynextadventure.cloud/mcp` (Streamable HTTP)
- **Upstream API:** `https://api.mynextadventure.cloud` (v1 public API)
## Authentication
Every request needs a MyNextAdventure API key. Create one in the app under
**Settings → API keys**, then send it as either:
- `Authorization: Bearer <api-key>` — what most MCP clients send, and what `mcp-remote` does, or
- `X-API-Key: <api-key>` — for clients that only let you set custom headers.
The worker forwards it to the API as `X-API-Key`. Requests without a key get a `401`.
Whatever the key can do, the assistant can do: keys are per-user, so the assistant only ever
sees that user's trips.
**Where your key lives.** The server is stateless: your key is read from the headers of each
request, used to make that one call to the MyNextAdventure API, and never stored — there is no
session storage and no Durable Object holding it between requests. It is never logged and is
not shared between requests or users. Revoke a key any time in the app under Settings → API keys.
## Tools
24 tools, covering the operations conversational planning actually needs. Each one is a
curated view over a MyNextAdventure API operation — see [Regenerating the tools](#regenerating-the-tools).
| Tool | What it does |
| --- | --- |
| `whoami` | Which account the API key belongs to (handy for checking the connection). |
| `list_trips` | List the user's trips; filter by status (`planning`, `ready`, `finished`, `cancelled`). |
| `get_trip` | One trip in full — variants, destinations, options, events. Needed before any edit. |
| `create_trip` | Create a new, empty trip. |
| `update_trip` | Rename, set a cover photo, or move the trip through its lifecycle. |
| `create_trip_share_link` | Create/refresh a public read-only share link. |
| `create_variant` | Add an alternative plan (dates live here: exact or flexible). |
| `duplicate_variant` | Copy a variant with everything in it. |
| `update_variant` | Rename a variant, change its dates or notes. |
| `select_variant` | Mark a variant as the chosen plan. |
| `add_destination` | Add a stop to a variant, in itinerary order. |
| `update_destination` | Change a stop's place name, dates or notes. |
| `reorder_destinations` | Reorder the stops in a variant. |
| `add_accommodation_option` | Propose a place to stay at a destination. |
| `add_transport_option` | Propose a way of getting to a destination. |
| `add_getting_around_option` | Propose local mobility (public transport, rental car, …). |
| `update_destination_option` | Edit an existing option of any of the three kinds. |
| `select_destination_option` | Select — or, with no `optionId`, deselect — an option. |
| `add_event` | Add an activity (museum, concert, restaurant, tour) to a variant. |
| `update_event` | Edit an activity. |
| `toggle_event` | Flip an activity between committed and "just an idea". |
| `delete_trip_item` | Permanently delete a variant, destination, option or event. Deleting a whole variant additionally requires `confirm: true`. |
| `list_goals` | List the user's travel goals / bucket list. |
| `add_goal` | Add a goal from a name or a URL. |
The server also ships MCP `instructions` describing the data model
(`trip → variants → destinations → options`, with events on the variant) and the usual
order of operations, so a client does not have to guess the workflow.
Not exposed yet, though the API supports them: collaborator access management and invites,
voting, collections, invite tokens, goal↔trip linking, API key management, and trip deletion.
### Tool annotations
Every tool publishes a human-readable `title` (also as `annotations.title`; set in
`scripts/tool-manifest.ts`, required by the Claude connectors directory) and all four MCP
[`ToolAnnotations`](https://modelcontextprotocol.io/specification/latest/server/tools)
hints on `tools/list`, so clients can tell at a glance what is safe to run unattended:
| Hint | True for |
| --- | --- |
| `readOnlyHint` | The 4 lookups: `whoami`, `list_trips`, `get_trip`, `list_goals`. The other 20 write. |
| `destructiveHint` | `delete_trip_item` only. Everything else adds records or edits fields in place. |
| `idempotentHint` | The `update_*` / `select_*` tools, `reorder_destinations`, `create_trip_share_link` (the API returns the existing token) and `delete_trip_item`. False for every `add_*`/`create_*` — a second call adds a second record — and for `toggle_event`, which flips a flag. |
| `openWorldHint` | The 8 tools whose request can reach a third-party API: the three read tools (Kinde profiles, Google Places, currency conversion) plus `add_accommodation_option`, `add_transport_option`, `add_event`, `update_destination_option` and `update_event`, which resolve a `googlePlaceId` through Google Places when one is supplied. The rest only touch MyNextAdventure storage. |
The spec's defaults are the pessimistic ones (`destructiveHint` and `openWorldHint` both
default to `true`), so the manifest requires all four to be stated per tool rather than
inherited. `test/annotations.test.ts` holds the expected classification tool by tool and
cross-checks `readOnlyHint` against the HTTP verb each tool actually issues.
## Connecting
### Claude Desktop
Settings → Developer → Edit Config, then add:
```json
{
"mcpServers": {
"mynextadventure": {
"command": "npx",
"args": [
"mcp-remote",
"https://mcp.mynextadventure.cloud/mcp",
"--header",
"Authorization: Bearer ${MNA_API_KEY}"
],
"env": { "MNA_API_KEY": "your-api-key" }
}
}
}
```
Restart Claude Desktop; the tools appear under the connector.
### claude.ai / Claude Code
Add a custom connector pointing at `https://mcp.mynextadventure.cloud/mcp` and set the
`Authorization: Bearer <api-key>` header. From Claude Code:
```bash
claude mcp add --transport http mynextadventure https://mcp.mynextadventure.cloud/mcp \
--header "Authorization: Bearer <api-key>"
```
### ChatGPT
Settings → Connectors → Add custom connector, MCP server URL
`https://mcp.mynextadventure.cloud/mcp`, authentication "custom header" with
`X-API-Key: <api-key>` (or `Authorization: Bearer <api-key>`).
### Any other MCP client
Streamable HTTP at `/mcp`. `GET /` returns a small JSON health document with the endpoint
and tool count.
### Local stdio
The same tools over stdio, for clients that launch a subprocess, with the key in
`MNA_API_KEY`. Glama's build check runs this behind `mcp-proxy`.
```bash
npm ci
MNA_API_KEY=<api-key> npm run start:stdio
```
## Development
```bash
npm install
npm run dev # wrangler dev on http://localhost:8787
npm test # vitest: request building, schemas, generator freshness
npm run type-check # tsc --noEmit
npm run generate:tools # regenerate src/generated/tools.ts from the OpenAPI spec
npx tsx scripts/smoke.ts # drive the MCP protocol against localhost:8787
```
`scripts/smoke.ts` performs a real handshake (initialize → `tools/list` → one `tools/call`),
reading the API key from `$MNA_API_KEY` or `~/.config/mna/credentials`. It refuses to call
mutating tools unless `--force` is passed. To exercise write tools without touching real
data, point the worker at a local mock:
```bash
npx wrangler dev --var MNA_API_BASE_URL:http://localhost:8899
```
### Regenerating the tools
Tool schemas are **generated**, not hand-written:
```
spec/openapi-v1.json (the API's OpenAPI snapshot)
+
scripts/tool-manifest.ts (which operations become tools; names, descriptions, annotations)
↓ scripts/generator.ts
src/generated/tools.ts (committed; zod input schemas, annotations + request builders)
```
To add or change a tool, edit `scripts/tool-manifest.ts` and run `npm run generate:tools`.
Never edit `src/generated/tools.ts` by hand — a test fails if it drifts from the spec.
A manifest entry names one API operation, or groups several body-less operations behind a
discriminator argument (`delete_trip_item`'s `target`, `select_destination_option`'s
`action`) to keep the tool count down. Path params can be renamed (`id` → `tripId`), query
params can be narrowed to enums, and descriptions can be overridden — everything else,
including the request-body schemas, comes from the spec.
Each entry must also carry `annotations` (all four MCP hints; see
[Tool annotations](#tool-annotations)). TypeScript enforces this, so a new tool cannot be
added without classifying it, and `test/annotations.test.ts` checks the result against an
expected table.
### Deployment
`npm run deploy` (wrangler) — production only, so it is gated on a maintainer; CI never
deploys this worker.
## Development
```
npm ci
npm run type-check
npm test # includes a drift test: src/generated/tools.ts must match the generator output
npm run dev # wrangler dev
```
Tools are generated from the vendored OpenAPI snapshot in `spec/openapi-v1.json`
(canonical live spec: https://api.mynextadventure.cloud/api/v1/openapi.json) via
`scripts/generate-tools.ts`. Regenerate after refreshing the snapshot.
This repo is the deployable source for `mcp.mynextadventure.cloud`. Development also
happens in the My Next Adventure monorepo and is synced here on release. Issues and
PRs welcome.
## License
MIT
TDQS
Scored across 25 tools
Each tool targets a distinct resource and action, with explicit disambiguation between similar families such as add_transport_option (between destinations) versus add_getting_around_option (local mobility), and toggle_event versus delete_trip_item. Polymorphic tools like update_destination_option and select_destination_option use a kind argument but their descriptions clearly explain the boundaries.
The set overwhelmingly follows a predictable verb_noun snake_case pattern (list_trips, create_variant, add_accommodation_option, update_event). The only deviations are whoami and connector_info, which are informational tools rather than CRUD operations, so the convention is still easy to follow.
Twenty-five tools is on the high side, but the trip-planning domain has several nested resources (trips, variants, destinations, three option kinds, events, goals) that justify most tools. Some option-add tools could potentially be consolidated, but each still earns its place.
Core lifecycle coverage is strong: trips, variants, destinations, options, and events all have create/read/update/select/delete paths. Minor gaps remain, such as no direct delete_trip operation, no revoke for share links, and goals only supporting list/add without update or delete.