Skip to main content
Glama

data_rows

Read rows. WITH id = the single row (by _id). WITHOUT id = one PAGE of rows (filter/q narrow it, order_by sorts it) with a stable cursor.

Read with empties="sentinel" before re-sending a list: an assumed empty (written @empty, its reason in comment) then comes back "@empty" — an ordinary "" stays "" — and, sent back as is, stays one; sent back as "" it becomes an ordinary empty, refused on a required field.

⚠️ datastore = the table's NUMBER (ns_id, e.g. 174) — the form to use; the reply carries it back. A name still resolves until 2026-11-08, then is refused. slot:<name> also works.

⚠️ List mode returns {rows, count, next_cursor, ns_id}. When next_cursor is not null there are MORE rows: call again with cursor=<next_cursor> (same datastore/filter/order) — repeat until next_cursor is null. A page is not the table.

versions picks which VERSIONS of each cell you get. By default, the current value only; the origin (the FIRST version of the data) is asked for with versions=["current","origine"]: current is what we established, origine what the client handed over. ⚠️ Ask for BOTH in ONE call when you compare them — two calls are not atomic, and an write in between would make you compare the before of one state with the after of another.

Layers come back FLAT by default (champ.origine beside the bare name); layers="nested" returns the shape you write — guide datastore-semantics. The bare name ALWAYS carries the current version; versions only decides what is added beside it — at any depth: without origine, a list item carries no item["email.origine"] either. The reply states what it served in versions_servies, so "I did not ask for it" never looks like "this cell has none". The REST face GET …/rows pages by offset with a total, no cursor.

Without order_by the cursor is keyset-stable (rows created meanwhile don't shift the paging). With order_by it pages by offset instead, since an arbitrary sort has no stable keyset — so a row inserted mid-walk can shift the remaining pages. q without order_by ranks the rows (best match first), so it pages by offset too. Keep the SAME order_by and q across a walk: passing a cursor from one regime into the other is rejected rather than silently mispaged.

Use count_only=True to get just the TOTAL number of (optionally filtered) rows — computed server-side, no rows returned — when you only need the count (e.g. how many leads match a filter) without pulling the data into context.

Use fields to PROJECT a subset of columns when the full row is heavy and you only need a few (e.g. name + email + score over a large vivier): each row is trimmed to those columns (plus _id, always kept so you can still update the row), drastically shrinking the payload. Bump limit when projecting — narrow rows let you pull far more per page.

Every column DECLARED in the schema is on each row, null when no value is in place ("nothing here yet", not "no such column") — including in a projection.

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "datastore": "<datastore>"
    },
    "name": "data_rows"
  }
}

Input Schema

NameRequiredDescriptionDefault
qNofree-text search over the row. Every WORD of `q` must be found, in any order, either by meaning (French stemming, accents and case folded: `cahiers` finds "Le Cahier", `corp acme` finds "ACME Corp") or as a literal FRAGMENT (a piece of a SIREN, an URL or a reference pasted as is). Without an explicit sort, best matches come first (rows matched by meaning, then rows matched only by fragment); an explicit sort stays yours. Where to search is `q_scope`. The way to find a row when you don't know WHICH column holds the word. Combines with `filter` (AND). (list mode only)
idNo`_id` of one row ; omit = list rows.
_orgNoOrg (id) de CET APPEL. `oto_list_orgs()`.
limitNopage size (default 100, list mode only).
_groupNoÉquipe (id) de CET APPEL, sous son org parente.
cursorNoopaque `next_cursor` from a previous call = fetch the NEXT page.
fieldsNolist of column names to keep (projection) — the returned rows carry only these plus `_id`; a declared one with no value comes back `null`. Omit = full rows.
filterNodict `{column: value}` — exact match. A column may instead take ONE operator: `{"posted_at": {"gte": "2026-06-01"}}`, `{"author": {"contains": "sylvie"}}`, `{"status": {"ne": "processed"}}`, `{"idcc": {"in": ["573", "86"]}}`, `{"email": {"not_empty": true}}`. Ops: eq, ne, contains, in, gt, gte, lt, lte, empty, not_empty. The system columns are filterable too — `_updated_at`/`_created_at` (ops eq/ne/gt/gte/lt/lte; a plain `YYYY-MM-DD` means that WHOLE day, so `{"_updated_at": {"gte": "2026-08-01"}}` = touched since the 1st) and `_id`. Filtering happens in SQL — never pull the whole table to filter it yourself. (list mode only)
layersNoshape of a cell that carries layers (`origine`/`comment`/`link`). ⚠️ This is a READ: do NOT send the row back. Write only the fields you established — and never `<field>.origine`, which is read here and set by the platform, never by an agent. You WRITE nested (`{"valeur": …, "comment": …}`) and, by default, read back FLAT — this parameter lifts that asymmetry. `flat` (default): `row["email"]` is the value, and each filled layer sits BESIDE it as `row["email.origine"]`. `nested`: `row["email"]` is `{"valeur": …, "origine": …, "comment": …, "link": …}` — `valeur` always, the other keys only when filled — i.e. the shape you WRITE with `data_write`. A cell without layers is the same plain value in both shapes. Any other value is refused. With `nested`, `fields` names columns (a nested cell keeps its layers); `email.origine` as a field name only exists in `flat`. The default WILL switch to `nested`, with dated notice: pass `layers` explicitly if you depend on one shape.flat
_run_idNoOBLIGATOIRE à chaque appel dès `run_start` ou `data_claim_next` : sans lui, l'écriture sur une ligne réservée est refusée. Hérité seulement si le serveur tient un run actif — n'y compte pas.
emptiesNohow an assumed empty (written `@empty`) comes back. `plain` (default): `""`, like any empty cell. `sentinel`: `"@empty"`, the word that writes it — top level, nested (`{"valeur": "@empty", …}`) and inside list elements alike. It is not a value: never copy it into a deliverable. Any other value is refused.plain
filtersNolist of clauses, for what `filter` cannot express — ONE clause may target SEVERAL columns at once. A single notion often lives on numbered columns (`contact1_fonction`, `contact2_fonction`…): ask about all of them in one go by NAMING them. `[{"fields": ["contact1_fonction", "contact2_fonction", "contact3_fonction"], "op": "in", "value": ["DRH", "DAF"]}]` = rows where ANY of those three holds an HR/finance role, whichever rank carries it. `match` picks the sense: `any` (default, one column is enough) or `all` (every listed column) — `all` + `empty` is how you get "rows with NO contact at all", which is NOT the negation of the first. A clause may also name a single column (`{"field": …}`), and a `champ.origine`/`.comment`/`.link` suffix targets that layer. Clauses combine with AND, and with `filter`. (list mode only)
q_scopeNowhere `q` searches: `values` = only the values a reader is served (no column names, no layers — `origine`/`comment`/`link` — no JSON quoting); `all` = the whole stored row, names and layers included (finds a row by its `comment`). Default `all`. Any other value is refused.
_projectNoProjet (id) de CET APPEL — jeton PRIMAIRE. `oto_project(op='list')`.
order_byNosort column — a user field, or a system one (`_created_at`, `_updated_at`, `_id`). Omit = creation order. Sorting in SQL is how you get "the 10 most recent" or "the top scores" without pulling the table and sorting it yourself. With `q` and no `order_by`, best matches come first. (list mode only) Sorting honors the DECLARED type of the column: a `number` sorts numerically (never "10 < 2"), an `enum` sorts in its declared option order, a `date` chronologically. Values that don't fit the type (junk in a number column, a value outside the enum's options) go to the TAIL in both directions, alphabetically; empty cells go last of all. When that happens the response carries `order_health: {off_type, empty}` — counts over the whole filtered set, absent when everything conforms.
versionsNo
datastoreYestarget datastore, or `slot:<name>` = the table bound under that slot name by the ACTIVE project (actionable error if unbound).
order_dirNo`desc` (default) or `asc`. Meaningful with `order_by`; with `q` alone, it orders rows of EQUAL rank by creation.desc
count_onlyNoreturn only `{total}` (filtered row count), no rows.

Schema Changelog

Changes observed during successful MCP inspections.

No schema history has been recorded yet.

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations supplied, the description carries the full burden and does so extensively: it discloses keyset vs offset pagination, cursor regime rejection, the datastore number migration deadline, `next_cursor` semantics, `order_health` on type mismatches, and the shape of list-mode replies. It also explains the unusual `empties="sentinel"` behavior and the `versions_servies` response field.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core read-mode distinction, but it is very long and dense for a tool description, with several warnings and asides that interrupt the flow. Some details are repeated or over-explained (e.g. the layers shape, the cursor regime caveat), and key pagination behavior is buried mid-description rather than surfaced early.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 19-parameter tool with no output schema, the description is unusually complete: it describes the list-mode return payload (`{rows, count, next_cursor, ns_id}`), the `count_only` return (`{total}`), projection behavior, pagination regimes, and layer/version response shapes. An agent has enough information to call the tool correctly and interpret the main results without opening the schema.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is already 95%, so the baseline is 3, but the description adds meaningful cross-parameter semantics: how `versions` interacts with nested layers, how `layers` changes projection behavior, how `q` and `order_by` determine pagination regime, and how `empties` affects read-back sentinels. It does not add much for context parameters like `_org`, `_group`, `_project`, or `_run_id`, but those are already well documented in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The definition opens with a specific verb and resource: 'Read rows,' then immediately distinguishes the two modes (with `id` = single row; without = page). It also clarifies the scope of list mode (filter/q/order_by/cursor) and names the read/write boundary by warning against sending rows back to write fields.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives clear situational guidance: use `count_only` when only a total is needed, use `fields` when projecting heavy rows, ask for both versions in one call when comparing, and keep the same `order_by`/`q` across a cursor walk. It does not explicitly direct agents to sibling tools such as `data_aggregate` or `data_get_schema`, so it falls short of full alternative routing.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources