MCP
MCP uses the same operations as the API. The hosted endpoint is https://app.adoor.halmoslabs.com/mcp. Send Authorization: Bearer with a connection credential. These pages are public: the docs and the agent index. OAuth metadata service_documentation and the MCP server instructions point at those two URLs.
query, execute, context, history, undo, export, and request_access are always listed. Workspace and space tools, including spaces_create, spaces_archive, and spaces_unarchive, are always listed too. Sharing, organizing, billing, and account changes are separate tools, one per operation. import is listed for ChatGPT only. Claude, Grok, Cursor, and Muse do not see it. A tool that needs access you don't have returns an error that says what to request with request_access. Grants are checked when the tool is called. Each heading below is one tool. Listing sessions, revoking a session, signing out everywhere, changing email, and deleting the account stay on the website and the CLI. They are not MCP tools.
Call context to see the signed-in account email and every workspace this connection can open, with names and slugs. Call whoami for a stable account id, the account name, the email, and a nickname of the account and the workspace. The id does not change when the connection is refreshed, reconnected, or its access changes. Pass workspace as an id or slug on any tool to use one of those workspaces. Workspaces belong to the account chosen at sign-in. To use a different account, reconnect and sign in as it. A name that is not listed does not exist. The server says so, lists the current tools, and tells the client to call context and to refresh the connector's tool list. ChatGPT keeps the tool list for an existing conversation: refresh the connector in app settings, then start a new chat. Codex, Claude, and Cursor pick up tools/list on a new session.
Federations and consent stay on the website. open_checkout and open_billing_portal return a link the user opens. Checkout records a claim on the workspace billing account, and either call can create a Stripe customer and store its id. No card data is returned, this call makes no charge, and the plan does not change until a webhook. export returns a download link for the whole workspace.
Each tool accepts an optional workspace id or slug. The call uses that workspace only when this connection can open it. Any other value is the same error as a missing workspace. workspaces_create accepts that selector and an optional account id. The new workspace belongs to the signed-in account and is not placed inside the selected workspace. A different account id creates nothing. There is no organization. The text content and the structured result are the same JSON, with a one-line summary before the JSON. A link in a tool result is url plus kind. approvalUrl and deliveryUrl are the same link. A result with status needs_human is not finished until a person opens url. When a credential field was removed, secretsOmitted names it. User table columns are not removed.
Organizing data
A workspace is one project, team, or app. Sharing and permissions belong to that workspace. Call context first. Reuse a workspace that already matches the project and pass workspace with its slug. Writing in the default space is fine when it is the only user space, or when the plan cannot add another space. The default space starts named General; a renamed default keeps its name. When another space is possible, make a dedicated space for the project and pass spaceId.
When context.fullAccess is true, create the workspace and the space yourself, unless the workspace is already at its space limit and the only user space is the default: then use that space and do not create another. Otherwise call the workspace or space tool; if it returns an error, call request_access with the capability it names. Only the signed-in user can choose Allow everything on the consent screen. Allow everything is the preselected choice and includes account, member, space, workspace, and billing changes. Regular data handling can read, write, import, undo, and history in one space. It cannot drop tables or wipe undo history. It cannot read another space, and a field its grant leaves out stays denied. It refuses account settings, members, invites, billing, deleting workspaces, and creating a space. Cautious can read that one space. This connection cannot upgrade itself. request_access does not create a workspace or a space. It asks for more authority in a workspace this connection can already open. moreSpaces asks for a space the connection cannot see. The approving person chooses the space.
A space groups related tables inside a workspace. CREATE TABLE with no spaceId lands in the default space, which starts named General. Pass spaceId (a space id or its short name) to put a table in an existing space, and keep a project's tables together. Writing in that default space is fine when it is the only user space, or when the plan cannot add another. When another space is possible, make a dedicated space for the project.
Name the workspace with a short project name. Table and column names use lowercase letters, numbers, and underscores, start with a letter, and are at most 63 characters. Table and column comments are not stored, so the name says what the data is.
When the only user space is the default space, the context message says writing there is fine. When the plan can add a space, it suggests a dedicated space. When full access is at the space limit, the message names that default space and says no new space was created. A CREATE TABLE that lands in the default space still commits, and the result includes hint.
| Tool | Class | Grant |
|---|---|---|
query | Read | select on the tables the SQL touches |
execute | Write. DELETE, TRUNCATE, and DROP are sensitive writes | a write action; create for schema changes |
context | Read | the connection’s own grant; no extra capability |
history | Read. Row images are sensitive | You can read and inspect your own changes. Someone else's row images need recover. Lists omit read-only queries unless reads is true |
undo | Sensitive write | Write on the columns the change touched. You can undo your own changes. Another connection's changes need recover |
import | Write. The download is open-world. Rows are not inserted | a write action, and the client is ChatGPT. Other clients do not see it |
request_access | Write of a pending request only. The grant does not change | none |
export | Read. The snapshot is a download | always listed. export plus unrestricted workspace read |
invite_member | Sensitive write. Open-world | always listed. manage_members |
resend_invitation | Sensitive write. Open-world | always listed. manage_members |
revoke_invitation | Sensitive write | always listed. manage_members |
update_member | Sensitive write | always listed. manage_members |
remove_member | Sensitive write | always listed. manage_members |
leave_workspace | Sensitive write | always listed. No management capability. The last owner is refused |
create_connection | Write | always listed. manage_connections |
revoke_connection | Sensitive write | always listed. Same authority as revoking a connection on the website |
update_connection | Sensitive write | always listed. manage_connections |
list_access_requests | Read | always listed. inspect_access or manage_connections |
resolve_access_request | Sensitive write | always listed. manage_connections. This connection cannot approve its own request |
rename_workspace | Write. Not idempotent | always listed. manage_workspace |
rename_space | Write. Not idempotent | always listed. manage_spaces |
move_table | Sensitive write | always listed. manage_spaces |
add_reference | Write | always listed. Create-table access in both spaces |
remove_reference | Sensitive write | always listed. Create-table access in both spaces |
list_references | Read | always listed |
get_billing | Read | always listed. manage_billing |
get_usage | Read | always listed. inspect_access |
open_checkout | Write. Open-world. Returns a link the user opens | always listed. manage_billing |
open_billing_portal | Write. Open-world. Returns a link the user opens | always listed. manage_billing |
trim_history | Sensitive write. Can't be undone | Owner only. Nothing newer than 30 days is deleted. Regular access cannot wipe undo history |
get_account | Read | always listed. account_profile |
rename_account | Write. Not idempotent | always listed. account_profile |
list_invitations | Read | always listed. account_workspaces |
accept_invitation | Write | always listed. account_workspaces |
decline_invitation | Sensitive write | always listed. account_workspaces |
spaces_create | Write | always listed. manage_spaces |
spaces_archive | Write. Idempotent. Rows stay | always listed. manage_spaces |
spaces_unarchive | Write. Idempotent | always listed. manage_spaces |
whoami | Read | always listed. No extra capability. id is stable for this account |
workspaces_create | Write | always listed. account_workspaces. Optional workspace selects the acting connection. Optional account must be the signed-in account |
Shared limits and errors
A verified credential is limited to 300 requests per minute. An address without that credential is limited to 6000 unauthenticated requests per minute. A refusal is 429 RATE_LIMITED with Retry-After. A full waiter list is 503 QUEUE_FULL.
SQL runs at most 4 queries per workspace and 2 per account, with 32 waiting per workspace for up to 10 seconds. Past that, the call is 429 QUERY_BUSY. A statement stops at 30 seconds (STATEMENT_TIMEOUT). A free-plan write stops at 2 seconds and a free-plan read at 5 seconds. An execute script stops at 60 seconds (SCRIPT_TIMEOUT). Free-plan writes are 1 MB (1000000 bytes, not MiB) per minute per workspace, with a 250 KB burst. Paid workspaces are 64 MB per minute with an 8 MB burst. Crossing the write budget is 429 RATE_LIMITED. A single statement larger than the per-minute allowance plus the burst is refused without Retry-After, because waiting cannot refill past a full bucket. A write that runs spends those bytes, including one that commits.
| Code | When |
|---|---|
UNAUTHORIZED | The credential is missing, revoked, or expired. |
FORBIDDEN | The grant cannot perform the call. |
INVALID_REQUEST | The input does not match the tool schema. |
NOT_FOUND | The workspace, row, transfer, or event is not visible. |
RATE_LIMITED | A request, write, invite, or export-parse limit refused the call. |
QUERY_BUSY | The workspace SQL queue is full. |
PAYLOAD_TOO_LARGE | The body, file, or normalized export is over its ceiling. |
CONFLICT | expectRowCount did not match, or a concurrent edit won. |
PLAN_LIMIT_EXCEEDED | A free-plan row, space, or table ceiling refused the change. |
STORAGE_QUOTA_EXCEEDED | The workspace storage quota refused the write. |
query
Reads rows with PostgreSQL SELECT, including joins, expressions, CTEs, and aggregates, or exports with COPY ... TO 'adoor://new'. Nothing in the tables is written. COPY TO needs ordinary read permission on the tables in that query, including one table or a filtered subset. It does not need the export capability. The whole-workspace snapshot is the export tool. It returns a download link.
Inputs. sql (required), optional params (at most 65535 values and 8 MiB), limit (1 to 1000), cursor, and workspace. atomic is ignored. dryRun is rejected.
Outputs. rows, rowCount, truncated, nextCursor, and command. A COPY TO row includes download_url. The summary states how many rows were returned or exported.
Example.
{ "sql": "SELECT name, company FROM contacts ORDER BY id", "limit": 100 }
{ "rows": [{ "name": "Leah Chen", "company": "Elm Studio" }], "rowCount": 1, "truncated": false, "nextCursor": null }
Side effects. A SELECT has none. COPY TO stages a download that expires six hours after creation. The staged file is deleted when the download finishes.
Errors. FORBIDDEN when a named table or field is outside the grant. STATEMENT_TIMEOUT at 30 seconds (5 seconds on a free-plan read). RESULT_LIMIT_EXCEEDED above 16 MiB. The shared HTTP and SQL limits apply. Every page contains at most 1000 rows. MCP defaults to 100 when limit and a constant SQL LIMIT are both absent. A constant LIMIT of 1000 or less is kept. A page without ORDER BY a unique key can repeat or skip rows when other writes commit.
execute
Runs PostgreSQL against this connection’s own ADoor database. This is not a shell and it does not run code outside that database. MCP rejects SELECT and COPY TO; query reads and exports. HTTP and the CLI accept SELECT in execute.
Accepted writes are INSERT, UPDATE, DELETE, managed CREATE TABLE, TRUNCATE or DROP TABLE of one user table, DROP COLUMN, RENAME of a table or field, ALTER COLUMN TYPE, ADD UNIQUE or CHECK, DROP CONSTRAINT of a field rule, SET or DROP NOT NULL, SET or DROP a literal DEFAULT, a plain CREATE INDEX or DROP INDEX, and COPY FROM an uploaded ADoor file. DELETE, TRUNCATE, and DROP overwrite or remove data, so this tool is a sensitive write. The tool stays in tools/list when the permission list has no write action. The call then returns an error that says to call request_access for insert or update, and create for new tables. Schema statements also need create.
Inputs. sql (required), optional params, idempotencyKey, atomic, dryRun, expectRowCount, limit, cursor, spaceId, and workspace. spaceId is a space id or short name this connection can already see. Omit it and CREATE TABLE lands in the default space, which starts named General.
Outputs. command, rowCount, and for a script batch, failedAt, completed, and committedStatements. CREATE INDEX returns the index id. A CREATE TABLE in the default space includes hint. The table is still created. A rolled-back statement has status rolled_back, committed false, and no event id.
Example.
{ "sql": "INSERT INTO contacts (name, company) VALUES ('Leah Chen', 'Elm Studio')" }
{ "command": "INSERT", "rowCount": 1 }
Side effects. The default is one transaction: a failure rolls the script back. atomic: false commits each statement on its own and names the statements that already committed. dryRun: true runs the transaction and rolls it back, so nothing is committed and no history event is kept. dryRun rejects schema changes, COPY, TRUNCATE, DROP, atomic: false, and an idempotencyKey. The same idempotency key is only for the exact same script. A completed COPY FROM deletes the staged bytes. A large write keeps one snapshot and can be undone. A history list of that change may say undo may conflict until undo checks every row.
Errors. STATEMENT_TIMEOUT (30 seconds; 2 seconds for a free-plan write). SCRIPT_TIMEOUT at 60 seconds. A deadlock sets retryable. RULE_VIOLATED_BY_EXISTING_ROWS names how many rows break a new rule and returns no values. SCHEMA_IN_USE when a multi-column rule blocks a drop. FORBIDDEN without the write or create grant. Write-byte RATE_LIMITED and the shared HTTP and SQL limits apply.
context
Reads the signed-in account email, every workspace this connection can open, with names and slugs, this connection, the current workspace, the spaces and tables it can see, field rules, indexes, permissions, and the expiry clocks. The result says how to switch workspace: pass workspace with a slug on any tool. When this connection cannot write, it says to call request_access for insert or update. When this workspace is personal, or General is the only user space, the message says to ask the user for a project workspace before creating tables. It does not change data. No extra capability is required. connections and people are included only when this connection can see them. No secret is included. A rule or index that names a hidden field is omitted. The account is name and email only.
Inputs. Optional workspace.
Outputs. account (name and email, or null when this connection has no signed-in account), connection, workspace, workspaces, spaces, tables, permissions, capabilities, expiresAt, effectiveExpiresAt, and expiry. workspaces lists only workspaces this connection can open. effectiveExpiresAt is the earliest of this connection’s clock, ancestor connections, the access credential, and a browser sign-in when the call has one. A permission grant has no clock. An OAuth access token lasts 1 hour and is refreshed. A truncated list sets tablesTruncated, spacesTruncated, connectionsTruncated, or peopleTruncated and includes the first 1000 items. omitted names a field this connection cannot see.
Example.
{}
{ "account": { "name": "Ada", "email": "ada@example.com" }, "workspace": { "name": "Acme", "slug": "acme" }, "expiresAt": null, "effectiveExpiresAt": "2026-10-20T12:00:00.000Z", "tablesTruncated": false }
Side effects. None.
Errors. FORBIDDEN is recorded in omitted for a list this connection cannot see, rather than failing the whole call. The shared HTTP limits apply. This tool does not run SQL.
history
Reads retained changes, newest first. Passing id inspects one change, including its row images. You can read and inspect your own changes. Listing does not need recover. Someone else's row images, and undo marks for someone else's changes, need recover. Row images are sensitive: they contain stored values. They still need unrestricted read of that table. Events this connection cannot see are skipped before the page is filled. A list omits read-only queries. Pass reads true to include them. Those query events stay in the usage ledger either way. Inspecting one event by id still returns a query event. Pass table for that table's current name. The match uses that live table's id, so a dropped table that used the same name stays out. Pass tableId to list one table, including one that was dropped. A missing table, a dropped table passed by its old name, and a table this connection cannot see are all Table not found. For an event of at most 64 retained rows, canUndo is false when a later edit of the same row would be overwritten. It does not read the live row. The comparison uses the columns that were in the event image, by attnum, so a column added later does not by itself reject the undo, including a default that is different on each existing row. A later change to that column still rejects undoing the insert, because that would delete the row. A DROP TABLE event stays in the workspace list, in that space, and under the dropped table's tableId, including when the same name exists in another space. When canUndo is false because the grant lacks delete, undoReason names that permission. A change above the undo limit has canUndo false, and undoReason repeats note. A stored number and the same value stored as text compare as one value, matching undo. A list reads the row count from the usage ledger and does not read every row image. An event with more than 64 retained rows reads one sample image for a schema block. When that sample does not block undo, canUndo is unknown, because a later edit of another row can still conflict. Undo checks every row. A large write keeps one snapshot and can be undone. A history list of that change may say undo may conflict until undo checks every row. Inspecting one event returns a page of row images.
Inputs. Optional id, before, limit, cursor, reads, table, tableId, and workspace. A list uses before and limit (1 to 100, default 100). With id, limit (1 to 100, default 50) and cursor page that change's rows. cursor is the previous nextCursor. id cannot be combined with before, table, or tableId. reads is ignored when id is set.
Outputs. A list has activity, rowCount, limit, truncated, and nextCursor. truncated is false only when no further visible event remains. Each event includes connectionId and actorLabel. actorLabel is the connection name, its access level, and the first 8 characters of the connection id. An inspection adds inspected: true, changes for this page, changeCount for every retained row, limit, truncated, and nextCursor.
Example.
{ "limit": 20 }
{ "activity": [{ "id": "1042" }], "rowCount": 1, "limit": 20, "truncated": false, "nextCursor": null }
Side effects. None.
Errors. INVALID_REQUEST when id is combined with before, table, or tableId, or when cursor is passed without id. NOT_FOUND with Table not found when table or tableId names a missing table or a table this connection cannot see. FORBIDDEN when the event is another connection's and the grant lacks recover, or when this connection cannot read the whole table. The shared HTTP limits apply.
undo
Reverses one retained change, or several when every event was made by this connection. Ownership is the connection. A later grant change does not move earlier events to another connection. The current grant still has to allow the write. This overwrites current values, so it is a sensitive write. You can undo your own changes. That needs write access to the columns that change touched. Undoing an insert requires the delete permission. It does not need read access to the whole table or to every column, and it does not need the recover capability. Another connection's changes, a human's changes, and a batch that mixes both still need recover. For a caller without recover, an event that belongs to another connection looks the same as an event that does not exist. A mixed batch is refused before anything is written and does not name those events. A later edit that would be overwritten is left unchanged and nothing is committed. A column added later is left in place, including a default that is different on each existing row. Undoing an insert still stops when a later change wrote that column, because the row would be deleted. The tool stays in tools/list without recover. A caller that can only read is told to call request_access with that capability.
Inputs. id (one event id from history) or ids (more than one). Optional workspace. ids are reversed newest first.
Outputs. The restored change. A conflicting later edit returns without committing.
Example.
{ "id": "1042" }
Side effects. The named change is reversed and a new history event records the undo. Other rows stay as they are.
Errors. FORBIDDEN without write access to the columns that change touched, or without the delete permission when the change is an insert. A caller that can only read gets that same refusal for another connection's event and for an event that does not exist. The message does not name event ids. NOT_FOUND when a caller with write access, or with recover, asks for an event that is not available: a missing event, or, without recover, another connection's event. History does not list that other connection's event, and undo uses the same message as a missing id. The message does not name event ids. If history did list the event, undo would return requires recover instead. CONFLICT when a later edit would be overwritten. UNDO_TOO_LARGE when a retained change has no snapshot and exceeds 10,000 rows or 80 MiB of images, or when several per-row changes together exceed 1,000 rows or 8 MiB. A single change within that limit is restored in chunks inside one transaction. Changes that are each undoable but too large together say to undo them separately, and each stays undoable. A write above the single-change limit still commits and includes a warning to export first. UNDO_TIMEOUT when the restoration runs out of time. Nothing is changed, and the error says it took too long. Write-byte RATE_LIMITED and the shared HTTP and SQL limits apply.
import
Stages bytes from a client file object so COPY FROM in execute can load them. Rows are not inserted. The file object has download_url and file_id, and may include mime_type and file_name.
import accepts ChatGPT files from files.oaiusercontent.com only. A Claude, Grok, or Muse URL is refused with FILE_URL_NOT_ALLOWED and is not fetched. A Claude or Grok conversation export inside that ChatGPT file is normalized under the same ceilings: a download at or under 32 MiB, normalized JSON at or under 64 MiB, 100,000 conversations, and 1,000,000 messages. A ChatGPT export, CSV, and any other unrecognized file are staged unchanged. The tool is a write because it stores staged bytes, and it is open-world because it downloads that ChatGPT file. It is listed for ChatGPT whether or not the grant includes a write. A missing write returns an error that says to call request_access, and the file is not downloaded. Claude, Grok, Cursor, and Muse do not see it. Calling it anyway returns FILE_URL_NOT_ALLOWED and names ChatGPT as the supported client. It needs no named capability.
Inputs. file.download_url, file.file_id, optional file.mime_type, file.file_name, and optional workspace.
Outputs. transfer_id, an adoor:// URI, bytes, and sha256. A recognized export also has format (claude or grok), conversations, messages, normalized, and original_sha256.
Example. A ChatGPT attachment:
{ "file": { "download_url": "https://files.oaiusercontent.com/file", "file_id": "file_abc", "file_name": "contacts.csv" } }
{ "transfer_id": "8c6…", "uri": "adoor://8c6…", "bytes": 42, "sha256": "…" }
A Claude or Grok file URL returns:
{ "error": { "code": "FILE_URL_NOT_ALLOWED", "message": "import accepts ChatGPT files from files.oaiusercontent.com only. Claude, Grok, and Muse URLs are not accepted and are not downloaded." } }
Side effects. The bytes are staged for six hours, or until COPY FROM finishes or SELECT adoor_file('close', '<transfer_id>') deletes them. Table rows are unchanged. Redirects are not followed. The signed URL and the upstream file id are not stored.
Errors. FILE_URL_NOT_ALLOWED for every host other than files.oaiusercontent.com. FILE_DOWNLOAD_FAILED when the attachment does not return the file. PAYLOAD_TOO_LARGE when the file or the normalized export is over its ceiling. TRANSFER_QUOTA when in-flight bytes are at the ceiling. A named export allows one parse in flight and six parse starts per minute per workspace and per account; the next one is RATE_LIMITED. The shared HTTP limits apply.
request_access
Creates a pending request for more authority and returns an approval link. The request grants nothing. Existing permissions and capabilities are kept. Approval can only widen access. No capability is required to call it, and a read-only grant still sees the tool.
Inputs. At least one of permissions, capabilities, or moreSpaces. Optional reason (at most 500 characters) and workspace. Name a space only when this connection can already see it. For a space it cannot see, moreSpaces carries the per-space grant and no space id. The approving person chooses the spaces and can narrow the added grant.
Outputs. url with kind approval. approvalUrl is the same link. addedPermissions and addedCapabilities name only what this call adds. permissions is the full grant approval would apply. A duplicate pending request keeps the previous link and reason.
Example.
{ "capabilities": ["recover"], "reason": "Undo a change another connection made." }
{ "url": "https://app.adoor.halmoslabs.com/#/w/acme/access/…", "kind": "approval", "status": "pending" }
Side effects. One pending request is stored. The connection’s grant does not change until a person approves it on the website. query, execute, context, history, undo, export, and request_access are always listed. Workspace and space tools, including spaces_create, spaces_archive, and spaces_unarchive, are always listed too. Sharing, organizing, billing, and account changes are separate tools, one per operation. import is listed for ChatGPT only. A call without the grant names request_access.
Errors. INVALID_REQUEST when permissions, capabilities, and moreSpaces are all absent. RATE_LIMITED when this connection already has 3 pending requests; repeating the same request returns its existing link. The shared HTTP limits apply. This tool does not run SQL.
invite_member
Invites a person to this workspace by email. Needs manage_members. This is a sensitive write, and the mail leaves ADoor, so it is open-world. The result has url and no invitation token.
Inputs. email. Optional role (owner, editor, or viewer), permissions, capabilities, and workspace. Omitting role and permissions invites a viewer.
Example.
{ "email": "teammate@example.com", "role": "viewer" }
{ "url": "https://app.adoor.halmoslabs.com/#/w/acme/credentials/…", "kind": "delivery" }
Side effects. Creates a pending invitation and sends mail when the sender is allowed. This call does not grant the caller any new access.
Errors. FORBIDDEN names request_access when manage_members is missing. INVITE_RATE_LIMITED after a burst of 5 invitations, then 1 per minute, 50 per workspace per UTC day (10 for an inviting account younger than 7 days), or a second send to the same mailbox within an hour. A named table that does not exist is NOT_FOUND. The invitation link still works when mail is refused.
resend_invitation
Emails a pending invitation again and replaces its link. The new link expires seven days after this resend. Needs manage_members. The previous link stops working. The result says whether the email went out. No invitation token is returned.
Inputs. id. Optional workspace.
Errors. FORBIDDEN names request_access when manage_members is missing. NOT_FOUND when the invitation is not pending.
revoke_invitation
Revokes a pending invitation so its link stops working. Needs manage_members. No token is returned.
Inputs. id. Optional workspace.
Errors. FORBIDDEN names request_access when manage_members is missing.
update_member
Changes a member’s role, permissions, or capabilities, or the grant on a pending invitation. Needs manage_members. Pass at least one of role, permissions, or capabilities. A pending invitation keeps its link, and the change sends no email.
Inputs. id. Optional role, permissions, capabilities, and workspace.
Errors. FORBIDDEN names request_access when manage_members is missing. NOT_FOUND for a missing table named by a permission.
remove_member
Removes a person from this workspace and revokes that identity. Needs manage_members. This is a sensitive write.
Inputs. id. Optional workspace.
Errors. FORBIDDEN names request_access when manage_members is missing.
leave_workspace
Leaves this workspace. No management capability is required. The last owner is refused. This is a sensitive write.
Inputs. Optional workspace.
Errors. LAST_OWNER when the last owner tries to leave.
create_connection
Creates a child connection in this workspace. Needs manage_connections. The new secret is not returned. The result has url for the signed-in account to open.
Inputs. Optional name, role (app, agent, server, or sandbox), permissions, capabilities, ttlSeconds, expiresAt, profileId, profileVersion, scope (workspace or account), idempotencyKey, and workspace.
Errors. FORBIDDEN names request_access when manage_connections is missing.
revoke_connection
Revokes a connection so it stops working. The same authority as revoking a connection on the website applies. No token is returned. This is a sensitive write.
Inputs. id. Optional workspace.
update_connection
Replaces a child connection’s permissions and optional capabilities. Needs manage_connections. permissions is required. The change stays within the caller’s authority. This is a sensitive write.
Inputs. id, permissions. Optional capabilities and workspace.
Errors. FORBIDDEN names request_access when manage_connections is missing.
list_access_requests
Lists pending access requests in this workspace that this connection can inspect or act on. Needs inspect_access or manage_connections. Nothing is changed.
Inputs. Optional limit, cursor, and workspace.
resolve_access_request
Approves or denies someone else’s pending access request. Needs manage_connections. This connection cannot approve its own request. That case returns the approval link and changes nothing. Optional permissions and capabilities can only narrow the grant. This is a sensitive write.
Inputs. id and decision (approve or deny). Optional permissions, capabilities, and workspace.
Errors. FORBIDDEN names request_access when manage_connections is missing.
rename_workspace
Renames this workspace or changes its personal or team label. Needs manage_workspace. Repeating the same name still writes a workspace event, so this is not idempotent.
Inputs. name. Optional kind (personal or team) and workspace.
Errors. FORBIDDEN names request_access when manage_workspace is missing.
rename_space
Renames a space this connection can see. Needs manage_spaces. The short name stays. Repeating the same name still bumps the revision and writes a space event, so this is not idempotent. A space this connection cannot see is not found, and its name is not returned.
Inputs. id (space id or short name) and name. Optional workspace.
Errors. FORBIDDEN names request_access when manage_spaces is missing. NOT_FOUND when the space is not visible.
move_table
Moves a table into another space in this workspace. Needs manage_spaces. This is a sensitive write. A table or space this connection cannot see is not found, and its name is not returned.
Inputs. table and spaceId. Optional workspace.
Errors. FORBIDDEN names request_access when manage_spaces is missing. NOT_FOUND when the table or space is not visible.
add_reference
Adds a foreign key between two tables in this workspace. Needs create-table access in both spaces. Pass createColumn true to add a nullable source field.
Inputs. table, column, and targetTable. Optional targetColumn, createColumn, and workspace.
Errors. FORBIDDEN names request_access when create-table access is missing. NOT_FOUND when a table is not visible.
remove_reference
Removes a foreign-key reference and keeps the field and the rows. Needs create-table access in both spaces. This is a sensitive write.
Inputs. id. Optional workspace.
Errors. NOT_FOUND when the reference is not visible.
list_references
Lists foreign-key references this connection can see. Pass spaceId to limit the list to one space. Nothing is changed.
Inputs. Optional spaceId and workspace.
get_billing
Reads the current plan, storage allowance, and whether upgrade checkout is open. Needs manage_billing. No card data is returned. Card entry and cancellation stay on the billing page.
Inputs. Optional workspace.
Errors. FORBIDDEN names request_access when manage_billing is missing.
get_usage
Reads usage against the plan caps. Needs inspect_access. period is YYYY-MM. Omit it for the current period. No card data is returned.
Inputs. Optional period and workspace.
Errors. FORBIDDEN names request_access when inspect_access is missing.
open_checkout
Returns a link the user opens for Stripe Checkout of the paid storage plan when this workspace is allowlisted. Needs manage_billing. This call records a checkout claim on the workspace billing account before the link is returned, and clears that claim if Stripe rejects the session. When the workspace has no Stripe customer for the current key mode, this call creates one and stores its id. No card data is returned and this call makes no charge. The ADoor plan does not change until Stripe sends a webhook. This is open-world.
Inputs. Optional workspace.
Errors. FORBIDDEN names request_access when manage_billing is missing. BILLING_NOT_CONFIGURED when Stripe is not set up. No charge is made in that case. A workspace that is not allowlisted is rejected before Stripe is called.
open_billing_portal
Returns a link the user opens for the Stripe Customer Portal, for invoices, payment methods, and cancellation. Needs manage_billing. When this workspace has no Stripe customer for the current key mode, this call creates one and stores its id before the link is returned. No card data is returned and this call makes no charge. The ADoor plan does not change until a webhook arrives. This is open-world.
Inputs. Optional workspace.
Errors. FORBIDDEN names request_access when manage_billing is missing. BILLING_NOT_CONFIGURED when Stripe is not set up. A workspace that is not allowlisted and has no Stripe customer or subscription is rejected before Stripe is called.
tables_deleted
Lists tables the owner deleted in the last 30 days. Each entry has id, name, spaceId, droppedAt, purgeAfter, and rowCount. After 30 days the table is gone. Only the workspace owner can call this.
Inputs. Optional workspace.
Errors. FORBIDDEN when the caller is not the workspace owner.
tables_purge
Permanently deletes one table from Recently deleted, or all of it. Pass id from tables_deleted, or all set to true, not both. Dropped tables and truncated copies stop counting toward the storage quota immediately. This can't be undone. Restore and undo will not bring the data back. Only the workspace owner can call this. This is a sensitive write.
Inputs. id or all. Optional workspace.
Errors. FORBIDDEN when the caller is not the workspace owner. NOT_FOUND when that table is not in Recently deleted, or when Recently deleted is already empty. INVALID_REQUEST when both id and all are sent, or neither is.
tables_restore
Restores one table from Recently deleted. Pass id from tables_deleted. Pass name when the old name is taken. A name clash returns TABLE_EXISTS and changes nothing. Only the workspace owner can call this. This is a sensitive write.
Inputs. id. Optional name and workspace.
Errors. NOT_FOUND when that table is not in Recently deleted. TABLE_EXISTS when the name is already in use. FORBIDDEN when the caller is not the workspace owner.
trim_history
Drops retained history images older than 30 days so the history budget can accept new rows. This can't be undone. Omit table to drop the oldest images older than 30 days until the workspace fits. Name a table to drop that table’s undo images older than 30 days. Nothing newer than 30 days is deleted. Live rows stay. The result is counts, never row values, plus retentionDays and keptRecentRows. Only the workspace owner can trim history. Regular access cannot wipe undo history. This is a sensitive write.
Inputs. Optional table and workspace.
Errors. FORBIDDEN when the caller is not the workspace owner. Regular access gets its own message.
get_account
Reads this account’s id, display name, and email. Needs account_profile. The display name is the only profile field that changes immediately.
Inputs. Optional workspace.
Errors. FORBIDDEN names request_access when account_profile is missing.
rename_account
Changes this account’s display name. Needs account_profile. Email is not accepted here. Repeating the same name still updates the account and writes an audit event, so this is not idempotent.
Inputs. name. Optional workspace.
Errors. FORBIDDEN names request_access when account_profile is missing.
list_invitations
Lists pending invitations addressed to this account’s verified email. Needs account_workspaces. Each row names that workspace. Invitations for any other email are omitted.
Inputs. Optional workspace.
Errors. FORBIDDEN names request_access when account_workspaces is missing.
accept_invitation
Accepts one invitation addressed to this account’s verified email and joins that workspace. Needs account_workspaces. The inviter’s live grant is the ceiling. No invitation token is returned. No email is sent. The membership change stays inside ADoor, so this is not open-world.
Inputs. id. Optional workspace.
Errors. FORBIDDEN names request_access when account_workspaces is missing. NOT_FOUND for an invitation that is missing or addressed to someone else, without another workspace’s name.
decline_invitation
Declines one invitation addressed to this account’s verified email. Needs account_workspaces. This is a sensitive write. A missing invitation, or one addressed to someone else, is not found and does not name another workspace.
Inputs. id. Optional workspace.
Errors. FORBIDDEN names request_access when account_workspaces is missing. NOT_FOUND without another workspace’s name.
export
Exports the whole workspace and retained history as a download. The tool is always listed. It needs the export capability and unrestricted workspace read. HTTP and the CLI return the snapshot inline. MCP returns a download link and does not inline the snapshot.
Inputs. Optional workspace.
Outputs. url with kind download, plus download_url, byte_count, sha256, and expires_at. The snapshot is the file behind that link.
Side effects. Stages one export file. It expires on the same clock as other downloads. One table or a filtered query still uses COPY ... TO 'adoor://new' in query, which needs ordinary read permission and does not need export.
Errors. FORBIDDEN without export or without unrestricted workspace read. The message names request_access when the capability is missing. RESULT_LIMIT_EXCEEDED above 16 MiB.
spaces_create
Creates a space in a workspace this connection can open. Needs manage_spaces. The default space starts named General. Creating a space records read and write on that new space id only.
Inputs. Optional name, slug, and workspace.
Errors. FORBIDDEN names request_access with manage_spaces.
spaces_archive
Archives a space. The space, its tables, and its rows stay. An archived space does not count toward the space plan limit. The default space and the ADoor system space cannot be archived. Needs manage_spaces. Nothing is deleted. Repeating the call on an archived space has no further effect.
Inputs. id. Optional workspace.
Errors. FORBIDDEN names request_access with manage_spaces.
spaces_unarchive
Restores an archived space. It counts toward the space plan limit again. Restoring a space past that limit is rejected and nothing changes. Needs manage_spaces. Repeating the call on a space that is already active has no further effect.
Inputs. id. Optional workspace.
Errors. FORBIDDEN names request_access with manage_spaces.
whoami
Read-only profile for this connection. It takes no arguments. id is a stable account id. It does not change when the connection is refreshed, reconnected, or its access changes, and it is not an email address. name, email, and nickname are included when this account has them. nickname is the account name and the workspace name.
Inputs. None.
Errors. UNAUTHORIZED when this connection has no signed-in account.
workspaces_create
Creates a personal or team workspace owned by the signed-in account. Needs account_workspaces. Optional workspace selects the connection acting in that workspace. The new workspace is not created inside it. Optional account must be that signed-in account. A different account creates nothing. There is no organization.
Inputs. name. Optional kind, account, and workspace.
Errors. FORBIDDEN names request_access with account_workspaces.
Credentials
create_connection and invite_member return a website link. The tool result does not contain the new secret. Links include the workspace short name, for example #/w/acme/access/<id>. App consent stays on /oauth/consent/<id>. Trusting an issuer and approving an app stay on the website. Approving this connection’s own access request returns that same approval link. A holder of manage_connections can approve someone else’s request with resolve_access_request.
Ordinary MCP calls use the live grant and do not keep a session. Live change notifications use https://app.adoor.halmoslabs.com/mcp/live.