MCP SERVER

Using MapLogics from an AI agent

MapLogics runs a Model Context Protocol server, so an agent such as Claude can ask the same questions your code asks the API: is this address served, who owns it, and why. The answers come from your published territories and rules, not from a model, so an agent gets the answer your website and your CRM get.

Endpoint and key

URL
https://api.maplogics.com/mcp
Header
Authorization: Bearer ml_test_... or ml_live_..., the same keys the API takes. Create one in Settings, under API keys.
Transport
Streamable HTTP with JSON responses. Protocol versions 2025-06-18 and 2025-03-26. No session is kept, so there is nothing to reconnect.

A missing or rejected key gets a 401 with a WWW-Authenticate header. The server does not run an OAuth flow, so a client that offers to sign in through a browser will not get anywhere: give it the header instead.

Tools

All three only read. Each tool returns a short text answer for the model and the full structured result alongside it, described by an output schema.

ToolKey scopeWhat it answers
check_addressresolveWhether an address is served, the territory it falls in and the location that handles it. Takes one line of text or separate fields; separate fields geocode more reliably.
check_coordinateresolveThe same answer for a longitude and latitude. Nothing is geocoded, so the answer is exactly reproducible.
explain_decisionconfig:readWhy an earlier decision came out the way it did, in plain sentences, citing every rule that acted. Takes the request_id the other two return.

A key without the scope a tool needs gets a tool error naming the scope, which an agent can pass on to you. Give an agent a key with resolve alone unless you want it explaining decisions too.

Claude Code

One command. Swap in your own key.

Terminalbash

claude mcp add --transport http maplogics https://api.maplogics.com/mcp \
  --header "Authorization: Bearer ml_test_..."

Claude Desktop

Claude Desktop's config file starts local programs rather than calling a URL, so it reaches a remote server with a header through the open source mcp-remote bridge. Add this to claude_desktop_config.json and restart the app.

claude_desktop_config.jsonjson

{
  "mcpServers": {
    "maplogics": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote", "https://api.maplogics.com/mcp",
        "--header", "Authorization:${MAPLOGICS_AUTH}"
      ],
      "env": { "MAPLOGICS_AUTH": "Bearer ml_test_..." }
    }
  }
}

The header is written without a space after the colon and the key is passed through an environment variable on purpose: some clients split arguments on spaces, and that splits the header in two.

Other clients

Any client that speaks Streamable HTTP and can send a header works. Most take a block like this one; the field holding the URL is called url or serverUrldepending on the client.

MCP client configjson

{
  "mcpServers": {
    "maplogics": {
      "type": "http",
      "url": "https://api.maplogics.com/mcp",
      "headers": { "Authorization": "Bearer ml_test_..." }
    }
  }
}

What goes over the wire

JSON-RPC over a POST, if you are writing a client or want to see a call by hand. A real client sends initialize first; the server keeps no session, so a single call works on its own too.

Requestbash

curl https://api.maplogics.com/mcp \
  -H "Authorization: Bearer ml_test_..." \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "MCP-Protocol-Version: 2025-06-18" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "check_address",
      "arguments": { "address": "2 E Main St, Richmond, VA 23219" }
    }
  }'

Responsejson

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "content": [{
      "type": "text",
      "text": "Served. Territory: Richmond Central. Handled by: Richmond Central.\n..."
    }],
    "structuredContent": {
      "request_id": "req_9039f987",
      "status": "resolved",
      "serviceable": true,
      "territory": { "id": "trr_...", "name": "Richmond Central" },
      "location": { "id": "loc_...", "name": "Richmond Central" },
      "reason_code": "PRIMARY_ELIGIBLE_TERRITORY",
      "message": "...",
      "outputs": {},
      "ruleset_version": "cfg_01M0X5..."
    }
  }
}

A refusal from the resolver, such as a rate limit or an address no geocoder could find, comes back as a result with isError set and the reason in the text, so the agent can read it. Malformed arguments and unknown tools are JSON-RPC errors instead.

Test keys and the trial

  • Test keys work here, trial included. An ml_test_ key answers from your published configuration through the same code as a live key, is never billed and does not count against your plan. Start an agent on a test key.
  • Publish something first. Until a configuration is published there is nothing to decide against, and every check returns an error saying so. Loading the example data in the app publishes one, which is enough to try it.
  • Each check is one decision. With a live key, every check_addressand check_coordinate call is metered and rate limited exactly like a call to /v1/resolve, and appears in your decision log. An agent that checks the same address twice has made two decisions. explain_decision is not metered.

Calling from a browser

Server-side clients send no Origin header and are not affected. A request from a web page is accepted only when its origin is on your organization's allowed origins list, the same list the API checks, and anything else is refused with a 403. That is what stops a leaked live key being used from somebody else's page.

Getting help

Email hello@maplogics.com with the request id from the tool result. It finds the exact decision in your log. The reason codes in the API reference say what each outcome means and what to change.