Connecting a client
Wire the Parcel MCP Server into Claude Code, Claude Desktop, or any MCP-aware client — Bearer token, OAuth 2.0 with PKCE, and raw JSON-RPC over Streamable HTTP.
The endpoint is https://api.landrecords.us/mcp and the transport is Streamable HTTP. There are two ways to authenticate, and every client uses one of them:
- Bearer token — send an
Authorizationheader on every request. Your token is the same UUID you use for the/protile and WFS endpoints; find it on your account page. Simplest path, and what you want for servers, scripts, and agents. - OAuth 2.0 authorization code + PKCE — for clients that run the MCP spec's OAuth flow before speaking JSON-RPC. You paste your API token into a login form once and the client stores the result.
Both end up in the same place: the access token is your API token, validated against your plan on every request.
Setting the Authorization header
This is where most failed connections come from, so it is worth being exact. The header name is Authorization. The value is the word Bearer, a single space, then your API token verbatim:
Authorization: Bearer 3f2b1c9e-7a41-4d88-9b02-e5c6d7a81234Four rules cover every client:
Beareris required, and it is case-insensitive —bearer <token>works, a bare<token>with no scheme does not, and neither does any other scheme name.- The token goes in raw. It is a plain UUID from your account page — no
lr_prefix, no quotes, no base64, no URL-encoding. Anything that isn't a well-formed UUID is rejected, which in practice means a truncated copy/paste. - Every request, not just the handshake. The server is stateless — each JSON-RPC call is authenticated on its own, so the header belongs in the client's default headers, not in a one-time login step. MCP clients configured with a
headersblock do this for you. - Use the header, not
?token=./mcpvalidates the token with the same code path the tile and WFS routes use, so a?token=on the POST URL is accepted — but no MCP client offers a way to put one there, and OAuth-capable clients key off theWWW-Authenticatechallenge the header path returns. Treat the header as the only way in.
Each failure names itself, so the body tells you which mistake you made:
{ "error": "Missing authorization header or token parameter" }The other forms are Invalid token (the UUID is not on this account), Token is disabled, and Token expired — all 401. Two failures are 403 instead: No plan associated with token, and Your plan does not include this feature. for a plan with no MCP allowance. See Limits.
Claude Code
Pass the whole header as one quoted argument — name, colon, space, value. --header (or -H) can be repeated, but this server needs only the one:
claude mcp add --transport http landrecords \
https://api.landrecords.us/mcp \
--header "Authorization: Bearer $LR_TOKEN"Use double quotes so your shell expands $LR_TOKEN — single quotes send the literal string $LR_TOKEN and every call comes back 401. Run echo $LR_TOKEN first if you are not sure the variable is set; an empty variable produces Authorization: Bearer with nothing after it, which fails the same way.
Then /mcp inside Claude Code lists landrecords and its eight tools. To share the server with a repository instead of your user config, commit a .mcp.json at the project root:
{
"mcpServers": {
"landrecords": {
"type": "http",
"url": "https://api.landrecords.us/mcp",
"headers": {
"Authorization": "Bearer ${LR_TOKEN}"
}
}
}
}Claude Code expands ${LR_TOKEN} from the environment when it reads the file, so keep the token there rather than inline — .mcp.json is checked in, and the token grants full read access to your plan's quota. Note the ${…} braces: a bare $LR_TOKEN is not expanded in JSON and is sent literally.
Claude Desktop and other local-command clients
Clients that expect to launch a local process rather than call a URL can use mcp-remote as a bridge. Edit claude_desktop_config.json:
{
"mcpServers": {
"landrecords-parcels": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://api.landrecords.us/mcp",
"--header", "Authorization: Bearer ${LR_TOKEN}"
],
"env": { "LR_TOKEN": "your-api-token" }
}
}
}Two things about that args array:
- The header is two entries —
"--header", then the entire"Authorization: Bearer …"string as one element. SplittingAuthorization:,Bearer, and the token across three entries is the usual mistake, and it arrives as no header at all. mcp-remoteitself expands${LR_TOKEN}inside the header value from theenvblock, so the token never has to appear in the args.
On Cursor and Claude Desktop for Windows, spaces inside args aren't escaped when the client shells out to npx, which mangles the header. Move the space into the environment variable and write the header with no space after the colon:
{
"mcpServers": {
"landrecords-parcels": {
"command": "npx",
"args": [
"-y", "mcp-remote",
"https://api.landrecords.us/mcp",
"--header", "Authorization:${LR_AUTH}"
],
"env": { "LR_AUTH": "Bearer your-api-token" }
}
}
}The server does not care about the space after the colon — that part is HTTP-optional — but it does need the Bearer prefix, which is why it moves into LR_AUTH rather than being dropped.
Restart the client and the parcel tools appear alongside its others. If you omit --header, mcp-remote runs the OAuth flow described below instead.
Any client that speaks remote MCP
The Streamable HTTP endpoint is https://api.landrecords.us/mcp. Any client that can set a request header connects directly with:
Authorization: Bearer <YOUR_TOKEN>No other headers are required, and no cookie or session is involved. The server runs stateless — every request is authenticated and served on its own, so there is no Mcp-Session-Id to capture and replay, and a client that sends one anyway is fine. Mcp-Session-Id is exposed through CORS for browser-based clients that expect to read it.
https://api.landrecords.us/mcp/, with a trailing slash, is the same endpoint — use whichever form your client's config prefers. The legacy SSE handshake at /mcp/sse has been retired: it and /mcp/messages/ answer 410 with a pointer back here. An SSE handshake parks a stream in one process and needs the follow-up POST to reach that same one, which the edge runtime cannot guarantee.
The OAuth flow
Modern MCP clients are expected to run OAuth 2.0 before speaking JSON-RPC. The server maps that flow onto your existing API token:
- The client hits
GET /mcp/authorizewith itsclient_id,redirect_uri, and PKCEcode_challenge. - The server redirects to a login form at
GET /mcp/login. - You paste your API token into the form and submit it.
- The token is validated against your account; the server issues a one-time authorization code and redirects back to the client's
redirect_uri. - The client exchanges the code (plus its PKCE verifier) at
POST /mcp/tokenand receives an access token — which is your API token. - Every subsequent MCP request carries
Authorization: Bearer <access_token>.
Practical details:
- Discovery answers at
GET /.well-known/oauth-authorization-server(with or without a trailing/mcp) and at the v1 locationGET /mcp/.well-known/oauth-authorization-server./.well-known/oauth-protected-resourceis served the same three ways, and an unauthenticatedPOST /mcpreturns aWWW-Authenticateheader pointing at it. - Client registration is closed. Dynamic registration is disabled; instead a small set of client IDs is pre-registered, one per common
mcp-remotecallback port. The IDs arelandrecords-mcp:7075,landrecords-mcp:3333,landrecords-mcp:8080,landrecords-mcp:9000, andlandrecords-mcp:4200, each with redirect URIhttp://localhost:<port>/callback.mcp-remotedefaults to port 7075. If you need a different callback port, contact us — we would rather add a port than open registration. - Authorization codes are single-use and expire in 5 minutes.
- No refresh tokens are issued.
POST /mcp/tokenaccepts onlygrant_type=authorization_code. The response advertisesexpires_in: 86400, but the access token is your API token, so its real lifetime is that token's: when it expires or is revoked, requests return 401 and the client runs you back through the form.
Raw JSON-RPC
Useful for debugging, health checks in CI, or a client you are writing yourself. Because the server is stateless, each call is a self-contained POST carrying the Authorization header — there is no session to establish and no handshake to replay. Responses come back as plain application/json, but sending both accept types is what the Streamable HTTP spec asks of a client and costs nothing:
export LR_TOKEN=3f2b1c9e-7a41-4d88-9b02-e5c6d7a81234
curl -i https://api.landrecords.us/mcp \
-H "Authorization: Bearer $LR_TOKEN" \
-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.0" }
}
}'initialize reports the protocol version and server info. Listing and calling tools takes the same three headers and no session ID:
# List the tools
curl -s https://api.landrecords.us/mcp \
-H "Authorization: Bearer $LR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
# Call one
curl -s https://api.landrecords.us/mcp \
-H "Authorization: Bearer $LR_TOKEN" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0", "id": 3, "method": "tools/call",
"params": {
"name": "parcel_count",
"arguments": { "where": [{ "column": "geoid", "op": "ge", "value": "51" }, { "column": "geoid", "op": "lt", "value": "52" }] }
}
}'To see whether the header is the problem, drop it: the same request without Authorization returns 401 {"error": "Missing authorization header or token parameter"}. If you get that with the header set, the value isn't reaching the server — check shell quoting first. Any other 401 body (Invalid token, Token is disabled, Token expired) means the header arrived and the token itself is the problem.
If you only want to verify reachability, GET /mcp/health needs no token and no handshake.
For a friendlier programmatic path, use an MCP SDK instead of raw JSON-RPC — see Examples.
Troubleshooting
| Symptom | Cause |
|---|---|
401 Missing authorization header or token parameter |
The header never arrived: missing entirely, missing Bearer, or an unexpanded $LR_TOKEN. Work down the four rules above |
401 Invalid token / Token is disabled / Token expired |
The header arrived and the token is the problem — re-mint it on your account page |
403 No plan associated with token |
The header was fine and the token is real, but no plan is attached to it |
403 Your plan does not include this feature. |
The token and plan are both real, but the plan's MCP allowance is zero. See Limits |
| Tools list is empty, or the client shows no tools | Almost always the token. Reproduce with the tools/list curl above: if that works, the client isn't sending your header |
| Client reports "session terminated" and retries over SSE | Either the URL is wrong — https://api.landrecords.us/mcp and /mcp/ both work, anything else under /mcp returns 404 {"error": "Not found"} — or the client tried GET/DELETE on /mcp, which answer 405: the server is stateless and has no stream to open or session to end |
| OAuth login redirects but the client reports an invalid grant | The authorization code expired (5 minutes) or was already used — restart the flow |