---
title: "Tools"
description: "How gtable's MCP tools are named, what their hints mean, how lists page, and what is deliberately not a tool."
canonical: "https://gtable.app/docs/agents/tools"
updated: "2026-10-05"
---

# Tools

Every MCP tool is an operation of the REST API, generated from the same definition. A new
endpoint is a new tool the day it ships, and the two cannot describe different things.

## Names

A tool is named after the operation it mirrors, with the dots turned into underscores:
`records.list` is `records_list`, `schema.get` is `schema_get`, `rules.simulate` is
`rules_simulate`. Every page of the [App API](/docs/app-api/records/list) and
[Builder API](/docs/builder-api/apps/list) references names its tool. Operation names never
change silently: renaming one would break every agent and script written against it, so an old
name is deprecated first and kept until a published date.

## Hints

Each tool carries the MCP annotations a client uses to decide when to ask you first:

| Hint              | Set when                                                               |
| ----------------- | ---------------------------------------------------------------------- |
| `readOnlyHint`    | the operation only reads                                               |
| `destructiveHint` | it deletes, or rewrites many rows at once: a bulk delete, a SQL update |
| `idempotentHint`  | calling it twice is the same as calling it once                        |

A client that asks before destructive calls therefore asks at the right moments.

## Lists

A list tool returns `{ data, meta }`. While `meta.hasMore` is true, pass `meta.nextCursor` back
as `cursor` for the next page. The tool descriptions ask the model to narrow its filter first,
because walking pages spends a conversation's context on paging rather than on thinking. Lists
that are short by nature (a record's comments, your saved views of a table) take a `limit` and
have no cursor.

## Errors come back as results

A refusal is returned as a tool result marked `isError`, with the same `code` and `message` the
REST API would give, not as a protocol error. The model reads it and adjusts. See
[Errors](/docs/api/errors).

## What is not a tool, on purpose

| Operation                    | Why not                                                                    | Instead                                                                                                      |
| ---------------------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| Uploading a file             | a tool call is JSON a model writes, and a file is bytes                    | upload with the REST API, then attach the returned descriptor with an ordinary record write, which is a tool |
| Making or revoking a key     | a credential that can mint credentials turns one leak into a permanent one | a person, in a signed-in browser                                                                             |
| A person's set-password link | it is a way to become that person                                          | handed over by a builder, from the Studio                                                                    |

## Large writes

Saving a page's code sends the whole file. Through MCP that means the model writes every byte as
tokens, which is slow and expensive for a large page. For anything big, use the REST API
(`PUT …/pages/{pageId}/code`) from a script.
