Developer docs · v1.0.0

Background check MCP server tutorial: run a registry search from Claude or Cursor

Connect an MCP client to the OffenderSearch server, see which of the eight tools an agent reaches for, give it the rules that keep a registry answer honest, and read one run against the sandbox.

Updated November 3, 2026

What the background check MCP server is, and its status today

The OffenderSearch MCP server is a Model Context Protocol endpoint that exposes the sex offender API and the sex offender map API as tools an MCP client can call. Point Claude Desktop, Cursor or any client that supports remote MCP servers with custom headers at it, and the model can search 58 US registries, run a list of people, ask what is covered, and read a map, with the same scored, cited records the REST API returns. The server is open source at github.com/offendersearch/mcp, which is where the tool schemas, the per-client install notes and the stdio variant live.

Private beta. The server is built and the tools below are final, but the hosted endpoint is not open to self-serve sign-ups yet. Create an account, then ask for MCP access from the MCP server page. It uses the same API key and the same billing as the REST API, and an os_test_ key routes to the sandbox, where nothing is billed.

Two things to be clear about before you build. First, a tool result is a public-records lookup, not a consumer report; an agent must never decide a hire, a tenancy, credit or insurance from it, and the rules below make that explicit in the system prompt. Second, the tools cover the registry and the map. Criminal records (jail bookings, court cases, custody, warrants) are on the same key through the REST API only, which the last section shows. A fuller description of both layers is on the background check API page.

Configure Claude or Cursor for the MCP server

One config block. The key goes in the X-API-Key header, exactly as it would on a REST call; the client sends it with every tool invocation. Use the test key while you develop and swap it for the live key when you go live.

MCP client config (Claude Desktop, Cursor and compatible clients)
{
  "mcpServers": {
    "offendersearch": {
      "url": "https://mcp.offendersearch.app",
      "headers": { "X-API-Key": "os_live_••••" }
    }
  }
}

Keep the key in the client's secret configuration, never in a shared prompt or a repository. The key is the account: anyone who can read it can run metered searches on it, and keys are minted and revoked in the dashboard. Once the client connects it lists the tools with their input schemas and the model picks one when a question fits.

The eight MCP server tools and what each one costs

Three registry tools and five map tools. Each is metered like the REST call it wraps: a registry search is $0.15 a call, $0.11 past 2,000 calls in a month, with 25 free calls on sign-up and no card; the coverage tool is free; the map tools are billed at the Map API prices, for accounts that have turned the map on.

ToolWhat it doesREST equivalentBilling
search_offendersSearch 58 US registries by name, date of birth or age, address, ZIP or a latitude/longitude radius. Returns the same scored, de-duplicated, cited records as the REST API.POST /v1/searchOne search per call
batch_searchUp to 1,000 people in one call, one scored row out per row in, for an agent working through a roster, an applicant list or a sign-up sheet.POST /v1/batchOne search per row
get_registry_coverageThe catalog of the 58 registries with scope, legal status and health, so the agent can say what it searched before it says what it found.GET /v1/sourcesFree, no key required
map_viewportWhat to draw for one map view, as a bounding box or a centre and radius: clusters zoomed out, pins closer in, names close up.POST /v1/map/viewportOne map request
map_groupThe people behind a pin that stands for several people at one address or an approximate area, one page at a time.GET /v1/map/groups/{groupId}One map request per page
map_areaThe same answer a map view gets, for a drawn polygon or a buffered line.POST /v1/map/areaOne map request
map_routeCounts per leg and per stop along a walking or bus route, with pins and distances but no names.POST /v1/map/routeRoute units
map_open_recordThe complete record behind one marker id or saved record reference. Opening the same marker again is free.GET /v1/map/records/{markerId}One record open

Tool results are the REST envelopes, unchanged: the agent reads status, counts, sourceStatus[] and records[] exactly as a program would, and every record carries matchState, source.recordUrl and source.lastCheckedAt. That is deliberate. A tool that returned a summary would hide the fields that make the answer checkable.

From prompt to tool: how an agent picks an MCP background check tool

The model chooses a tool from its description and the shape of the question. These are the mappings you should expect, and the ones to test against when you write evaluations for your agent.

Someone asks the agentWhat it calls
"Is Jordan Rivera, born 1988-04-12, on any sex offender registry?"search_offenders with name and dob: one person, one scored answer with its citation. On the sandbox this synthetic name returns a complete answer with zero records.
"Check these 40 volunteers before Saturday."batch_search over the list, one row per volunteer, in input order, which the agent then summarises row by row.
"Who is registered within a mile of 123 Main St, Springfield?"search_offenders with an address or a lat, lng and radiusMiles, returning registrants and their distances.
"Which registries did you actually check for Texas?"get_registry_coverage, so the agent cites the catalog instead of guessing.
"How many registered addresses are in this part of Columbus, and who is at this pin?"map_viewport for the view, then map_open_record for the one marker the user picks.
"Count registered addresses along this bus route."map_route: counts per leg and stop, no names.

A question that mixes a list with a place ("which of these 40 people live near the school?") is two tools: batch_search for the people, then the addresses on each record compared with the place. A question about criminal history is not a tool at all today; the agent should say so and your integration should route it to the REST call in the last section.

System-prompt rules for a background check agent

The tools make an answer checkable; the system prompt makes the agent say the checkable parts. Put rules like these in front of any agent that can call them.

System prompt
You have registry search tools from OffenderSearch. Rules:
1. The results are a public-records lookup, not a consumer report. Never decide, recommend
   or score a hiring, housing, credit or insurance outcome from them. If asked to, say that
   such a decision has to run as an FCRA order and stop.
2. Ask for a date of birth (or an age) before searching a person when the user has one.
3. Only a record whose matchState is "dob_match" is an identification. A record with
   matchState "no_dob_age_year", or any record returned without a date of birth, is a
   possible match for a human to review: say "possible match, not verified".
4. Cite sources[].recordUrl for every record you mention. Tell the user to confirm there.
5. If counts.sourcesIncomplete is above zero, say the search is incomplete. Never say
   "no match" over an incomplete search.
6. Say which registries were searched (counts.sourcesQueried) before saying what was found.
7. Use batch_search for a list of people, not a loop of search_offenders calls.

Rule 3 is the one that matters most. dob_match means the full date of birth on the record agrees with the one the user gave, and it is the only automatic identification. year_match and age_match are weaker agreements; no_dob_age_year means the registry published nothing to verify against, so the record is a name match and nothing more; null means no date of birth or age was sent at all. A record whose date of birth contradicts the one sent is not returned. The matching and confidence guide has the full state table, and the no-match and multiple-match results tutorial shows what the "Name Only" sandbox person (three possible records in three states, no date of birth on any) should look like in a UI, which is also what the agent should say in words.

A sample MCP run against the sandbox: Exact Match, 1980-01-01

The transcript below is an illustration of the documented sandbox scenario: the "Exact Match" person with date of birth 1980-01-01 returns one record with matchState dob_match from the Ohio sample registry, two addresses, a photo, offense "Sexual battery", Tier III. The tool result is abridged from the captured sandbox response; the agent's wording is what the rules above produce.

Illustrative transcript
# Illustration of the documented sandbox scenario "Exact Match", 1980-01-01 (os_test_ key).
# The tool result is abridged from the captured sandbox response.

User:   Is Exact Match, born 1 January 1980, on a sex offender registry?

Agent:  Calling search_offenders …
        → { "firstName": "Exact", "lastName": "Match", "dob": "1980-01-01" }

Tool:   {
          "searchId": "srch_sbx_so_exact", "status": "complete",
          "counts": { "records": 1, "sourcesQueried": 58, "sourcesIncomplete": 0, "sourcesComplete": 58, "recordsReturned": 1 },
          "records": [ {
            "recordId": "rec_28db9639d712c4c61e8e", "personRef": "prs_28db9639d712c4c61e8e",
            "recordType": "sex_offender", "matchState": "dob_match", "unverified": false, "matchConfidence": 0.9,
            "name": { "full": "Exact Sample Match" }, "dob": "1980-01-01", "registrationState": "OH",
            "addresses": [ { "type": "residence", "city": "Sampleville", "state": "OH" }, { "type": "employment", "city": "Sampleville", "state": "OH" } ],
            "offense": { "crime": "Sexual battery", "tier": "Tier III", "convictionDate": "2004-05-12" },
            "source": { "jurisdiction": "OH", "registryName": "Sandbox sample registry (OH)",
                        "recordUrl": "https://offendersearch.app/docs/sandbox", "lastCheckedAt": "2026-10-01T00:00:00+00:00" }
          } ]
        }

Agent:  Yes. 58 registries were searched and all 58 answered completely. One record matched
        on name and full date of birth (matchState dob_match), so this is an identification,
        not a namesake: Exact Sample Match, born 1980-01-01, registered in Ohio, Tier III,
        offense "Sexual battery", convicted 2004-05-12, with a residence and an employment
        address in Sampleville, OH. Source: https://offendersearch.app/docs/sandbox (last
        checked 2026-10-01). Please confirm on that registry page before acting. This is a
        public-records lookup, not a consumer report, and must not be used to decide a hire,
        a tenancy, credit or insurance.

Three other sandbox scenarios are worth running through the same agent before you trust it: "Clear Visitor" (a complete answer, zero records, and the agent must say "58 searched, none matched", not "clean"), "Exact Match" with date of birth 1990-05-05 (zero records because the date contradicts the registry's, which the agent should explain), and "Name Only" (three possible matches the agent must present as unverified). The test a background check integration tutorial turns them into a test suite.

Criminal records are not MCP tools today: the REST fallback for a background check

Criminal records and monitoring are not exposed as MCP tools yet. They are on the same key through the REST API: POST /v1/criminal/search for jail bookings, court cases, custody and warrants, and POST /v1/monitors for a standing watch. Every MCP tool also maps to a REST endpoint, so an agent framework that is not MCP, or a client you cannot give a remote server, gets identical results from a one-function tool that calls POST /v1/search. The two calls below are the whole fallback.

REST: the registry call and the criminal call
# The same call without the MCP server: POST /v1/search on the sandbox
curl -X POST https://sandbox.offendersearch.app/v1/search \
  -H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": {"firstName": "Exact", "lastName": "Match", "dob": "1980-01-01"}, "match": "balanced"}'

# The criminal side, REST only today
curl -X POST https://sandbox.offendersearch.app/v1/criminal/search \
  -H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": {"firstName": "Felony", "lastName": "Record", "dob": "1975-06-15"}, "page": 1, "perPage": 50}'

When you wrap the REST call as your own tool, keep the envelope fields the model needs to reason and cite, and drop the rest. A trimmed result is also a cheaper one for the model to read.

A one-function tool definition over POST /v1/search
// A one-function tool for a framework that is not MCP: the agent gets the same envelope
export const searchOffendersTool = {
  name: "search_offenders",
  description: "Search 58 US sex offender registries by name and date of birth. Returns scored, cited records. Not a consumer report.",
  parameters: {
    type: "object",
    properties: {
      firstName: { type: "string" },
      lastName:  { type: "string" },
      dob:       { type: "string", description: "YYYY-MM-DD" },
      state:     { type: "string", description: "USPS code, optional" },
    },
    required: ["lastName"],
  },
  async run(args) {
    const res = await fetch("https://sandbox.offendersearch.app/v1/search", {
      method: "POST",
      headers: { "X-API-Key": process.env.OFFENDERSEARCH_TEST_KEY, "Content-Type": "application/json" },
      body: JSON.stringify({ query: args, match: "balanced" }),
    });
    if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
    const body = await res.json();
    // Hand the model only what it needs to reason and cite
    return {
      status: body.status,
      sourcesQueried: body.counts.sourcesQueried,
      sourcesIncomplete: body.counts.sourcesIncomplete,
      records: body.records.map((r) => ({
        name: r.name.full, dob: r.dob, matchState: r.matchState, unverified: r.unverified,
        registrationState: r.registrationState, offense: r.offense?.crime, tier: r.offense?.tier,
        recordUrl: r.source.recordUrl, lastCheckedAt: r.source.lastCheckedAt,
      })),
    };
  },
};

The criminal envelope reads the same way, with one addition: every criminal response carries legal.notice ("Not a consumer report…"), and the record's cases[].charges[].disposition is what decides whether a charge became a conviction. The search criminal records by name and date of birth tutorial reads one, and the criminal records API page describes the record kinds. Switching from your own tool to the MCP server later changes nothing the agent sees.

Run a background check from Claude with MCP, step by step

  1. Create an account and ask for MCP access. Sign up (25 free calls, no card), mint an os_test_ key in the dashboard, and ask for MCP access from the MCP server page. The server is in private beta.
  2. Add the config block to your MCP client. Paste the mcpServers block into Claude Desktop, Cursor or any client that supports remote servers with custom headers, with your key in the X-API-Key header. Use the os_test_ key while developing.
  3. Put the agent rules in the system prompt. Not a consumer report; dob_match is the only identification; cite sources[].recordUrl; incomplete means incomplete; batch_search for lists.
  4. Run a sandbox search. Ask the agent about "Exact Match", born 1980-01-01. It calls search_offenders and gets one record with matchState dob_match from the Ohio sample registry.
  5. Read the result the way a program would. status, counts.sourcesQueried and counts.sourcesIncomplete first, then each record's matchState and source.recordUrl. Confirm on the registry page before acting.
  6. Go live. Swap the os_test_ key for an os_live_ key. Each tool call is metered like a REST call: $0.15 a call, $0.11 past 2,000 calls in a month; the map tools at Map API prices.

Sandbox limits are 60 calls a minute and 2,000 a day per test key; an agent that loops search_offenders over a list will hit them, which is one more reason rule 7 says batch_search. Python and Node.js SDKs ship with the first registry release; see SDKs. Until then the raw HTTP calls above are the integration, and the Learn APIs by example hub has the rest of the series.

Background check MCP server: questions developers ask

What is a background check MCP server?

An MCP endpoint that gives an AI agent background-check tools it can call with your API key. This one exposes sex offender registry search across 58 US registries, a batch tool, a coverage tool and five map tools, returning the same scored, cited records as the REST API.

Can I use it from Claude or Cursor today?

Once you have beta access, yes: the config block above works in Claude Desktop, Cursor and any client that supports remote MCP servers with custom headers. Access is requested from the MCP server page. The source is open at github.com/offendersearch/mcp for a local stdio run.

Does the MCP server include criminal records?

Not as tools yet. Criminal records are on the same key through POST /v1/criminal/search, and the REST fallback section shows a one-function tool that gives an agent both today.

How are MCP tool calls billed?

The same as REST: $0.15 a call, $0.11 past 2,000 calls in a month, 25 free calls, no card. batch_search is one search per row. get_registry_coverage is free. The map tools are billed at Map API prices for accounts that have turned the map on. An os_test_ key is never billed.

Is an agent's answer from these tools a consumer report?

No. It is a public-records lookup. An agent must not decide hiring, housing, credit or insurance from it; those decisions run as FCRA orders through the FCRA API, and whether your use needs one is a question for your counsel.

What should the agent say when a name matches but there is no date of birth?

"Possible match, not verified", with the registry link. matchState no_dob_age_year means the registry published nothing to verify against; only dob_match is an identification.

Can I build the same thing without MCP?

Yes. Every tool maps to a REST endpoint; the one-function tool definition above calls POST /v1/search and returns the same envelope. Switching to the MCP server later changes nothing the agent sees.

What this page does not cover