Tools
Know which of the up-to-nineteen MCP tools your key can see, what each one takes and returns, and what a refusal looks like.
An MCP client sees up to 19 tools, and only the ones its credential can use. A tool your key cannot use is absent from tools/list, not merely refused. Every call is then checked again against the specific object it touches.
Two layers
Visibility is coarse. It is derived from every object permission your key holds, then intersected with the OAuth scopes when you signed in with OAuth:
- any object granting
readmakes the read tools visible; - any object granting
createorupdatemakes the write tools visible; - any object granting
deletemakes the delete tools visible; list_workspace_usersanswers to thepeople:readsystem permission, the same oneGET /usersrequires. Object access alone never unlocks it.
Authorisation is per call. A visible tool still refuses a record or object your key may not touch with that action, and the bulk tools authorise each id against its own owning object.
Arguments are validated against the advertised schema before any tool runs, the bulk tools included. A mismatch is a standard -32602 invalid-params error listing what was wrong.
Read
Visible with read on at least one object, or mcp:read with OAuth.
| Tool | Inputs | Limits and notes |
|---|---|---|
get_schema | objectNames? (names or ids) | Omit objectNames for every object you can read. Returns attributes, types, required fields and views. Call it before writing to an object. |
search_records | objectId, query?, limit?, offset?, fields?, filters?, sorts? | Default 20 results, maximum 1000. A larger limit is clamped and the response carries a hint. With query the search engine answers; without it the database is read directly, for complete coverage. Paginate with offset; hasMore and nextOffset say when to stop. |
get_record | recordId, fields? | One record by UUID. Prefer it over search_records when the id is known. |
global_search | query, limit?, objectNames? | Across every readable object. Default 20, maximum 1000. No offset: narrow with objectNames or refine the query instead. |
bulk_search | searches[] of { objectId, query?, limit?, offset?, fields?, filters?, sorts? } | 1–20 sub-searches per call, each capped at 1000. For one object, use search_records. |
list_record_drive | recordId, parentFolderId?, query? | The record's folder and file tree as indented text with ids, five levels deep. Pass parentFolderId to drill into a truncated folder, query to find an entry by name. |
read_document | documentId, includeContent? | Metadata, files and extracted text. Files never processed are extracted on demand, so a first read can take a few seconds. contentStatus is ready, ocr_processing (call again shortly) or ocr_unavailable. |
list_workspace_users | query? | Every member's id, name and email, filtered by name or email. Hard limit 20. Needs people:read, or mcp:people:read with OAuth. |
Write
Visible with create or update on at least one object, or mcp:write with OAuth.
| Tool | Inputs | Limits and notes |
|---|---|---|
create_record | objectId, data | Needs create on the object. Call get_schema first for attribute names. |
update_record | recordId, data | Needs update on the record's object. Only the fields in data change. |
bulk_create_records | objectId, records[] | 1–100 records per call, all in the same object. |
bulk_update_records | objectId, then either recordIds[] + data or updates[] of { id, data } | 1–100 per call. One shape or the other, not both. Each id is authorised against its own object; refused ids are reported in errors while the rest are written. Ids from another object than objectId are refused. |
create_folder | recordId, name, parentFolderId? | Omit parentFolderId for a root folder. Folder ids come from list_record_drive. |
attach_file | recordId, fileId, attributeName?, documentId?, folderPath?, properties? | The fileId comes from a REST or CLI upload; there is no upload tool over MCP. A file is consumed by its first attach; a reused fileId links its existing document. documentId appends to an existing pack and is authorised on that pack too. |
remove_file | documentId, fileId | Removes one file from a pack. The document survives; the file stays as a blob. |
update_document | documentId, title?, parentFolderId? | Rename or move within the same record drive. Never touches the files. |
initialize_record_drive_structure | objectName, recordId, presetId | Instantiates a declared folder preset onto the record's drive. |
Delete
Visible with delete on at least one object, or mcp:delete with OAuth. There is no bulk delete over MCP: bulk_delete_records is never exposed, whatever the key.
| Tool | Inputs | Limits and notes |
|---|---|---|
delete_record | recordId | Soft delete, restorable. The usage rules ask the client to confirm with you first. |
archive_document | documentId | Archives the document and its whole file pack. Soft delete, restorable. This is a delete tool, not a write tool: mcp:write alone never shows it. |
Refusals
Two shapes, and the difference tells you which layer refused.
Unknown or withheld tool. Calling a name outside your list is a JSON-RPC error, whether the tool does not exist or your key cannot see it. The two cases are indistinguishable on purpose:
{
"jsonrpc": "2.0",
"id": 7,
"error": { "code": -32602, "message": "Tool delete_record not found" }
}Visible tool, object out of reach. The call runs, and the tool answers with an error result:
{
"content": [
{ "type": "text", "text": "{\"error\":\"Access denied: not authorized to create \\\"contacts\\\"\"}" }
],
"isError": true
}The message is always Access denied: not authorized to <action> "<object>", with <action> one of read, create, update, delete. Invalid arguments never reach this stage; they fail as -32602 first.
Next steps
- MCP — connect a client and read the endpoint's error table.
- OAuth — how scopes narrow the list a signed-in client sees.
- Agent workflow — both refusal shapes, produced live with a read-only key.
- Workspaces and API keys — the roles that decide visibility.