Skip to main content
The protocol-level detail for connecting any MCP client to Lawtte. For what the server is and who can use it, start at the Lawtte MCP server; to get it working in Claude without any of this, use connect Claude.

Endpoint

Call tools act for one Lawtte Studio receptionist. An account that connected before it had a receptionist (for example, a firm signing up from Claude) is signed in; place_call and check_call answer with a tool error naming set_up_calling until it has run, and the read tools report no calls yet. After set_up_calling, the call tools act for the account’s trial receptionist, and after activation for the activated one, without reconnecting.

Discovery

The server uses lazy authentication. initialize, tools/list and any public tool work without a token. Calling a tool that needs an account without a token returns 401 with the OAuth 2.0 Protected Resource Metadata (RFC 9728) pointer in WWW-Authenticate:
A request that sends a token the server can’t verify (expired, revoked or malformed) gets the same 401 on every method, so a client knows to refresh. That document names the authorization server:
Follow the discovery chain rather than hard-coding the authorization server. It is an implementation detail and can move; the resource metadata URL will not.

OAuth 2.1

The authorization server publishes standard metadata at /.well-known/oauth-authorization-server and supports: Because registration is dynamic, a client needs no pre-issued credentials. The flow:
1

Register

POST to the registration_endpoint from the authorization server metadata. You get a client_id back.
2

Authorize

Send the user to the authorization_endpoint with PKCE. Request offline_access if you want a refresh token.
3

The user picks a receptionist

Lawtte shows its own consent screen: the user signs in, chooses one Studio calling agent, and approves the call tools. That choice is stored as a grant against your client_id and their user — it’s what the server later resolves a token to.
4

Exchange and call

Swap the code at the token_endpoint and send the access token as Authorization: Bearer <token> on every MCP request.
One grant is one receptionist. A token never spans two Studio accounts, and a user who wants to switch re-approves. Grants are revocable at any time from Settings → AI connectors, and revocation takes effect immediately.

Connector-key authentication

For clients that can’t do OAuth — a local mcp-remote setup, a script, CI — the server also accepts a Lawtte connector key as a bearer token:
Keys are created by the Studio account owner in Settings → AI connectors → Connector keys, shown once, and stored only as a SHA-256 hash. A key is bound to one Studio agent and carries calls:read, or calls:write (which includes read). Receptionist-setup tools need the receptionist:* scopes, which only an OAuth grant has, so a key gets 403 insufficient_scope for them.
A key can place real phone calls. Keep it in a file or secret store, never in a URL or a command argument — arguments are visible to every process on the machine. See Claude Desktop for the header-file pattern.

Tools

The call tools and the receptionist setup tools are described in plain language in connector tools. The initialize response carries instructions: a short description of Lawtte’s two kinds of agents (the inbound receptionist and outbound calls) and which tools belong to each. See inbound and outbound agents. The schemas, for a client that needs them:
get_calling_setup_status carries readOnlyHint: true. set_up_calling carries readOnlyHint: false, destructiveHint: false and idempotentHint: true: running it again updates the settings and never provisions a second number. On an account without a card it starts a trial — one dedicated calling number, TRIAL_CALLS calls (3 by default), each capped at the shorter of TRIAL_CALL_MAX_MINUTES (5 by default) and the calling agent’s own 3-minute limit. Trial accounts need a confirmed email, get one trial per firm email domain (per address on personal mailboxes), and lose an unactivated number after TRIAL_NUMBER_DAYS (14 by default). place_call, check_call and get_call include trial_calls_remaining for a trial account. Trial calls aren’t metered or billed. Activation on the Lawtte activation page upgrades the same receptionist and keeps its number. place_call carries destructiveHint: true and openWorldHint: true; the read tools carry readOnlyHint: true. A client that surfaces those hints should require confirmation before place_call — it dials a real person. name, phone, consent_confirmed and represented_by_counsel are always required. A call either brings its own objective and recipient_type, or runs a saved outbound agent with agent_id (plus fields for its placeholders) — not both: objective with agent_id is an error, and fields without it is too. Both tools are strict: an unknown argument is an error, not ignored. place_call and check_call share one schema and one rule set, so check_call returning allowed: true means place_call with the same arguments, at the same moment, would dial.
objective and context are rejected outright if they contain a Social Security number, a payment-card or bank account number, a date of birth, a password or one-time code, an API key, or health information (a diagnosis, prognosis, prescription, test result, medical record number or a sensitive condition). They are also rejected if they try to suppress the AI or recording disclosure. Ordinary injury-case language — the accident, the injury, ongoing or finished treatment, medical bills — is fine.
Every call opens with a fixed line the server builds from your firm’s name and your receptionist’s name: “Hi, this is , a virtual assistant calling on behalf of . This call is recorded.” It is sent as the call’s first message and nothing in objective or context can change or remove it. Results come back as a text content block holding JSON. Trailing whitespace and key order aren’t guaranteed; parse it, don’t pattern-match it.

Scopes

Don’t put these scopes in your authorization request — the authorization server only accepts OIDC scopes and rejects the request. Ask for none of them (or only openid / offline_access) and the consent screen offers every scope; the user approves and Lawtte records the grant on its side. Call a tool without the scope for it and you get 403 with a step-up challenge:
Respond by re-authorizing once without a scope (the consent screen then offers every scope), then retry once — don’t loop.
Clients that don’t use scopes keep full access. A connection that asked for none of our scopes — including every connector authorized before scopes existed — is treated as holding all of them. A client that never requested a scope can’t step up to one, so refusing it would break it for no security gain. Connector keys created before key permissions existed likewise carry every scope; newer keys carry only the permission chosen when they were created.

Errors

Business-rule refusals are not HTTP errors. A call blocked by consent, a represented party, the recipient type, the Do Not Call list, the daily cap, the two-hour repeat rule, calling hours, an account that isn’t set up for calling (the text names set_up_calling) or a trial with no calls left (the text names create_activation_link) comes back from place_call as a tool error (isError: true) whose text names the rule that failed and what would change the answer — surface it to the user. check_call returns the same text as { "allowed": false, "reason": "…" }. Bad input gets the same treatment, naming the field to fix. See limits and safety.

Connecting a client

Point it at https://www.lawtte.ai/api/mcp with an Authorization: Bearer <key> header, or let it run the OAuth flow. Good for confirming the tool list and running check_call before you write any code.
For clients that only speak stdio:
The header file holds one line: Authorization: Bearer lawtte_sk_…
Any MCP SDK works. Connect over streamable HTTP to the server URL, attach the bearer token, and call tools/list to confirm you see place_call, check_call, get_call, get_transcript, list_calls and the receptionist setup tools. The list is the same with or without a token; the token decides which calls succeed.

Before you go to production

Run check_call first with the exact arguments you’ll send to place_call. It performs every check — consent, represented party, recipient type, call content, number validation, Do Not Call, quota, calling hours — and dials, reserves and records nothing, so you can prove your integration end to end without calling anyone.
Then read limits and safety. The caps aren’t advisory: they’re enforced server-side before dialing and they fail closed.