Skip to main content
Glama

List counterparties

well_list_counterparties
Read-only

List the workspace's counterparty companies and how each one is CATEGORIZED — the company-level industry labels a counterparty carries. Use it for "which suppliers have no category?", "what industries are my counterparties in?", and before categorizing a counterparty so you name real ids instead of guessing.

Name a scope, and say whether to keep only the ones missing a category:

  • periods: [{ calendar_year, calendar_month }, …] (1-12): the counterparties whose invoices those months are still missing, categorized ones included, each row tagged with its month and carrying tx_count, base_total_amount in base_currency, and suggested_retrieval. Every month must have ended.

  • periods PLUS uncategorized_only: true: the same months, keeping ONLY the counterparties that carry no category. Use this whenever the question is which of a period's suppliers still need one, and whenever a step asks the user to categorize them: the categorized ones are not the work, and listing them buries it.

  • uncategorized_only: true alone: a WORKSPACE-WIDE sweep for every counterparty that carries no category, no month involved. Returns 50 rows per page plus total_count; tx_count, base_total_amount and suggested_retrieval are null because the call names no period. When next_cursor is not null the sweep has more counterparties: call again with cursor set to it to read them. It is a POSITION, not a row offset, so categorizing the rows of one page never hides the rows of the next. Only this sweep pages: cursor is refused beside periods.

  • missing_ledger_only: true alone: the LEDGER-ASSIGNMENT worklist — the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take. Its rows ride in ledger_rows, not rows, each carrying needs_payable/needs_receivable, AP/AR defaults, and the separate usual_expense_account/usual_revenue_account values. The classifier writes usual expense and revenue accounts automatically; well_update_company({ account_payable_default_id | account_receivable_default_id }) sets only a typed supplier/customer auxiliary for invoice control lines. Read chart accounts with well_list_ledger_accounts. It returns the first 500 counterparties needing a default, so a worklist that fills 500 (total_count equal to row_count at 500) is a FLOOR: assign those and read the scope again for the rest. It is its own scope — never combine it with periods, uncategorized_only, or cursor.

COST: the period form has no batch endpoint, so each named month is a separate read of that month's spend. Ask for the months the user actually named, not a whole year "to be safe".

Every row carries categories ([{ category_id, name }]) and is_categorized. categorized_count and uncategorized_count count the COUNTERPARTIES OF THE SCOPE, once each however many months they appear in, not the rows returned. Under uncategorized_only the result lists the uncategorized ones alone while categorized_count still counts the ones it withheld, so the two together are the period's coverage and uncategorized_count is the work left. Report both: naming the listed rows as the period's whole counterparty set overstates how much is uncategorized.

TO SET a counterparty's categories, call well_update_company({ company_id, category_ids: [...] }) — that field REPLACES the company's whole set. Read the available labels first with well_query_records({ root: "categories", whereClause: { category_type: { _eq: "company" } } }): that is the company-category catalog. It has no curated allowlist — the labels are minted during enrichment — so pass ids from it rather than inventing a taxonomy.

suggested_retrieval is derived from the PROVIDER match, not from the category. Categorizing a counterparty does not change it; do not tell the user otherwise.

This tool only reads. It categorizes nothing, mints no task, connects nothing and fetches no invoice.

No workspace read is needed first: the workspace is resolved from the caller's authorized token, same as every other well_* tool.

When the user asks to see, list or fix these rows, call this tool directly: its card is the answer. ⚠️ This tool draws its card on EVERY call, the empty one included. So when nobody asked for the list and you only need to CHECK whether anything is left (the first pass of a gate, or a re-check after a repair), call well_get_worklist_status({ worklist: "counterparties_to_categorize", periods }) first, with the same scope you would pass here. It draws nothing. Call this tool after it only when it answers open: true.

Input Schema

NameRequiredDescriptionDefault
cursorNoThe next page of the workspace-wide uncategorized sweep: pass back the `next_cursor` the previous call returned. Only that sweep pages, so this needs `uncategorized_only: true` and NO `periods`, because a period scope returns every month it covers in one call.
periodsNoThe calendar months whose counterparties to list, 1-12. Each month costs one separate read of that month's spend. Duplicates are refused.
workspace_idNoTarget workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did.
conversation_idNoThe conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.
workspace_scopeNoRestrict this call to the family: the token's primary workspace plus the company workspaces under it that you are a member of. Unrelated authorized workspaces drop out of the read; row reads merge across the family, while a per-workspace figure still reports exactly one workspace.
uncategorized_onlyNoKeep only the counterparties that carry no industry category. WITH `periods`: the uncategorized counterparties OF those months. Use it whenever the question is which of a period's suppliers still need a category. WITHOUT `periods`: a WORKSPACE-WIDE sweep for every uncategorized counterparty, 50 rows per page plus the total, with a `next_cursor` for the page after this one.
missing_ledger_onlyNoA workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable`, the AP/AR defaults it holds now (a needed slot is empty), its `usual_expense_account` and `usual_revenue_account`, and `suggested_payable_default`/`suggested_receivable_default` when the classifier proposed an account for a needed slot; it rides in `ledger_rows` rather than `rows`.

Output Schema

NameRequiredDescriptionDefault
modeNoWhich SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones. `missing_ledger_only` returns its rows in `ledger_rows`, not `rows`.
rowsYes
errorNo
hintsNo
successYes
next_stepNoWhat to do with the card this result renders. Added by the dispatcher when the card asks for a click.
row_countNo
ledger_rowsNo
next_cursorNoThe uncategorized sweep's next page: pass it back as `cursor`. Null when this page ends the sweep, absent on the periods scope, which pages nothing.
total_countNoCounterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`. Under `missing_ledger_only` it is the worklist actually returned and equals `row_count`: that scope reads the first 500 counterparties needing a default, so a worklist that fills 500 is a FLOOR, not a complete count. Assign those and read the scope again for the rest.
workspace_idNo
base_currencyNo
conversation_idNoThe conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.
newly_connectedNoTools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. The connected-line rule says when to tell an entry; when it does, one line per entry, in the person's language: "<name> is connected.". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later.
periods_coveredNoThe months the result covers, oldest first.
categorized_countNoCOUNTERPARTIES of the scope that carry at least one category, counted over the whole scope, not over `rows`, and counted once however many months a counterparty appears in. Under `uncategorized_only` these are exactly the counterparties the result withheld, so a non-zero figure beside rows that are all uncategorized is the coverage, not a contradiction.
resolved_workspaceNoThe workspace that answered, when the caller named none and the token authorizes several.
uncategorized_onlyNoWhether `rows` holds ONLY the counterparties that carry no category. True on the workspace-wide sweep and on a period scope the call filtered. When true, `categorized_count` counts counterparties the result did NOT list.
uncategorized_countNoCOUNTERPARTIES of the scope that carry none, counted the same way. This is the outstanding work; with `categorized_count` it is the scope's whole population.
conversation_id_noteNoPresent only when the server opened a fresh lane, stating that no choice recorded earlier was read.
conversation_id_sourceNoWhere the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed14 schema fields changed
    • removedOutput schema / properties / next_cursor / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / next_cursor / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / resolved_workspace / properties / name / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / resolved_workspace / properties / name / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / rows / items / properties / base_total_amount / anyOf
      Removed value: -[
      -  {
      -    "type": "number"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / rows / items / properties / base_total_amount / type
      Added value: +[
      +  "number",
      +  "null"
      +]
    • removedOutput schema / properties / rows / items / properties / domain / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / rows / items / properties / domain / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / rows / items / properties / logo_url / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / rows / items / properties / logo_url / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / rows / items / properties / period_label / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / rows / items / properties / period_label / type
      Added value: +[
      +  "string",
      +  "null"
      +]
    • removedOutput schema / properties / rows / items / properties / suggested_categories / items / properties / reasoning / anyOf
      Removed value: -[
      -  {
      -    "type": "string"
      -  },
      -  {
      -    "type": "null"
      -  }
      -]
    • addedOutput schema / properties / rows / items / properties / suggested_categories / items / properties / reasoning / type
      Added value: +[
      +  "string",
      +  "null"
      +]
  2. Changed1 schema field changed
    • changedInput schema / properties / missing_ledger_only / description
      Previous value: -"A workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable`, the AP/AR defaults it holds now (a needed slot is empty), and `suggested_payable_default`/`suggested_receivable_default` when the classifier proposed an account for a needed slot; it rides in `ledger_rows` rather than `rows`."New value: +"A workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable`, the AP/AR defaults it holds now (a needed slot is empty), its `usual_expense_account` and `usual_revenue_account`, and `suggested_payable_default`/`suggested_receivable_default` when the classifier proposed an account for a needed slot; it rides in `ledger_rows` rather than `rows`."
  3. Changed1 schema field changed
    • changedOutput schema / properties / newly_connected / description
      Previous value: -"Tools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. Start the reply with one line per entry, in the person's language: \"<name> is connected.\". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later."New value: +"Tools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. The connected-line rule says when to tell an entry; when it does, one line per entry, in the person's language: \"<name> is connected.\". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later."
  4. Changed1 schema field changed
    • addedInput schema / properties / workspace_scope
      Added value: +{
      +  "description": "Restrict this call to the family: the token's primary workspace plus the company workspaces under it that you are a member of. Unrelated authorized workspaces drop out of the read; row reads merge across the family, while a per-workspace figure still reports exactly one workspace.",
      +  "enum": [
      +    "family"
      +  ],
      +  "type": "string"
      +}
  5. Changed1 schema field changed
    • addedOutput schema / properties / newly_connected
      Added value: +{
      +  "description": "Tools that connected to this workspace since the last result and that no session was told about yet, oldest first. Present only when at least one tool is new. Start the reply with one line per entry, in the person's language: \"<name> is connected.\". Never fold several entries into one sentence. Each entry comes back once, on one result only: never repeat its line later.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "name": {
      +        "type": "string"
      +      },
      +      "service_id": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "name",
      +      "service_id"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
  6. Changed1 schema field changed
    • changedInput schema / properties / missing_ledger_only / description
      Previous value: -"A workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable` and the AP/AR defaults it holds now (a needed slot is empty), and rides in `ledger_rows` rather than `rows`."New value: +"A workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable`, the AP/AR defaults it holds now (a needed slot is empty), and `suggested_payable_default`/`suggested_receivable_default` when the classifier proposed an account for a needed slot; it rides in `ledger_rows` rather than `rows`."
  7. Changed9 schema fields changed
    • addedInput schema / properties / conversation_id
      Added value: +{
      +  "description": "The conversation id returned by the previous Well result, in its meta under well/conversation_id, in its structuredContent, or in its JSON text block. Pass it back on every call in the same conversation, including a call a card makes, so the chosen workspace and the earlier answers still apply. It decides the conversation on its own: nothing the host states about the session replaces it. Omit it only on the first call of a conversation.",
      +  "type": "string"
      +}
    • addedInput schema / properties / missing_ledger_only
      Added value: +{
      +  "const": true,
      +  "description": "A workspace-wide sweep for the counterparties that have a bank transaction and still need a ledger account (COA) default set for the direction their transactions take — the ledger-assignment worklist. It is its OWN scope: never pass it with `periods`, `uncategorized_only`, or `cursor`. Each row carries `needs_payable`/`needs_receivable` and the AP/AR defaults it holds now (a needed slot is empty), and rides in `ledger_rows` rather than `rows`.",
      +  "type": "boolean"
      +}
    • addedOutput schema / properties / conversation_id
      Added value: +{
      +  "description": "The conversation this result belongs to. Pass it back as the conversation_id argument on every later Well call in the same conversation.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / conversation_id_note
      Added value: +{
      +  "description": "Present only when the server opened a fresh lane, stating that no choice recorded earlier was read.",
      +  "type": "string"
      +}
    • addedOutput schema / properties / conversation_id_source
      Added value: +{
      +  "description": "Where the conversation id came from: the host's own request meta, the caller's argument, or a fresh lane the server opened.",
      +  "enum": [
      +    "host_meta",
      +    "argument",
      +    "minted"
      +  ],
      +  "type": "string"
      +}
    • addedOutput schema / properties / ledger_rows
      Added value: +{
      +  "items": {
      +    "additionalProperties": {},
      +    "propertyNames": {
      +      "type": "string"
      +    },
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • changedOutput schema / properties / mode / description
      Previous value: -"Which SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones."New value: +"Which SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones. `missing_ledger_only` returns its rows in `ledger_rows`, not `rows`."
    • changedOutput schema / properties / mode / enum
      Previous value: -[
      -  "periods",
      -  "uncategorized_only"
      -]New value: +[
      +  "periods",
      +  "uncategorized_only",
      +  "missing_ledger_only"
      +]
    • changedOutput schema / properties / total_count / description
      Previous value: -"Counterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`."New value: +"Counterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`. Under `missing_ledger_only` it is the worklist actually returned and equals `row_count`: that scope reads the first 500 counterparties needing a default, so a worklist that fills 500 is a FLOOR, not a complete count. Assign those and read the scope again for the rest."
  8. Changed1 schema field changed
    • addedOutput schema / properties / next_step
      Added value: +{
      +  "description": "What to do with the card this result renders. Added by the dispatcher when the card asks for a click.",
      +  "type": "string"
      +}
  9. Changed9 schema fields changed
    • changedInput schema / properties / cursor / description
      Previous value: -"The next page of the uncategorized sweep: pass back the `next_cursor` the previous call returned. Only the sweep pages, so this needs `uncategorized_only: true`."New value: +"The next page of the workspace-wide uncategorized sweep: pass back the `next_cursor` the previous call returned. Only that sweep pages, so this needs `uncategorized_only: true` and NO `periods`, because a period scope returns every month it covers in one call."
    • changedInput schema / properties / uncategorized_only / description
      Previous value: -"Instead of a period, sweep the WHOLE workspace for counterparties that carry no industry category. Returns 50 rows per page plus the total, and a `next_cursor` for the page after this one."New value: +"Keep only the counterparties that carry no industry category. WITH `periods`: the uncategorized counterparties OF those months. Use it whenever the question is which of a period's suppliers still need a category. WITHOUT `periods`: a WORKSPACE-WIDE sweep for every uncategorized counterparty, 50 rows per page plus the total, with a `next_cursor` for the page after this one."
    • changedOutput schema / properties / categorized_count / description
      Previous value: -"Rows in this result that carry at least one category."New value: +"COUNTERPARTIES of the scope that carry at least one category, counted over the whole scope, not over `rows`, and counted once however many months a counterparty appears in. Under `uncategorized_only` these are exactly the counterparties the result withheld, so a non-zero figure beside rows that are all uncategorized is the coverage, not a contradiction."
    • changedOutput schema / properties / mode / description
      Previous value: -"Which scope the call asked for; the row fields that are populated follow from it."New value: +"Which SCOPE the call asked for; the row fields that are populated follow from it. `periods` whenever the call named months, whether or not it also filtered to the uncategorized ones."
    • addedOutput schema / properties / rows / items / properties / suggested_categories
      Added value: +{
      +  "description": "A stored classifier PROPOSAL is a candidate label, never a decision: `categories` is what this counterparty actually carries, and only a write makes a proposal true. Highest confidence first, at most three. Empty when the classifier has not run, abstained, or a human already resolved its proposal — an empty array is not evidence that no label fits. Present on both scopes. Report a proposal as a suggestion to the user, never as the counterparty's industry, and NOTE this is unrelated to `suggested_retrieval`, which is about fetching invoices.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "category_id": {
      +        "description": "The catalog id of the proposed label — pass it straight to `well_update_company` to accept it.",
      +        "type": "string"
      +      },
      +      "confidence": {
      +        "description": "0..1, the classifier's own scale.",
      +        "type": "number"
      +      },
      +      "name": {
      +        "type": "string"
      +      },
      +      "rank": {
      +        "description": "1 is the classifier's top proposal.",
      +        "maximum": 9007199254740991,
      +        "minimum": -9007199254740991,
      +        "type": "integer"
      +      },
      +      "reasoning": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "description": "The classifier's own account of this proposal, or null when the run stored none."
      +      }
      +    },
      +    "required": [
      +      "category_id",
      +      "name",
      +      "confidence",
      +      "rank",
      +      "reasoning"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • changedOutput schema / properties / rows / items / required
      Previous value: -[
      -  "company_id",
      -  "name",
      -  "domain",
      -  "logo_url",
      -  "calendar_year",
      -  "calendar_month",
      -  "period_label",
      -  "tx_count",
      -  "base_total_amount",
      -  "categories",
      -  "is_categorized",
      -  "suggested_retrieval"
      -]New value: +[
      +  "company_id",
      +  "name",
      +  "domain",
      +  "logo_url",
      +  "calendar_year",
      +  "calendar_month",
      +  "period_label",
      +  "tx_count",
      +  "base_total_amount",
      +  "categories",
      +  "is_categorized",
      +  "suggested_categories",
      +  "suggested_retrieval"
      +]
    • changedOutput schema / properties / total_count / description
      Previous value: -"Matching counterparties BEFORE the row cap, so a capped sweep says what it left out. On the periods scope: DISTINCT counterparties across the window — a multi-month call lists one counterparty on one row per month, so row_count can exceed it."New value: +"Counterparties MATCHING the call, before the row cap, so a capped sweep says what it left out. On the periods scope: the DISTINCT counterparties `rows` names, and a multi-month call lists one counterparty on one row per month, so row_count can exceed it. Under `uncategorized_only` it counts the uncategorized ones alone; the scope's whole population is `categorized_count` plus `uncategorized_count`."
    • changedOutput schema / properties / uncategorized_count / description
      Previous value: -"Rows in this result that carry none."New value: +"COUNTERPARTIES of the scope that carry none, counted the same way. This is the outstanding work; with `categorized_count` it is the scope's whole population."
    • addedOutput schema / properties / uncategorized_only
      Added value: +{
      +  "description": "Whether `rows` holds ONLY the counterparties that carry no category. True on the workspace-wide sweep and on a period scope the call filtered. When true, `categorized_count` counts counterparties the result did NOT list.",
      +  "type": "boolean"
      +}
  10. Changed5 schema fields changed
    • addedInput schema / properties / cursor
      Added value: +{
      +  "description": "The next page of the uncategorized sweep: pass back the `next_cursor` the previous call returned. Only the sweep pages, so this needs `uncategorized_only: true`.",
      +  "type": "string"
      +}
    • changedInput schema / properties / uncategorized_only / description
      Previous value: -"Instead of a period, sweep the WHOLE workspace for counterparties that carry no industry category. Returns at most 50 rows plus the total."New value: +"Instead of a period, sweep the WHOLE workspace for counterparties that carry no industry category. Returns 50 rows per page plus the total, and a `next_cursor` for the page after this one."
    • changedInput schema / properties / workspace_id / description
      Previous value: -"Target workspace. Omit to use the only authorized workspace, or (for read tools) to query all authorized workspaces grouped by workspace. Required for write tools when the token authorizes more than one workspace."New value: +"Target workspace. This read reports one workspace's own data. Omit it and the token's primary workspace answers, which may not be the one you mean; the result names the workspace that did."
    • addedOutput schema / properties / next_cursor
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "description": "The uncategorized sweep's next page: pass it back as `cursor`. Null when this page ends the sweep, absent on the periods scope, which pages nothing."
      +}
    • addedOutput schema / properties / resolved_workspace
      Added value: +{
      +  "additionalProperties": false,
      +  "description": "The workspace that answered, when the caller named none and the token authorizes several.",
      +  "properties": {
      +    "name": {
      +      "anyOf": [
      +        {
      +          "type": "string"
      +        },
      +        {
      +          "type": "null"
      +        }
      +      ]
      +    },
      +    "workspace_id": {
      +      "type": "string"
      +    }
      +  },
      +  "required": [
      +    "workspace_id",
      +    "name"
      +  ],
      +  "type": "object"
      +}
  11. Added

TDQS

A4.8/5.0
Behavior5/5

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

Annotations already declare readOnly; the description goes well beyond them by disclosing that the tool draws a card on EVERY call including empty ones, that the sweep pages by cursor position rather than row offset, that the ledger worklist caps at 500 and is a FLOOR, and that `suggested_retrieval` derives from provider match and won't change on categorization. These are non-obvious traits an agent would otherwise get wrong.

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?

Front-loaded with a clear lead sentence, but the body is unusually long for 7 parameters and repeatedly restates rules the input schema already spells out (cursor paging, `missing_ledger_only` scope exclusivity, sweep row counts). The bold/ALL-CAPS emphasis and emoji help scanning but add bulk; trimming the duplication would sharpen it.

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?

Covers the semantics of `categorized_count`/`uncategorized_count` (counting counterparties of the scope once each, not rows), warns against reporting listed rows as the whole set, and names the follow-up write path via `well_update_company` and `well_query_records` for the category catalog. With an output schema present, nothing material is left for the agent to guess.

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

Parameters5/5

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

Schema coverage is 100%, yet the description still adds real meaning: the mutual exclusivity of `periods`/`uncategorized_only`/`missing_ledger_only`/`cursor`, that every month must have ended, that `cursor` is a position not an offset, and that ledger rows arrive under `ledger_rows` not `rows`. This is well above the baseline the schema alone would justify.

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?

States a specific verb and resource ('List the workspace's counterparty companies and how each one is CATEGORIZED') and enumerates the distinct scopes it serves. It separates itself from `well_list_uncategorized_window`, `well_list_ledger_accounts`, and `well_get_worklist_status` by name, so an agent can pick the right tool without opening a schema.

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

Usage Guidelines5/5

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

Gives explicit when-to-use conditions per scope ('Use this whenever the question is which of a period's suppliers still need one'), prohibitions ('never combine it with periods, uncategorized_only, or cursor'), and a routing rule to call `well_get_worklist_status` first when merely checking. Alternatives and their selection criteria are stated, not inferred.

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