MCP Server

Connect Claude, Cursor, or your own MCP client to Mapiry and manage maps, locations, categories, tags, and layers with tools instead of HTTP calls

Updated October 11, 2026

Mapiry also speaks the Model Context Protocol, so MCP clients — Claude Desktop, Cursor, and others — can discover and call the API without hand-written glue.

The endpoint is:

https://mapiry.com/mcp

Everything below about authentication, scopes, rate limits, and ownership applies unchanged. There is no separate MCP credential: use the same API key as the REST API.


Configuring a client

Most clients take a JSON block. Use the same key you created earlier:

{
  "mcpServers": {
    "mapiry": {
      "type": "http",
      "url": "https://mapiry.com/mcp",
      "headers": {
        "Authorization": "Bearer mk_live_your_key_here"
      }
    }
  }
}

Where that goes:

ClientLocation
Claude Desktopclaude_desktop_config.json → mcpServers
CursorSettings → MCP → Add server
Cline / Continue / most IDE agentsTheir MCP server list

The Authorization header must be sent on every request, including the first initialize. Requests without a key get 401 MISSING_API_KEY.


Verify it works

Do this before debugging your client. If the handshake succeeds, the problem is on the client side.

With MCP Inspector — the official test client:

npx @modelcontextprotocol/inspector@latest

Open the URL it prints, choose Streamable HTTP, enter https://mapiry.com/mcp, and add your key under Authorization as Bearer mk_live_.... You should see 24 tools listed. Selecting one shows its schema and lets you call it.

With curl — a raw handshake:

curl -X POST https://mapiry.com/mcp \
  -H "Authorization: Bearer mk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

A working response contains serverInfo: { name: "mapiry", version: "1.0.0" }.

Then ask what it can do:

curl -X POST https://mapiry.com/mcp \
  -H "Authorization: Bearer mk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

A 401 here means auth, not MCP — see the troubleshooting table below. A 200 with tools means the server is fine and your client is misconfigured.


ChatGPT does not work with this server yet

This is worth knowing before you try it.

ChatGPT cannot send a custom API key. Its connector framework authenticates users through an OAuth 2.1 flow or runs anonymously — there is no field for a static bearer token. OpenAI's documentation is explicit about this: ChatGPT "does not support machine-to-machine OAuth grants such as client credentials, service accounts, or JWT bearer assertions, nor can it present custom API keys."

So pointing ChatGPT at https://mapiry.com/mcp from Settings → Plugins → Add custom MCP server will fail at the authentication step, no matter which key you hold. The API key in the block above is a static credential, and ChatGPT will not send it.

What works with ChatGPT today:

PathHowWorks now?
ChatGPT app / connectorsPlugins → Add custom MCP serverNo — needs OAuth
Responses API (tools: [{ type: "mcp" }])Pass the key in the tool's authorization fieldYes, if you build the integration yourself

The Responses API path is programmatic, not a ChatGPT setting — it is for code you run, not the chat product:

curl https://api.openai.com/v1/responses \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
  "model": "gpt-6-astra",
  "input": "List my maps and add a bakery to the Tokyo one",
  "tools": [
    {
      "type": "mcp",
      "server_label": "mapiry",
      "server_url": "https://mapiry.com/mcp",
      "authorization": "mk_live_your_key_here"
    }
  ]
}'

Two caveats. The API does not store the authorization value, so you must resend it on every request. And the docs describe that field as an OAuth access token — Mapiry sends it as Authorization: Bearer <key>, which is what the MCP spec requires, but this combination has not been tested against a live key.

What ChatGPT support would require: an OAuth 2.1 authorization server in front of the MCP endpoint, conforming to the MCP authorization specification — protected-resource metadata, an authorization server with discovery documents, PKCE, and the resource parameter echoed through the flow. OpenAI strongly recommends using an established identity provider rather than building one. That is a substantial piece of work and a product decision, not a configuration change.

If ChatGPT support matters, that is the next change to scope.


Common workflows

Every workflow starts with list_maps. Tools take map IDs, not names or slugs, and there is no tool that accepts a slug — so if you do not already know the ID, look it up first.

Add a place to an existing map

  1. list_maps → find the map's id
  2. create_location with { mapId, name, lat, lng }
list_maps
  → { data: { maps: [{ id: "clxyz...", title: "Tokyo Coffee", ... }] } }

create_location { mapId: "clxyz...", name: "Fuglen Tokyo", lat: 35.6654, lng: 139.7006 }
  → { data: { id: "loc...", name: "Fuglen Tokyo", ... } }

Build a map from a list of places

  1. create_map with { title, country, city }
  2. bulk_create_locations with { mapId, locations: [...] } — up to 100 per call
create_map { title: "Best Coffee in Tokyo", country: "Japan", city: "Tokyo" }
  → { data: { id: "clxyz...", slug: "best-coffee-in-tokyo" } }

bulk_create_locations { mapId: "clxyz...", locations: [
  { name: "Fuglen Tokyo",       lat: 35.6654, lng: 139.7006 },
  { name: "Bear Pond Espresso", lat: 35.6585, lng: 139.6968 }
]}
  → { data: { created: [ ... ], count: 2 } }

For more than 100 places, call bulk_create_locations repeatedly — one batch per call. Each batch is atomic on its own.

Tag and categorize as you go

create_location accepts tag names, created on demand. Categories work the same way via category:

create_location {
  mapId: "clxyz...",
  name: "Fuglen Tokyo",
  lat: 35.6654, lng: 139.7006,
  category: "Coffee",
  tags: ["wifi", "outdoor-seating"]
}

Leave tags out to keep a location's existing tags. Send [] to remove them all. Matching is case-insensitive, so "Wifi" and "wifi" are the same tag.

Read-only review

With a READ_ONLY key, list_maps, get_map, list_locations, list_categories, list_tags, and list_layers all work. Write tools return INSUFFICIENT_SCOPE.

list_maps → list_locations { mapId } → list_categories { mapId }

Before you start

Four things that trip people up.

There is no geocoding tool. You must supply lat and lng yourself. If you have addresses rather than coordinates, geocode them first — the API will not guess, and a location with wrong coordinates fails silently rather than erroring.

bulk_create_locations is all-or-nothing. One invalid row and nothing is created. If a large import keeps failing, bisect the list to find the bad row rather than resubmitting it whole.

list_maps is not optional. There is no lookup by title or slug. A client that needs a map ID must call list_maps and match on the title itself.

Deletes are permanent. delete_map removes the map with all of its locations, categories, and tags. There is no undo.

Pagination: pageSize is capped at 200, and out-of-range values are clamped rather than rejected. page starts at 1.


What you can do

Each tool performs the same operation as its REST endpoint, with the same rules and the same { "data": ... } response — so a client needs one parser for both.

Maps

ToolWhat it does
list_mapsList your maps with location counts — start here to discover map IDs
create_mapCreate a map
get_mapGet a map with its categories and tags
update_mapUpdate a map; only supplied fields change
delete_mapPermanently delete a map

Locations

ToolWhat it does
list_locationsList locations, filterable by name, category, or tag
create_locationAdd a location; coordinates are required
bulk_create_locationsCreate up to 100 locations atomically
get_locationGet one location with its tags and layers
update_locationUpdate a location
delete_locationDelete a location
set_location_layersReplace the layers a location belongs to

Categories, tags, and layers each have the same five tools: list_*, create_*, update_*, delete_* (layers additionally appear on locations via set_location_layers).

Tools return errors as a result marked isError, carrying the same error codes the REST API uses (NOT_FOUND, VALIDATION_ERROR, CONFLICT, INSUFFICIENT_SCOPE, …).


Writing your own client

If you are not using an existing MCP client, the protocol dance is four steps. Send the Authorization header on every one of them.

  1. initialize — negotiate the protocol version. The response gives you the server's name and version.
  2. notifications/initialized — a notification, no response expected. Skip it and some clients misbehave.
  3. tools/list — returns each tool's name, description, inputSchema, and annotations. Cache this; it does not change per request.
  4. tools/call — { name, arguments }. The result is a content array plus an isError flag.

Handling results:

  • isError: false — success. The content[0].text is JSON containing { "data": ... }, identical to the REST response body, so one parser serves both surfaces.
  • isError: true — the operation failed. content[0].text is JSON containing { "error": { code, message } } with the same codes the REST API uses. Branch on code, not on message — the message is human-readable text and its wording can change.

A tool that does not exist, or arguments that fail the tool's schema, also come back as isError: true rather than a protocol error. Treat any isError result as a failed operation, and a JSON-RPC error response as a transport or protocol failure.

If you only need a few tools, pass allowed_tools where your client supports it — importing all 24 constrains model selection more than importing the five you want.


What you cannot do

API key management is deliberately not exposed. There is no tool to create, list, or revoke keys. A client that could mint keys could escalate its own access, and one that could revoke them could lock you out. Manage keys in your account settings.


Scopes and limits

Your key's scope decides which tools you can call:

ScopeTools
READ_ONLYThe list_* and get_* tools
READ_WRITEAll tools

Each tool also carries hints describing its effect — read-only, destructive, idempotent — so a client can tell what a call will do before making it.

Tool calls count against the same per-account rate limits as REST requests. If you use both surfaces, the quota is shared.


Troubleshooting

SymptomCause
401 MISSING_API_KEYNo Authorization header, or it is not a Bearer token
401 INVALID_API_KEYKey unknown or revoked
401 API_KEY_EXPIREDKey past its expiry date
403 PRO_SUBSCRIPTION_REQUIREDAccount has no active Pro subscription
403 INSUFFICIENT_SCOPEA READ_ONLY key called a write tool
429 RATE_LIMIT_EXCEEDEDAccount quota exhausted — check Retry-After

If a tool reports isError with NOT_FOUND for a map you can see in the dashboard, the map is owned by a different account — the API only ever exposes maps owned by the authenticated key's owner.

Related