For the complete documentation index, see llms.txt. Every page is also served as Markdown at its URL followed by .md.

standards
ToolsMCP

Tools

Know which of the eight MCP tools your key can see, what each one takes and returns, and what a refusal looks like.


An MCP client sees up to eight 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.

Seven verbs act on references, and one more suggests what to remember:

ToolWhat it does
searchFree text across records, documents, memory pages and, with people:read, members.
queryThe records of one known object, filtered, sorted and paged, or grouped and measured.
readOpens references in a batch: a record, a document, a file, a schema, memory pages, a guide.
writeCreates or updates records.
edit_contentEdits a richtext attribute in place.
organizeStructures a record's drive: attach, detach, folders, move, rename, presets.
deleteDeletes records and archives documents.
propose_memorySuggests one thing worth remembering.

References

A reference names one thing as <kind>:<id>. read, write, delete and organize take references; search returns them as the id of each hit, so a result can be passed straight to read. A bare id is refused with the form to use.

KindIdRead it for
recordthe record UUIDThe record, with its version and, in a read of up to 5 records, its memory page when there is one you can see (include: ["memory"] for a larger batch). include: ["drive"] adds its folder and file tree, up to 200 files: a larger drive answers truncated with its totalFiles and a next cursor that reads the following page.
documentthe document UUID (a folder id is accepted)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 (read again shortly) or ocr_unavailable.
filethe file UUIDThe file's name, mimeType, size and a signed download url with its expiresAt, valid 5 minutes. Read again for a fresh one. File ids come from reading a document.
schemaan object name, or *The object's attributes, types, required fields and views. Each list in a view comes with its source, layout, columns, filters, sorts and grouping. schema:* lists every object you can read. Read it before writing to an object.
memorya page name, or *One memory page. memory:* returns your current profiles: the workspace profile and the profile of the person behind the credential.
userthe id search returnsA workspace member. Needs people:read.
guidea kind nameThe full contract of that kind, with its pitfalls. guide:record, guide:document. guide:record ends with the filter operators each attribute type accepts, the table query's filters refers to.

Other kinds exist in the app (emails, threads, reply drafts, calendar events, skills) but depend on connectors and sandboxes that an MCP client does not have, so they are not served here.

A reference that does not exist and one you cannot see answer the same way. When the id belongs to another kind you can read, the answer names it: read document:<id> instead.

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:

ScopeTools it serves
mcp:readsearch, query, read, propose_memory
mcp:writewrite, edit_content, organize. A batch carries up to 100 records.
mcp:deletedelete, one reference per call.
mcp:people:readsearch and read over the user kind. Alone, this scope serves only those two, restricted to members.

With OAuth, the scopes are those of the connection's access level (Read, Write or Full, see OAuth), so raising or lowering the level in Connected apps changes this list on the connection's next request:

LevelTools
Readsearch, query, read, propose_memory
Writethe Read tools, plus write, edit_content, organize
Fullthe Write tools, plus delete, and search and read over members

For a key without OAuth, the same split follows the object actions it holds:

  • any object granting read makes the read tools visible;
  • any object granting create or update makes write, edit_content and organize visible;
  • any object granting delete makes delete visible;
  • the user kind answers to the people:read system permission, the same one GET /users requires. 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 a batch authorises each reference against its own owning object. In a batch, a refused member is reported on its own while the others succeed.

Arguments are validated against the advertised schema before any tool runs. A mismatch is a standard -32602 invalid-params error listing what was wrong.

Titles and hints

Every tool in tools/list carries a human title and the standard MCP annotations, so a client can label it and decide when to ask you before calling it. The title is served twice, as title and as annotations.title, with the same value, for clients and directories that read only the annotations:

  • readOnlyHint is true for search, query and read, which change nothing;
  • destructiveHint is true for every tool that overwrites or removes rather than adds: delete, write, edit_content and organize;
  • idempotentHint is true for the read tools;
  • openWorldHint is false everywhere: every tool acts on your workspace only.

Read

Visible with read on at least one object, or mcp:read with OAuth.

ToolInputsLimits and notes
searchquery?, kinds?, objects?, limit?, offset?Free text across the kinds your key can read. Returns results, a flat list of { id, title, url?, kind, snippet? } whose id is a reference, and a kinds object with each kind's total, totalIsExact, hasMore and nextOffset. Default 20 hits per kind, maximum 100. objects narrows record hits to those objects. Without query, name exactly one kind to list. query ranks by token relevance, not by value, so its hit list is neither exact nor complete: never count or list who matches a value through it, use query.
queryobject, filters?, sorts?, fields?, limit?, offset?, aggregate?The records of one object you already know. Exact criteria (a status, a select, a date, a location part such as postalCode) go in filters and the database read is complete. Default 20 rows, maximum 1000; paginate with offset, and hasMore and nextOffset say when to stop. A listing's total with totalIsExact: false is only a lower bound, and the response carries a note saying so. To answer how many, pass aggregate: { measure: "count" } with filters. Each row is { ref, kind, title, values? }.
query with aggregateaggregate.measure, aggregate.attribute?, aggregate.groupBy?measure is count, or sum, avg, min, max over a number or currency attribute (required unless count). Exact, over the records your key can read, in one call: use it for totals, breakdowns and averages instead of paging records. groupBy is a status, select, single user or single relation attribute, a date attribute (cut into months), or { attribute, bucket } with a date attribute (or createdAt) and day, week, month, quarter or year. Returns { groups: [{ key, value }], total, truncated, currency? }: a null key groups the records with no value, at most 100 groups, and truncated says more exist. Amounts in more than one currency answer per currency: value and total are null, currency is absent, and each group and the answer carry byCurrency: [{ currency, value }], sorted by code. A listing field (sorts, fields, limit, offset) with an aggregate is refused.
readrefs (1 to 50), include?, fields?, cursor?Each reference becomes a member { ref, kind, title, url?, content }, or { ref, error, code } when it could not be read; members fail independently. include adds drive or memory to a record. fields keeps only those attributes of each record. cursor takes a drive's truncated.next to read its next page, with that one record or folder in refs: a cursor from another drive, or shared by several references or with activity, is refused with INVALID_TOOL_INPUT. A drive with totalIsExact: false and no next holds a folder past the depth cap: read it as document:<id>.
propose_memorylearning, evidenceExcerpt?, record?Suggests one thing worth remembering (learning, up to 1,000 characters) with the user's words that support it; record is the record:<id> it is about, which the caller must be able to read. Writes nothing and returns { success: true, queued: true }, plus record: { id, label, objectName } when one was given: the workspace's memory pass decides where it belongs. At most 30 per person per hour, then SCHEMA_MEMORY_PROPOSAL_RATE_LIMITED. See Skills and memories.

Write

Visible with create or update on at least one object, or mcp:write with OAuth.

ToolInputsLimits and notes
writeaction (create or update), then for create object and items: [{ data }]; for update either items: [{ ref, data }] or refs plus one data1 to 100 records per call. create needs create on the object and call read schema:<object> first for attribute names. update needs update on each record's object and changes only the fields in data. A multi-value relation, document or user attribute sent as a list replaces the stored list; on one record, { "add": [{ "id": "record:<id>", "props"?: { … } }], "remove": [{ "id": "record:<id>" }] } changes only the members it names and keeps the others as stored, so a list too long to read is still written safely. add of a member already linked updates its props when it carries some and leaves them otherwise; the answer's edits lists, per attribute, the members added, updated, removed, unchanged and absent (asked to be removed, not linked). A create, a batch and a multiselect take the full list. With refs plus data, every record gets the same values. Each reference is authorised against its own object; refused members are reported in errors while the rest are written. Memory is written by the workspace only: write and delete on memory answer SCHEMA_SYSTEM_ENTITY_IMMUTABLE. On an artifact, only linkedRecords and visibility are written by hand; any other create, update, delete, move or document attach answers SCHEMA_SYSTEM_ENTITY_IMMUTABLE.
edit_contenttarget (record:<id> and the richtext attribute), command (replace | insert); replace: old_text, new_text, replace_all?; insert: text, after?Edits a richtext attribute in place: only the changed text travels. replace needs old_text to appear exactly once (or replace_all); insert puts text where the unique passage after ends, or at the end; after a whole line, text becomes the next line (past a blank line, a new paragraph), unless it opens with a space or , . ; : ), which continue that line. A passage not found is answered with the closest current line, to copy from. Needs update on the record's object. Several edits of one record in the same step apply one after another. A change made elsewhere in the field by anyone else while the edit runs is kept; only a change to the edited passage refuses the edit with SCHEMA_CONFLICT.
organizeaction, target, and that action's fieldsattach (target record:<id>, one of fileId, file or upload, optional attributeName, folderPath, properties): fileId as file:<id> names a file already in Standards, shown by reading a document or a record's drive, or returned by a REST or CLI upload. file sends the file itself as a base64 data URI, data:<type>;name=<file name>;base64,<bytes>, up to 512 KiB; it carries the x-mcp-file keyword of the MCP file uploads draft. A larger file is announced with upload (fileName, mimeType, size in bytes, up to 50 MiB): the answer is status: "upload_required" with an HTTPS upload.url to PUT the bytes to (with upload.headers, valid two hours) and a fileId as upload:<id>; once the bytes are sent, the same attach with that fileId stores and files them. Only the key that announced an upload can file it. Sent files get the same type check, storage quota and audit as any upload, and the record's update is checked before any byte is stored. Bytes the type check or the quota refuses are removed, so a refused upload is announced again; bytes over 50 MiB are refused before they are read. Bytes still waiting two hours after they were sent (never filed, or sent again after the attach) are removed within half an hour, so an upload is filed within two hours of sending its bytes. When the attach itself is refused after a sent file was stored (an unknown attributeName, say), the refusal's next names it as file:<id>: retry with that fileId rather than sending the bytes again. A file is consumed by its first attach; a reused fileId links its existing document. detach (target file:<id>): removes one file from its pack; the document survives and the file stays as a blob. create_folder (target record:<id>, name, parentFolderId?). move (target document:<id>, parentFolderId) and rename (target document:<id>, title): within the same record drive, folders included, never touching the files. init_structure (target record:<id>, 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. Over MCP, delete takes one reference per call, whatever the key.

ToolInputsLimits and notes
deleterefs (one reference)record:<id> is a soft delete, restorable. document:<id> archives the document and its whole file pack, also restorable. A file: reference is refused with the call to make instead: detach it with organize. The usage rules ask the client to confirm with you first. This is a delete tool, not a write tool: mcp:write alone never shows it.

Renamed tools

Earlier revisions of this server exposed thirty-odd tools named after one operation each. They are gone, and a client holding the old names gets the standard unknown-tool error (-32602, Tool <name> not found). Update stored prompts and client configuration with this table:

Old toolNow
get_recordread with refs: ["record:<id>"]
search_recordsquery (an object and filters) or search (free text)
aggregate_recordsquery with aggregate
global_searchsearch
create_record, bulk_create_recordswrite with action: "create" and items
update_record, bulk_update_recordswrite with action: "update" and items, or refs plus data
delete_recorddelete with refs: ["record:<id>"]
bulk_delete_recordsdelete, one reference per call (the bulk form was never served)
read_documentread with refs: ["document:<id>"]
list_record_driveread with refs: ["record:<id>"] and include: ["drive"]
get_file_urlread with refs: ["file:<id>"]
archive_documentdelete with refs: ["document:<id>"]
attach_fileorganize with action: "attach"
remove_fileorganize with action: "detach"
create_folderorganize with action: "create_folder"
update_documentorganize with action: "move" or "rename"
initialize_record_drive_structureorganize with action: "init_structure"
get_schemaread with refs: ["schema:<object>"] or ["schema:*"]
list_workspace_userssearch with kinds: ["user"]
get_memoryread with refs: ["memory:<name>"], or ["memory:*"] for your profiles
search_memoriessearch with kinds: ["memory"]

edit_content and propose_memory keep their names. edit_content now takes a record:<id> reference as its target.

Tools by credential

The tools each credential receives in tools/list, as measured by the server's own contract test. A narrower credential sees a subset of these.

CredentialCountTools
Every object action and people:read, no OAuth narrowing8delete, edit_content, organize, propose_memory, query, read, search, write
OAuth, mcp:read only4propose_memory, query, read, search
OAuth, mcp:people:read only2read, search

Caching a tool definition

Almost every tool in tools/list carries a revision under _meta:

{
  "name": "read",
  "title": "Read references",
  "inputSchema": {
    "type": "object",
    "properties": {
      "refs": { "type": "array", "items": { "type": "string" }, "minItems": 1, "maxItems": 50 },
      "fields": { "type": "array", "items": { "type": "string" } }
    },
    "required": ["refs"],
    "$schema": "https://json-schema.org/draft/2020-12/schema"
  },
  "_meta": { "new.standards/contract": "2f8b3a1f21aad231" }
}

The list is resolved per credential and per request, so a client holding a cached tool definition cannot otherwise tell a current copy from one whose arguments have since changed. Compare the revision instead: same value, same tool contract — its name and the argument schema it advertises. It is computed from that schema as published, so a constraint the schema does not express — one the tool checks itself, and reports as a refusal on the call — is outside it, as are what a tool returns and what it does.

Two things deliberately do not move it. Narrowing your scopes does not — a client whose list shrinks keeps the revisions of the tools it still sees, because the revision describes one tool and not the catalogue it arrived in. Neither does a reworded tool description, which tracks prompt wording rather than anything a caller can act on. A change to the advertised inputSchema does move it, including a reworded description on one argument: that text is part of the schema, and telling it apart from an argument genuinely named description is not a distinction worth risking a collision over.

A tool whose schema carries no JSON Schema converter is served without the key rather than withheld — one such tool never costs you the rest of the catalogue. That is not the same as a schema whose converter exists and refuses to render it: that failure is the SDK's own, it happens while tools/list is serialised, and it fails the whole call naming what it could not represent. Handle a missing _meta["new.standards/contract"] by refetching that tool rather than trusting a cached copy.

Treat the value as opaque: compare it, do not parse it.

The server declares no cache hints of its own, which leaves the 2026-07-28 defaults in place: ttlMs: 0 and cacheScope: "private" — the list is stale the moment it is served, and no shared intermediary may store it. That is deliberate, because the catalogue is resolved per credential. The revision is what replaces a time-based cache here: refetch whenever you like, and keep your own work only while the value is unchanged.

Refusals

Four 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 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.

Visible tool, refused by the workspace. Any other refusal the call meets (a reference that does not exist or that you cannot see, a malformed reference, a write conflict, a record the platform owns) is an error result whose JSON carries the error code beside the message. The full shape is { error, code, expected?, next? }: error says what is wrong in a sentence or two, code is stable, expected shows the accepted form with an example, and next names the call to make instead when there is one.

{
  "content": [
    { "type": "text", "text": "{\"error\":\"A bare id is not a reference: prefix it with its kind.\",\"code\":\"REFERENCE_INVALID\",\"expected\":\"<kind>:<id>, for example record:<id>. Kinds this step: record, document, file, schema, memory, guide.\"}" }
  ],
  "isError": true
}

Branch on code, not on the message. A batch reports a refused member inside its result, with its own code, and the rest of the batch still runs.

Visible tool, failed on our side. A failure that is not a refusal answers the same way, with no code and a fixed message: {"error":"Internal server error"}. The cause stays in the workspace's logs; retrying later is the only remedy on your side.

Next steps