MCP Server
Connect AI assistants to Charter Boats via the Model Context Protocol
The Charter Boats MCP server lets Claude, ChatGPT, Cursor, and any MCP-compatible AI assistant search boats, locations, POIs, routes, trips, and content -- with rich HTML widgets for visual boat and destination cards.
Endpoint: https://charter.boats/mcp
Install / quickstart page: open https://charter.boats/mcp in a browser.
Transport: Streamable HTTP (JSON-RPC 2.0).
Auth: None. The server does not pass request headers on to the API, so an API key sent to /mcp has no effect — MCP calls are neither attributed to an account nor affiliate-tagged. See also Rate limits.
Spec: MCP Apps 2026-01-26.
Deployment
The server is a Cloudflare Worker routed at charter.boats/mcp. It proxies tool calls to the Charter Boats AI API (https://charter.boats/api/ai/*) and serves two HTML widgets as MCP App resources.
A single /mcp URL handles three things:
GETwithAccept: text/html→ install / quickstart page (HTML)GETwithAccept: application/json→ server info JSONPOST→ MCP JSON-RPC
Tools
Ten read-only tools. Each returns the underlying API response as structuredContent (which the widgets render) plus a short text summary for the model. A search that finds nothing still returns the API's zero-results block — no_results (searched, why, try_next) and note — in structuredContent.
Search
| Tool | Description | Widget |
|---|---|---|
search_boats | Boats by location, type, dates, capacity, budget. Returns up to 8 with prices, images, booking links. | ✓ boats |
search_locations | Marinas, harbours and anchorages three ways — see below. Up to 10 with boat counts. | ✓ locations |
search_pois | Restaurants, fuel, groceries, etc. near a destination. Up to 10. | — |
search_routes | Sailing passages between destinations. Up to 5. | — |
search_trips | Multi-day curated itineraries. Up to 5. | — |
search_content | Articles, guides, FAQs (semantic search). Up to 5. | — |
search_locations answers three different questions, picked by which inputs you send:
- By name —
q(min 2 characters): "Lefkada", "Dubrovnik". - By popularity in an area —
region(a sailing region as guests say it: Dalmatia, the Ionian, the Cyclades…),country,city, optionallytype(marina,harbour,anchorage,bay,mooring,spot— the same words the results carry back,spotbeing an unclassified stop) andmonth(1-12, default the current month). Ranked bysort:popularity(default — observed vessel traffic for that month),boats(charter fleet size) orrating. - By reach from a place —
from(a location id or slug from an earlier result) pluswithin_nm(10,20,35,55or100; default 35): the places boats were recorded sailing to from there, most-travelled first, with estimated distance and hours at 6 knots.
See Search Locations for the response of each mode.
Detail
| Tool | Description |
|---|---|
get_boat_details | Full specs, 365-day availability + pricing ranges, bookable check-in/check-out slots, required/optional fees, active discounts, cancellation policy, check-in/out times. |
get_poi_details | Description, opening hours, address, contact, nearby marinas, special offers. POIs carry no rating or review count. |
get_location_details | Description, amenities, contact, boat count and cheapest day rate, plus tracked-AIS insight: how busy it is month by month (against its own peak, never another place), how boats use it (overnight vs day stop, median stay, what hour it fills), where boats sail to from it (how many boats made each passage, with estimated sailing times at 6 knots), and the nearest bookable base. |
get_trip_details | Day-by-day stops, distances, activities, highlights, notes. |
Full input schemas are exposed via tools/list -- view them in MCP Inspector by connecting to https://charter.boats/mcp.
Prompt presets
The server registers three prompts surfaced as one-click starters in supporting clients:
plan-sailing-trip-- destination + optional guests/dates/budgetfind-charter-boat-- location + optional type/guests/datesexplore-destination-- single destination, surfaces marinas + POIs + routes
UI widgets
Two HTML widgets are served as MCP App resources:
| Widget | URI | Used by |
|---|---|---|
| Boats | ui://widget/boats-{hash}.html | search_boats |
| Locations | ui://widget/locations-{hash}.html | search_locations |
The {hash} is a hash of that widget's own HTML, so the URI changes if and only if the widget does. ChatGPT caches MCP resources by URI with no expiry — a fixed URI means a redeployed widget is never re-read, however many times it ships — and OpenAI's guidance is to give the template a new URI when its HTML, JS or CSS changes. Read the current values from resources/list; never hard-code one. A read of a previously published URI (including the original unhashed ui://widget/boats.html) still returns the current widget rather than an error, so a host holding a stale URI degrades to old-metadata-but-correct-markup instead of a broken card.
Both widgets:
- Use the
text/html;profile=mcp-appMIME type. - Declare their image CSP twice over:
_meta.ui.csp.resourceDomains(the MCP Apps standard, which Claude reads) and_meta["openai/widgetCSP"].resource_domains(the snake_case compatibility key, which is the only one ChatGPT reads — without it ChatGPT applies a locked-down default and every boat photo renders as a broken image)._meta.ui.prefersBorder/openai/widgetPrefersBorderand_meta.ui.resourceUri/openai/outputTemplateare paired the same way. - List exact origins, never wildcards — ChatGPT rejects a
*.example.comentry and one bad entry discards the whole list. The allowed set ischarter.boats,media.charter.boats, our storage bucket's direct origin, andwsrv.nl. Only the boats widget shows images: boat photos are served frommedia.charter.boats(listed explicitly, since a CSP host source matches that host alone andcharter.boatsdoes not cover its subdomains), and a boat photo not yet copied to our storage resizes through wsrv. The locations widget shows no images, andsearch_poishas no widget — itsimage_urlvalues are passed through as stored (some on our storage bucket, many at a third-party site's own address: a review site, a venue's website, a local guide), and nothing renders them inside a widget. - Implement the full MCP Apps handshake:
sandbox-resource-ready→ui/initialize(withprotocolVersion,appInfo,appCapabilities) →ui/notifications/initialized→ render onui/notifications/tool-result. - Report height via
ui/notifications/size-changed, measured frombody.scrollHeight/#app(the document element returns 0 inside Claude's sandbox). - Route link clicks through
ui/open-linksince sandboxed iframes blocktarget="_blank". - Respect host theme: explicit
theme: 'dark' | 'light'fromhost-context-changedoverrides theprefers-color-schemeCSS fallback.
Install
For end users: open https://charter.boats/mcp in any browser -- the page has copy-paste install steps for Claude.ai, Claude Desktop, ChatGPT, Cursor, and MCP Inspector.
Quick reference
Claude.ai / Claude Desktop: Settings → Connectors → Add custom connector → https://charter.boats/mcp (Pro/Max/Team/Enterprise required).
ChatGPT: Settings → Apps → Create app → MCP Server → https://charter.boats/mcp. Widget rendering requires the ChatGPT Apps program; Developer mode shows tool calls as text only.
Cursor: add to ~/.cursor/mcp.json:
MCP Inspector: npx @modelcontextprotocol/inspector → Streamable HTTP → https://charter.boats/mcp.
Rate limits
Tool calls are subject to the shared /api/ai/* rate limit. That limit is counted per calling client, and for MCP the caller the API sees is this server, not your assistant or its user: the server forwards no end-user identity (no IP, no key). MCP traffic is therefore limited collectively rather than per person, and there is no per-user figure to plan against. A limited call surfaces as a tool error (API 429); wait a little and retry.
Differences from the Custom GPT
Both integrations call the same /api/ai/* endpoints but expose them differently:
| Feature | Custom GPT (Actions) | MCP Server |
|---|---|---|
| Protocol | OpenAPI 3.1 | Model Context Protocol (Streamable HTTP) |
| Rich UI | No | Yes -- boats + locations widgets |
| Transport | HTTPS | JSON-RPC over HTTP |
| Session state | GPT conversation ID | Stateless per request |
| Rate limit | 30 / 10 min per OpenAI user | Shared across MCP traffic — see Rate limits |
| Hosts | ChatGPT only | Claude, ChatGPT Apps, Cursor, Continue, any MCP client |