MCP server
Endpoint and authentication
- Streamable HTTP at
https://contents.team/api/mcp. - Authenticate with an org API key as a bearer token, exactly as for the REST API. Each tool needs the scopes listed below, so give an assistant's key only what you want it to do. A
ctk_test_key is a safe place to start. - OAuth access tokens (
cto_) work too. A request without a valid credential gets a 401 whoseWWW-Authenticateheader points at/.well-known/oauth-protected-resource/api/mcp, so OAuth-capable clients find the authorization server on their own. - The server is stateless: every call is a plain POST with JSON back, and there are no sessions to keep alive.
- The tools call the same operations as the REST API, with the same scopes, limits, ids, cents and basis points. A test key's
add_photosuses the fixture pipeline; sendX-Contents-Test-Mode: live-aion the connection to use the real one, as on REST. export_reportwaits up to 20 seconds for the file. If it is not ready by then, call it again with the returnedexportId.
Tools
| Tool | What it does | Scopes |
|---|---|---|
list_claims | List the organization's claims, newest first, with item counts and approved RCV in cents. Filter by externalRef (your own id) or updatedSince; page with cursor. | claims:read |
create_claim | Open a claim with the insured, loss address, loss type and date of loss, and your own externalRef. Returns the claim and its clm_ id. Pass a fresh idempotencyKey for each new claim and reuse it when retrying, so a retry never opens a second claim. | claims:write |
add_photos | Add up to 10 photos to a claim by public https URL, optionally filed by room name. Each photo is analyzed and its items drafted and priced; poll get_claim_status for progress. Pass a fresh idempotencyKey for each batch and reuse it when retrying, so a retry never adds the photos twice. | photos:write |
get_claim_status | How far a claim has got: photos received, queued, analyzing, analyzed and failed; items drafted, awaiting review, approved and rejected; approved RCV, ACV and payment totals in cents. | claims:read |
get_inventory | The claim's line items: room, description, quantity, condition, status, unit cost in cents, and each line's RCV, depreciation (basis points and cents) and ACV. priced and needsReview say what still needs a person. Filter by status, needsReview, priced or room; page with cursor. | items:read |
export_report | Generate the XactContents-format XLSX (default), the PDF report or a photo ZIP for a claim. Waits up to 20 seconds and returns the download link when ready; otherwise call again with the returned exportId. Pass a fresh idempotencyKey for each new export and reuse it when retrying, so a retry never starts a second export. | exports:write |
get_replacements | The replacement finder's offers for one item from its latest search, best first: title, retailer, price in cents, link and image, with proposed marking the finder's pick. Each has an rpl_ id for accept_replacement. | items:read |
update_item | Edit one item the way the app does: name, model, quantity, unitCostCents (integer cents; null removes the price), priceSource, priceSourceUrl, roomId, condition, estimatedAgeYears, notes. Returns the item, revalued. An item still being priced is refused (item_locked). Pass a fresh idempotencyKey for each edit and reuse it when retrying. | items:write |
approve_items | Approve up to 1000 items of a claim, as bulk approval in the app does; only approved items count toward totals and exports. Reports each id as approved, alreadyApproved, locked (still being priced), skipped (with a reason, e.g. unpriced) or notFound. Pass a fresh idempotencyKey for each approval and reuse it when retrying. | items:write |
accept_replacement | Price an item from one of its offers (an rpl_ id from get_replacements) and approve it in one step, as accepting in the app does. The price and quantity come from the offer; if the listing unit count is unclear the call asks you to confirm a quantity (quantity plus quantityConfirmed: true). Pass a fresh idempotencyKey and reuse it when retrying. | items:write |
add_item | Add an item the AI missed, as manual add in the app does: name, optional model, quantity (default 1), unitCostCents (integer cents; omit to add it unpriced), roomId, condition, estimatedAgeYears, notes. Returns the new item. idempotencyKey is required: send a fresh one per item and reuse it when retrying, so a retry never adds (and bills) a second item. | items:write |
delete_item | Remove one item from the inventory, as delete in the app does. To keep a record that an item was ruled out, use reject_items instead. Pass a fresh idempotencyKey and reuse it when retrying. | items:write |
unapprove_items | Send up to 1000 approved items of a claim back to review, as bulk unapprove in the app does. Pass a fresh idempotencyKey and reuse it when retrying. | items:write |
reject_items | Rule out up to 100 items of a claim (not part of the loss, a duplicate), as rejecting in the app does; rejected items stay on the claim but never count toward totals or exports. Pass a fresh idempotencyKey and reuse it when retrying. | items:write |
Retries
create_claim, add_photos and export_report take an optional idempotencyKey argument, the MCP form of the REST Idempotency-Key header with the same rules: 1 to 255 visible ASCII characters, kept for 24 hours. Send a new key for each action and the same key when retrying it, and a retry returns the first result instead of making a second claim, batch of photos or export. The same key with different arguments, or on another tool, is a tool error with code idempotency_key_reused; a retry while the first call is still running is idempotency_request_in_progress, so wait a moment and retry. A call that fails frees its key.
Connect a client
Each snippet reads the key from the CONTENTS_API_KEY environment variable. Keep the key out of shared config files and repositories.
Claude Code
claude mcp add --transport http contents-team https://contents.team/api/mcp \
--header "Authorization: Bearer $CONTENTS_API_KEY"Then ask Claude Code to create a test claim called JOB-1042 and show me its inventory. Run claude mcp list to check the connection.
Cursor
{
"mcpServers": {
"contents-team": {
"url": "https://contents.team/api/mcp",
"headers": { "Authorization": "Bearer ${env:CONTENTS_API_KEY}" }
}
}
}Claude Desktop
Claude Desktop starts local servers from its config file, so it reaches a remote server with bearer auth through the mcp-remote bridge. Add this to claude_desktop_config.json and restart the app.
{
"mcpServers": {
"contents-team": {
"command": "npx",
"args": [
"-y", "mcp-remote", "https://contents.team/api/mcp",
"--header", "Authorization:Bearer ${CONTENTS_API_KEY}"
],
"env": { "CONTENTS_API_KEY": "ctk_test_..." }
}
}
}Custom connectors on claude.ai and ChatGPT sign in with OAuth rather than a key. They work through OAuth apps, which are in preview; see Authentication.
ChatGPT and the OpenAI API
From your own code, the OpenAI Responses API connects to remote MCP servers and passes the key in a header:
from openai import OpenAI
import os
client = OpenAI()
response = client.responses.create(
model="gpt-5",
tools=[{
"type": "mcp",
"server_label": "contents_team",
"server_url": "https://contents.team/api/mcp",
"headers": {"Authorization": f"Bearer {os.environ['CONTENTS_API_KEY']}"},
"require_approval": "never",
}],
input="List my five newest claims and how many items each has.",
)
print(response.output_text)Good habits
- Give each assistant its own key, so you can see its activity and revoke it alone.
- Leave approval on for the write tools (
create_claim,add_photos,export_report,add_item,update_item,delete_item,approve_items,unapprove_items,reject_itemsandaccept_replacement) in clients that ask before calling a tool. - An assistant drafts, a person approves. Give a key
items:writeonly when a person signs off on what the assistant approves; without it, items still need a reviewer in Contents.team.