Metadata-Version: 2.5
Name: tazworks-mcp
Version: 0.1.0
Summary: MCP server for the TazWorks (InstaScreen) background screening API
Author: Exclugo
License: MIT
Requires-Python: >=3.10
Requires-Dist: httpx>=0.25
Requires-Dist: mcp[cli]<2,>=1.30
Description-Content-Type: text/markdown

# tazworks-mcp

An MCP server for the **TazWorks / InstaScreen background screening API** (TazAPI Advanced v2).
It lets you place and track background checks, look up clients, products, applicants, orders and
results, and reach every other part of the API — from a conversation with Claude, without logging
into TazWorks.

This is a standalone package. It shares no code with `exclugo-mcp` and has nothing to do with the
`taz_apploi` / `taz_rm` integrations inside the Exclugo Django app; it talks to the TazWorks API
directly.

---

## This only works from the office

The TazWorks application token is locked to the office IP address. The server runs on your own
laptop and calls TazWorks directly from it, so TazWorks sees whatever network you are on — not a
server somewhere.

In the office it works. From home, a hotspot, or anywhere else, it does not. TazWorks won't say
"wrong IP" when that happens: calls fail looking like an authentication or connection problem, so
check where you are before assuming the token is broken.

---

## Setup

You need one thing: a **JWT from an API application** in the
[TazWorks Developer Portal](https://developer.tazworks.com). Create an application there, copy its
token, and give the same token to everyone who should have access — nobody signs in individually.

> **Use a dedicated application, not the token your production integrations use.** TazWorks rate
> limits per application (5 requests/second, 20,000/day). Sharing a token means heavy use here can
> throttle live ordering, and one compromised laptop means rotating the credential production runs
> on.

Install [`uv`](https://docs.astral.sh/uv/), then add this to Claude Desktop's config
(`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "mcpServers": {
    "tazworks": {
      "command": "uvx",
      "args": ["tazworks-mcp"],
      "env": {
        "TAZ_API_TOKEN": "your.jwt.here"
      }
    }
  }
}
```

Restart Claude Desktop. Ask it "what TazWorks account am I connected to?" — it should answer with
your CRA name. If it errors instead, check you're on the office network before anything else.

### Settings

| Variable | Default | What it does |
|---|---|---|
| `TAZ_API_TOKEN` | *(required)* | The JWT from your Developer Portal application. |
| `TAZ_BASE_URL` | `https://api.instascreen.net` | Set to `https://api-sandbox.instascreen.net` to work against the sandbox. |
| `TAZ_REDACT_SSN` | `true` | Masks SSNs to `***-**-1234` in responses. Any tool takes `full=True` when you genuinely need the real value. |
| `TAZ_CONFIRM` | `always` | `never` disables the confirmation prompt described below. **Unsafe** — only for unattended scripts. |
| `TAZ_TIMEOUT` | `60` | Request timeout in seconds. |

---

## Safety: dangerous operations ask first

The server covers **all 168 documented endpoints**, including ones that delete clients, change
billing and place billable orders. Every endpoint is graded, and the grading is enforced in one
place — it applies the same whether a call comes from a named tool or from the generic `taz_call`.

| Grade | What it covers | Behaviour |
|---|---|---|
| `read` | all 91 GETs | runs |
| `write` | 26 ordinary mutations — create/update an applicant, add notes, set a report decision | runs |
| `danger` | 51 endpoints: **every delete**, anything **billable**, billing and fee changes, user accounts and permissions, client creation, and settings that change how future orders behave | **asks you first** |

When a dangerous call comes up, the server stops before sending anything, looks up what is actually
being affected, and asks:

```
⚠️  Delete Client
Target: Acme Health Services (client 8b521cd1…)
Request: DELETE /v1/clients/8b521cd1-0d0e-4f8a-9c11-2a3b4c5d6e7f
This permanently deletes data and cannot be undone.

Type DELETE to proceed.
```

Deletions and anything that costs money ask you to type a word; the rest are a yes/no. Say no and
**no request is sent**.

If your MCP client can't display a prompt, the tool returns without doing anything and hands back a
one-time token instead — the operation only runs when the tool is called again carrying it. Tokens
are single-use and bound to the exact call, so a confirmation for deleting one client cannot be
replayed against another.

An endpoint that isn't in the bundled catalog is treated as dangerous if it modifies anything, on
the grounds that there's no documentation to judge it by.

---

## What you can ask for

Common things, in roughly the order they come up:

- *"Find the client called Riverside"* → `find_client`
- *"What products can Riverside order?"* → `list_client_products`
- *"What's the status of file number 1710?"* → `list_orders` then `get_order_status`
- *"Which search is holding that order up?"* → `get_order_searches`
- *"Show me the results"* → `get_search_results`, `get_order_results_pdf`
- *"Order a county criminal check for this person"* → `create_applicant` then `submit_order` (asks first)
- *"Who's under monitoring and expiring this month?"* → `list_monitoring`

Anything else — client preferences, disclosures, adverse action settings, fees, users, QuickApp
configuration, tags, admin product catalog — is reachable through the escape hatch:

- `taz_list_endpoints` — browse all 168 endpoints, filter by text, risk or method
- `taz_describe_endpoint` — the full spec for one: parameters, example body, field documentation
- `taz_call` — call it

Browsing the catalog costs no API requests; it ships with the package.

<details>
<summary>All 39 tools</summary>

**Clients** — `find_client`, `list_clients`, `get_client`, `list_client_descendants`,
`list_client_ancestors`

**Products** — `list_client_products`, `get_client_product`, `get_product_search_fields`,
`list_base_products`

**Applicants** — `list_applicants`, `find_applicant`, `get_applicant`, `get_applicant_orders`,
`create_applicant`

**Orders** — `list_orders`, `get_order`, `get_order_status`, `get_order_results_pdf`,
`submit_order`, `add_to_order`, `cancel_order`, `add_order_note`, `set_report_decision`,
`list_order_attachments`, `get_attachment`

**Searches** — `get_order_searches`, `get_search`, `get_search_results`, `cancel_search`,
`add_search_note`

**Monitoring** — `list_monitoring`, `get_monitoring`, `renew_monitoring`, `cancel_monitoring`

**Everything else** — `taz_list_endpoints`, `taz_describe_endpoint`, `taz_call`, `get_api_info`,
`get_usage`

</details>

---

## Testing without being charged

TazWorks provides two canned applicants that incur **no charge even in production**:

| SSN | Name | Result |
|---|---|---|
| `111-22-3333` | Joe Clean | clear |
| `333-22-1111` | Hank Mess | records found |

Create one with `create_applicant` and order against it to exercise the whole chain for free.

---

## Development

```bash
# run the server against a local checkout
uvx --from . tazworks-mcp
```

### Regenerating the endpoint catalog

`src/tazworks_mcp/data/endpoints.json` is generated from the Postman collection TazWorks publishes
at [docs.developer.tazworks.com](https://docs.developer.tazworks.com) — every endpoint's method,
path, parameters, example body and field documentation. Rebuild it when TazWorks revises the docs:

```bash
python scripts/build_catalog.py
```

`--check` verifies the committed catalog is current without writing, for CI. Risk grades are stamped
in during the build from the rule table in `src/tazworks_mcp/risk.py`, which is the one place to
change if you disagree with how something is classified.
