MCP client contract
Audience: MCP clients and the developers building them. This contract is served as the deputise://docs/reference resource and at /developers/mcp. Creating assistants covers the setup workflow.
Connect to https://deputise.ai/mcp using MCP's HTTP transport. The endpoint accepts
POST, GET and DELETE and acts on the signed-in user's behalf. Mobile clients use a separate API. Administrator access is a separate resource and registration.
Authentication
Use OAuth authorization-code flow with PKCE S256 and resource https://deputise.ai/mcp.
Follow the WWW-Authenticate resource-metadata URL on an unauthenticated 401 response.
- Protected-resource metadata:
/.well-known/oauth-protected-resource/mcp. - Authorization server metadata:
/.well-known/oauth-authorization-server/api/auth. - Register a client at
POST /api/auth/oauth2/register, or use an HTTPS Client ID Metadata Document. - Authorize at
GET /api/auth/oauth2/authorize; the browser handles sign-in and consent. - Exchange the code at
POST /api/auth/oauth2/tokenwith the original callback URI and PKCE verifier.
Request mcp and the tool scopes you need. Request offline_access for refresh tokens.
Access tokens last 300 seconds; refresh tokens last 30 days and rotate on refresh, so store each
replacement. Without offline_access, return to browser authorization when access expires.
Revoking a grant prevents further refreshes; already-issued access tokens can remain valid for
their remaining five minutes. MCP tokens and mobile /api tokens are not interchangeable.
A token without mcp gets 403 insufficient_scope. Invalid credentials or a missing account get
401; a suspended account gets 403 and cannot be fixed by reauthentication. A disabled client
gets 403 access_denied.
Only tools whose scopes were granted appear in tools/list. Treat the listed schemas as the
current input/output contract. A field that needs a further scope, listed in the tools table, fails
the call with INSUFFICIENT_SCOPE when that scope was not granted. Tool discovery is private to the credential and may be cached for
five minutes. Documentation resources are public content and may be cached for one hour.
A dynamically registered client's scope ceiling is fixed at registration. Register again to
request a scope added later, such as memory:read and memory:write; CIMD clients use the current
scope list. Registration allows two
requests per minute per IP and answers 429 RATE_LIMITED, or 503 RATE_LIMIT_UNAVAILABLE when
the counter is down. The token endpoint allows 30 requests per minute per IP. Subscription changes
are managed on the website, not through MCP.
A new account has no plan until its holder chooses one on /subscribe and accepts the terms there,
Free included. Until then create_agent fails with SUBSCRIPTION_REQUIRED and
get_account_status reports a CHOOSE_PLAN step.
Callback configuration
Native loopback clients must register with application_type: "native". Registered HTTP
loopback callbacks may use a different port while preserving the host and path. Hosted HTTPS
callbacks match exactly. Use the exact callback selected for that login during code exchange.
Run a callback receiver; a registered URL alone does not create one. If the browser and client run
on different machines, forward the chosen callback port to the client machine.
Discovery
The initialize result carries instructions that point to the guidance resources. resources/list names seven: creating-agents, reference, account-setup, sharing, escalations, training and using-a-shared-assistant, each at deputise://docs/<slug>. prompts/list offers a workflow prompt only when the grant holds the scopes it uses:
| Prompt | Scopes |
|---|---|
set_up_assistant |
assistants:read, assistants:write, facts:read, facts:write |
share_assistant |
assistants:read, invitations:write |
triage_escalations |
escalations:read, escalations:write |
improve_assistant |
assistants:read, assistants:write, facts:read, facts:write, escalations:read |
Tools
Read the descriptions and input/output schemas returned by tools/list before calling a tool.
The shared read bucket is 60 calls per minute across every tool that uses it. Conversation turns
are also capped at 300 per day; a turn over that cap fails with DAILY_ALLOWANCE_EXHAUSTED and a
retryAfterSeconds that runs until the allowance renews.
| Tool | Area | Scope | Limit |
|---|---|---|---|
list_chats |
Chats | chats:read |
Shared read bucket |
chat_status |
Chats | chats:read |
Shared read bucket |
chat_thread |
Chats | chats:read |
Shared read bucket |
start_chat |
Chats | chats:write |
10 turns/minute and 300 turns/day |
send_message |
Chats | chats:write |
12/minute when the caller is the assistant's creator answering an escalation in the thread, otherwise 10 turns/minute and 300 turns/day |
list_agent_types |
Agents | assistants:read |
Shared read bucket |
list_my_agents |
Agents | assistants:read |
Shared read bucket |
create_agent |
Agents | assistants:write, plus facts:write to send facts or topics and profiles:write to send communicationProfileId |
Subscription and assistant-count limits; idempotencyKey makes a retry return the assistant the first call created; topics creates topics with their facts and rules |
get_agent_configuration |
Configuration | assistants:read |
Shared read bucket; owner only |
update_agent_configuration |
Configuration | assistants:write |
Service validation; owner only |
update_agent_settings |
Agents | assistants:write |
Paid-plan requirement for Premium |
share_agent |
Agents | invitations:write |
10 invitations/minute; a repeat for the same assistant and email while pending returns alreadyPending: true and sends no second email |
get_agent_facts |
Facts & profiles | facts:read |
Shared read bucket |
set_agent_facts |
Facts & profiles | facts:write |
Fact-count limit for the assistant type; at most five pinned |
get_agent_topics |
Facts & profiles | facts:read |
Shared read bucket |
set_agent_topics |
Facts & profiles | facts:write |
At most 15 topics; unique names; topic instructions count toward the assistant type's instruction limit |
list_communication_profiles |
Facts & profiles | profiles:read |
Shared read bucket |
set_communication_profile |
Facts & profiles | profiles:write |
2 new profile forks/minute; selecting an existing profile consumes no fork |
set_recipient_profile |
Facts & profiles | profiles:write |
Service validation |
list_escalated_questions |
Escalations | escalations:read |
Shared read bucket |
answer_escalation |
Escalations | escalations:write |
12 replies/minute; save keeps the answer as memory or a fact in the same call |
save_escalation_answer |
Escalations | escalations:write |
30 notes per person; fact-count limit for the assistant type |
list_memory |
Memory | memory:read |
Shared read bucket |
add_memory |
Memory | memory:write |
30 notes per person |
update_memory |
Memory | memory:write |
Service validation |
remove_memory |
Memory | memory:write |
Service validation |
clear_memory |
Memory | memory:write |
Service validation |
respond_to_memory_suggestion |
Memory | memory:write |
30 notes per person |
get_account_status |
Account | account:read |
Shared read bucket |
rate_message |
Feedback | feedback:write |
30 ratings/minute |
report_message |
Feedback | feedback:write |
2 reports/minute |
For the create/configure/facts/profile/share workflow and field guidance, read
Creating assistants. set_agent_facts replaces the
whole set; read first and preserve facts you want to retain. A fact with no topicId and pinned: true applies to every conversation; a fact with a topicId is used only when that topic comes up and cannot also be pinned; a fact sent with neither keeps its placement. set_agent_topics replaces the whole topic set: keep a topic's id (from get_agent_topics or an earlier set_agent_topics) to keep it, omit the id to add a topic, and a held topic left out is deleted with its facts and instructions. It returns each saved topic's id and name for placing facts. More than 15 topics returns TOPIC_LIMIT_EXCEEDED; two names that match case-insensitively, or the reserved name Other things recorded, return DUPLICATE_TOPIC_NAME. create_agent applies the same rules to the topics it creates and returns their ids.
Conversations, escalations and feedback
list_chats returns nextCursor; pass it back as cursor until null. chat_thread accepts
before and returns earlierCursor and cursorIgnored. Cursors are opaque; invalid cursors fall
back to the newest page. totalMessages counts the complete thread, including on older pages.
chat_status checks state without loading messages or marking replies viewed.
list_escalated_questions returns up to limit questions (default 20, max 100), ordered by
urgency then newest first, an opaque nextCursor, and hasMore. Expiry is applied as pages are
read. An unparseable cursor here is refused with INVALID_TOOL_INPUT rather than falling back
to the newest page. Each entry carries status (OPEN or IN_PROGRESS), createdAt and
expiresAt (null once IN_PROGRESS). answer_escalation takes the question's escalationId, can
answer a question without listing it first, and accepts save with the semantics of
save_escalation_answer to keep the answer in the same call; the result carries saved with each
part's outcome. An expired question returns ESCALATION_NOT_ACTIVE, and one whose assistant or
conversation was deleted returns ESCALATION_GONE. A suspended assistant or creator returns
AGENT_SUSPENDED. A save that fails after the answer was sent leaves the answer sent and reports
each requested part as { saved: false, code }.
save_escalation_answer keeps an answer the caller gave, including one sent without save:
memory ({ content }) as a note for the person who asked, fact ({ label, details, topicId })
in the assistant's facts, appended to a case-insensitively matching label or added as a new one, or
both. topicId is required: the id of one of the assistant's topics, or null to hold the fact
everywhere, of which at most five are held. Topic ids come from get_agent_topics, which needs the
facts:read scope. A label that already exists keeps the place it is held in whatever topicId is
sent. At least one part is required. An escalation that is missing, belongs to another creator or
has lost its assistant or conversation returns ESCALATION_GONE. Each part saves independently;
memory and fact in the result are each { saved: true, ... } with the new memoryId or the
fact's label, or { saved: false, code }, for example MEMORY_FULL, FACT_LIMIT_EXCEEDED,
PINNED_FACT_LIMIT_EXCEEDED (a sixth fact held everywhere) or TOPIC_NOT_FOUND (a new label's
topicId is not one of the assistant's topics). Use it once the question has a reply from the
caller. Granting escalations:write therefore lets an agent write memory and facts this way, and
the consent page says so.
send_message returns CONVERSATION_AWAITING_CREATOR while the asker is waiting for the creator.
The creator's reply or expiry of the 48-hour window lifts the lock. A conversation the creator has
ended, or one locked by a report, returns CONVERSATION_CLOSED permanently.
chat_thread messages include id, role, content, createdAt, rating, reported and
memorySuggestion, which is null unless an assistant message offered a memory note. list_chats
entries carry the assistant's agentId.
rating is the caller's own good, bad or null; reported indicates their existing report.
For non-assistant messages these fields are null and false. Pass the message ID to
rate_message with good, bad or none (clear), or to report_message with a trimmed summary
of at most 500 characters. Reports request human review and do not automatically edit or remove
an answer. A report permanently locks the chat: it stays readable, but further messages, ratings
and reports in it return CONVERSATION_CLOSED, so warn the user before reporting.
Inaccessible/non-assistant messages return MESSAGE_NOT_FOUND; reporting the same message again
returns ALREADY_REPORTED.
Memory
Each person an assistant is shared with has up to 30 notes of at most 500 characters, which the
assistant reads in every chat with them. Without recipientId, list_memory, add_memory and
clear_memory act on the caller's own share of agentId; list_chats gives the agentId of an
assistant shared with the caller. With recipientId, the caller must own the assistant.
update_memory and remove_memory take a memoryId from list_memory, on a share the caller
holds or an assistant they own. Notes from add_memory have source MCP. A share the caller
cannot reach returns SHARE_NOT_FOUND, a note that is missing or on a share the caller cannot
reach MEMORY_NOTE_NOT_FOUND, and a full share MEMORY_FULL.
When a reply offers a note, start_chat, send_message and chat_thread include memorySuggestion
({ content, status }) with the assistant message's id. respond_to_memory_suggestion settles a
PENDING offer with action save (optionally with edited content) or dismiss, and returns the
settled suggestion and the saved memoryId; a saved offer becomes a CHAT note, and a settled
one returns MEMORY_SUGGESTION_NOT_PENDING, and a messageId that is not the caller's own assistant message returns MESSAGE_NOT_FOUND. Ask the user before saving. Never save passwords,
codes, or financial details; a health detail that bears on how the assistant helps the person may be
saved. The assistant's owner can read every note.
Results, failures and retries
Successful results include readable content and schema-validated structuredContent.
Failures set isError: true, include readable guidance and put an error object in
structuredContent.error, for example:
{"code":"RATE_LIMITED","requestRef":"a1b2c3d4","retryable":true,"retryAfterSeconds":60}
Switch on code, not prose. Respect retryAfterSeconds when present. A counter outage
(RATE_LIMIT_UNAVAILABLE) has no fixed delay. DAILY_ALLOWANCE_EXHAUSTED means the day's turns are
used up, not that the caller is sending too fast. Validation failures use INVALID_TOOL_INPUT or
INVALID_TOOL_OUTPUT; unexpected failures use INTERNAL_ERROR. INVALID_TOOL_INPUT carries
details, one entry per schema issue with the field's dotted path and its message:
{"code":"INVALID_TOOL_INPUT","requestRef":"a1b2c3d4","retryable":false,"details":[{"path":"email","message":"Invalid email"}]}
Failures an account holder resolves on the website (SUBSCRIPTION_REQUIRED, AGENT_LIMIT_REACHED, PREMIUM_MODEL_NOT_AVAILABLE) carry actionUrl, the page to open.
Uncertain start_chat/send_message outcomes set retryable: false and
recovery: "read-before-retry". List chats after an uncertain start and read chat_thread
before an explicit retry. Do not automatically repeat a mutation.
Respect each tool's side-effect annotations. Reading a thread marks replies viewed; status and escalation reads may expire requests and send notifications; reading credits can initialise a ledger. Starting/sending is not idempotent, and replacing facts, settings or ratings can discard previous values. Annotations describe effects; scopes enforce authorization.
Assistant configuration
Read the whole configuration of an assistant you own before replacing any array in it.
get_agent_configuration takes the assistant's own id and nothing else:
{ "agentId": "agt_6f1b9a4c20e5" }
It answers with the assistant's settings, its additional instructions and its facts. The same ownership and assistant-type limits apply as in the web editor. Image attachments and web search are switched on in the assistant's settings on the website; no tool changes them.
update_agent_configuration changes one owned assistant. Send its id and the fields you are
changing:
{
"agentId": "agt_6f1b9a4c20e5",
"name": "Dad's phone helper",
"description": "Answers Dad's questions about his phone, one step at a time.",
"introMessage": "Hello [Name]. Ask me anything about your phone.",
"escalationGuidance": "Ask me before suggesting anything that costs money or changes his contract.",
"additionalInstructions": ["Give one step at a time and wait for him to say he has done it."]
}
Omitted fields are preserved. additionalInstructions is replaced as a whole when supplied, so
read the current rules first and send back the ones you are keeping. These are the assistant-wide
rules; instructions under a topic count toward the same limit, which the assistant type sets.
Send create_agent an idempotencyKey. Repeating the call with the same key returns the first
assistant with alreadyCreated: true. Without a key, read list_my_agents after an uncertain
response before creating again. A key whose assistant has since been deleted returns
IDEMPOTENCY_KEY_USED; send a new key.