shinnslist

docs / mcp

mcp for agent builders LIVE

Connect Claude, ChatGPT or any MCP client to POST /api/mcp and ask for off-market parcels in Colorado and Virginia the way a buyer asks: a county, a town or a ZIP, and a trade. Seven task tools, both protocol handshakes, no key for the aggregate tools, and never an owner name or a mailing address.

working reference. The methods, tools, error codes and response shapes below are quoted from the deployed functions code (functions/lib/mcp.js, functions/lib/mcp-tools.js and functions/api/mcp.js; server shinnslist 0.2.0). No method, parameter, tool name or error code is documented here that the code does not implement. The one planned surface on this page is marked DRAFT.

0 connect — either handshake

The endpoint is https://shinnslist.com/api/mcp, Streamable HTTP, JSON-RPC 2.0, one JSON object per POST. Copy-paste setup:

Claude Code:   claude mcp add --transport http shinnslist https://shinnslist.com/api/mcp
Claude.ai:     Settings → Connectors → Add custom connector → https://shinnslist.com/api/mcp
ChatGPT:       Settings → Connectors (developer mode) → https://shinnslist.com/api/mcp
Any client:    POST https://shinnslist.com/api/mcp  (content-type: application/json)

Two eras of the protocol are served on the one endpoint. A client of the handshake-based revisions (2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05: today's Claude, ChatGPT and most others) opens with initialize and gets the version it asked for back (or 2025-11-25 when it asks for one this server does not speak); notifications/initialized is accepted with an empty 202, and ping answers {}. No session id is issued and none is required.

{"jsonrpc":"2.0","id":1,"method":"initialize",
 "params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"my-agent","version":"1.0"}}}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": { "tools": { "listChanged": false } },
    "serverInfo": { "name": "shinnslist", "version": "0.2.0" },
    "instructions": "shinnslist: off-market parcels from county records in Colorado and Virginia, never owner names. Start with list_coverage, …"
  }
}

A client of the stateless revision 2026-07-28 may start with server/discover (it costs nothing against your quota) or send any request straight away; your protocol version and capabilities may ride in params._meta, accepted leniently, never a hard gate. Every result on this endpoint carries a resultType.

{"jsonrpc":"2.0","id":1,"method":"server/discover"}
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "server_discover",
    "supportedVersions": ["2026-07-28"],
    "serverInfo": { "name": "shinnslist", "version": "0.2.0" },
    "capabilities": { "tools": { "listChanged": false } }
  }
}

Prefer a browser? GET /api/mcp returns the same identity, the setup lines and the tool names as plain JSON.

1 list the tools — tools/list

Seven task tools, named the way buyers ask. This call is free (no quota), and the server invites caching for 60 seconds (ttlMs 60000, cacheScope server, resultType tools_list).

{"jsonrpc":"2.0","id":2,"method":"tools/list"}
toolwhat it answersargumentskey
find_parcelsoff-market parcels in a county, town or ZIP, by trade: wholesaler, flipper, str_investor, developer, cash_investor, brrr, land, two_households (a family compound), realtorstate, county, town, zip, trade, limit (at most 25), offsetnone
town_statshow many parcels an area holds, and the count behind each tradestate, county, town, zipnone
check_parcelone parcel's stored record and (Colorado) its stored checkskey, or address + zip, or lat + lngnone
list_coveragethe counties covered, their parcel counts and freshness, the open trades, the proven-signal countnonenone
list_signalsthe proven signals with the date, precision and recall of each proof (nothing pending is listed)nonenone
explain_gradinghow the site grades itself, with the current numbers and the page to citenonenone
run_signalone proven signal adapter, run live at a point in Colorado or Virginiasignal, lat, lngx-api-key

The old per-signal tool names (one per adapter — 271 today — the slug of the signal id such as best-value-number, never best_value_number) still answer tools/call for clients that cached the old list, with the same gates as run_signal; they are no longer listed. In that catalog each entry's description carries its honest status and radius_km is accepted but reserved.

2 call a task tool — tools/call

The six aggregate tools read the hunt database the way the site does and need no key: the records are public, owner names and mailing addresses are stripped, and the site's rate limit of 100 requests a minute per address applies.

curl -X POST https://shinnslist.com/api/mcp \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
       "params":{"name":"find_parcels",
                 "arguments":{"county":"Boulder","trade":"wholesaler","limit":5}}}'
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "content": [{ "type": "text", "text": "{\"ok\":true,\"kind\":\"parcels\",\"area\":\"Boulder County, Colorado\", … }" }],
    "structuredContent": {
      "ok": true, "kind": "parcels", "area": "Boulder County, Colorado",
      "trade": { "id": "wholesaler", "label": "Wholesaler", "what": "owners whose mail goes elsewhere, held 10+ years, bought for under half of today’s value" },
      "total": 1234, "count": 5, "offset": 0,
      "parcels": [ { "parcel_key": "CO|Boulder|R0012345", "address": "…", "zip": "80540", "county": "Boulder", "state": "CO",
                     "value_est": 612000, "last_sale_date": "2009-05-01", "last_sale_price": 240000, "tenure_yrs": 17.4,
                     "url": "https://shinnslist.com/hunt.html?q=…&zip=80540", "card": "https://shinnslist.com/api/hunt/parcel?key=…" } ],
      "as_of": "2026-10-17",
      "source": "Source: shinnslist (county records)",
      "url": "https://shinnslist.com/hunt.html?lens=wholesaler",
      "how_to_cite": "Say \"shinnslist, from county records\" and link the url. Owner names and mailing addresses are never in this answer."
    },
    "isError": false,
    "resultType": "tool_result"
  }
}

Copy-paste carefully: content[0].text is a JSON string — JSON.parse it, or read the same object from structuredContent. Every answer carries as_of, source and a shinnslist url, so you can cite it. When the database does not answer, the result comes back with isError: true and the reason in content[0].text, never a 500 and never a made-up number. A bad argument (an unknown trade, a ZIP that is not five digits, a town the places list does not know) is a JSON-RPC -32602 that says what to fix.

Other examples: {"name":"town_stats","arguments":{"town":"Lyons"}} · {"name":"check_parcel","arguments":{"address":"874 Cold Spring Rd","zip":"80302"}} · {"name":"check_parcel","arguments":{"key":"CO|Boulder|R0012345"}} · {"name":"list_coverage","arguments":{}}. A county name alone settles the state; a Virginia county reads Virginia. The trade doors are Colorado today, and the answer says so when you ask for one elsewhere.

3 run a proven signal — run_signal

run_signal runs one signal adapter live at a point (signal from list_signals, lat, lng) and returns the same signal envelope the REST API returns: signal_id, status, result, evidence, confidence, retrieved_at and data_as_of. Only proven signals run; asking for one that is not proven is -32602 with its verdict. It needs a key (the x-api-key header or ?key=; a free key from POST /api/keys; no key falls back to the anonymous tier, 5 a day) and runs the identical gate stack as /api/intel, in the identical order: API key → rate limit → daily quota → entitlements. The point must be in Colorado or Virginia.

curl -X POST https://shinnslist.com/api/mcp \
  -H "x-api-key: YOUR_API_KEY" \
  -H "content-type: application/json" \
  -d '{"jsonrpc":"2.0","id":4,"method":"tools/call",
       "params":{"name":"run_signal",
                 "arguments":{"signal":"tax_delinquency_early_warning","lat":40.5853,"lng":-105.0844}}}'
codewhendata carries
-32001invalid api keyhint: "get a free key: POST /api/keys"
-32002rate_limit_exceededretry_after_seconds (per-key/IP token bucket)
-32003daily_quota_exceededreason, quota {limit, resets_at}, hint: "quota resets at 00:00 UTC; paid tiers raise the cap"
-32004signal_not_entitleddenied: [{signal_id, reason}], hint: "this signal requires a higher tier"
-32005outside_coverage (the point is outside Colorado and Virginia)boundary: the jurisdiction check signal, with the detail

An entitlement denial never triggers computation and never consumes quota. A throwing adapter comes back as a degraded envelope with the reasons in unavailable, never a 500. Every JSON-RPC error rides HTTP 200 (MCP convention) with the code in the body — only transport-level failures use HTTP 400: -32700 (body isn't valid JSON) and -32600 (missing jsonrpc: "2.0" or method). An unknown method or tool is -32601.

what this endpoint will never do

planned: v1 REST evaluate DRAFT

The per-signal REST evaluate endpoints (POST /v1/signals/{signal}/evaluate) are the planned REST alternative to run_signal — same adapters, different envelope. They are not shipped: no deployed endpoint exercises them yet, and their shapes are described in the error reference only as drafts.

agent-builder checklist. Start with list_coverage; cache tools/list for its 60-second TTL; read structuredContent or JSON.parse(content[0].text); treat isError: true as "the database did not answer", not as "no parcels"; cite the url and as_of of every answer; honor retrieved_at and data_as_of on signal envelopes; never print an owner's name, because none is sent.

next: getting started · api reference · api keys · rate limits · how we grade