business-central-mcp
An MCP server that lets AI agents drive Microsoft Dynamics 365 Business Central like a real user — opening pages, filling fields, posting documents, running reports — plus bulk OData reads and AL test runs.
Open any page by numeric ID (list, card, document, Role Center) and get its full state as sections plus a
pageContextIdandstateVersion.Read data from any section (header, lines, factboxes) with server-side filters, sorting, column selection and offset/limit paging.
Write fields on headers and document lines with BC validation and server-confirmed values; target rows by
rowIndexor stablebookmark.Execute actions such as New, Delete, Post, Release, and cue-tile drill-downs, including multi-row batch actions.
Handle dialogs — answer confirms/messages, fill report request pages, and drive multi-step wizards (Next/Back/Finish/Cancel).
Navigate list pages: position the cursor on a row or drill down to open the record's card/document.
Run reports by ID and capture rendered output as PDF, Excel or Word.
Run AL tests through BC's Test Runner (page 130455) with per-method pass/fail rows and call stacks, filtered by codeunit range, extension or method name.
Bulk read standard entities via OData (
bc_query) over the Standard API v2.0 with$filter,$select,$orderby,$expandand$top.Manage companies — list available companies and switch the session to another one.
Upload files into BC file-upload dialogs (e.g. report layouts).
Safe retries — idempotency keys (
operationId), stale-state guards (expectedStateVersion), andbc_get_operationto check whether an uncertain call landed, with a read-back and evidence hash.Discover objects via Tell Me search (
bc_search_pages) and enumerate FK lookup candidates (bc_lookup).Close pages to free BC server-side resources.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@business-central-mcpLook up customer 'Contoso'."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
business-central-mcp
An MCP server that lets AI agents work in Microsoft Dynamics 365 Business Central the way a user does: open pages, fill fields, post documents, run reports.
The UI tools speak the web client's own WebSocket protocol, which I reverse-engineered from the decompiled BC server. Anything a user can do on a page, the agent can do, under that user's permissions and with BC's validation. For bulk reads there is OData, and on BC 29+ the tools in BC's own MCP server: AL queries over any table, and any installed API.
Overview
BC versions | BC26 to BC29. |
Hosting | On-prem and BC Online |
Auth | On-prem NavUserPassword. BC Online: browser sign-in for the UI, device code for OData and BC's MCP server |
Tools | 25, plus 10 prompts and 3 skills |
Transports | stdio, Streamable HTTP, REST |
Runtime | Node 20+ |
Tests | 1328 unit/protocol, 194 integration against live BC (as of 2.4.0) |
Related MCP server: origo-bc-mcp-server
Install
On-prem, NavUserPassword. For BC Online, read docs/saas.md first: no password goes in env there.
Claude Code
claude mcp add business-central \
-e BC_BASE_URL=http://your-bc-server/BC \
-e BC_USERNAME=you \
-e BC_PASSWORD=secret \
-- npx -y business-central-mcpAdd --scope project to keep it to one repo.
VS Code
Click the Install badge above. VS Code asks for URL, username and password and writes the entry to your user mcp.json.
Claude Desktop
Download the .dxt from Releases, double-click it, fill in URL, username and password, restart Claude Desktop.
Anything else (Cursor, Grok, a workspace .vscode/mcp.json):
{
"mcpServers": {
"business-central": {
"command": "npx",
"args": ["-y", "business-central-mcp"],
"env": {
"BC_BASE_URL": "http://your-bc-server/BC",
"BC_USERNAME": "your-user",
"BC_PASSWORD": "your-password"
}
}
}
}VS Code's file uses "servers" instead of "mcpServers". That's it.
Try it
Ask the agent things like:
"Open the customer list and show me everyone in London with a balance over 1000."
"Create a sales order for customer 10000 with 5 of item 1896-S, then post it as Ship."
"Run the Trial Balance report for last month and save the PDF."
"Which items have not sold in 90 days?" (an AL query on BC 29+)
Tools
Pages, the way a user works
Tool | What it does |
| Tell Me search for pages and reports |
| Open any page by id: list, card, document, Role Center. Returns header, lines, factboxes and cue tiles as |
| Re-read one section with filters, paging and column selection |
| Write fields; BC validates and echoes what it stored |
| Run a page, row or cue action (New, Delete, Post, Release, ...). Multi-row selection where the page allows it |
| Select rows and drill down |
| List the valid values for a lookup field |
| Answer confirms, messages and request pages |
| Back / Next / Finish through a wizard |
| Close a page |
| Upload a local file into a BC upload dialog |
Company, reports, tests
Tool | What it does |
| List companies, switch the session |
| Run a report, fill its request page, capture the PDF/Excel/Word |
| Run AL test codeunits through the Test Runner (page 130455) |
| Look up an earlier call by |
Bulk reads
Tool | What it does |
| Read-only OData over the Standard API v2.0 |
| Find tables by name or meaning (BC 29+) |
| Fields, types, FlowField formulas and relations of a table |
| TableRelation links, for joins |
| Compile and run an AL |
Any installed API (BC 29+)
Tool | What it does |
| Find actions on any API page or query: Microsoft's, ISV and your own |
| An action's fields, filters and parameters |
| Read through an API with OData filter, select, orderby and paging |
| Create, modify, delete or run a bound action, where an admin enabled it |
Every tool carries MCP annotations, so a client can auto-approve the 15 read-only ones and ask before the rest.
Safe retries
A timeout in the middle of a posting is the worst case: did it post or not? The ten tools that change state take an optional operationId. Retrying with the same id returns the stored result instead of posting twice. If the first call's outcome is unknown, you get OPERATION_IN_DOUBT with a snapshot of the target, and bc_get_operation reads the record back with an evidence hash. Each result also says replaySafe: true only when BC's response proved a re-run cannot double-apply.
Details, including the REST mapping: docs/replay-safety.md.
BC 29: BC's own MCP server
BC 29 ships its own MCP server on the OData port (/BC/mcp). bc-mcp connects to it and puts its tools next to the UI tools, so one agent gets both.
Data query (bc_data_*). The model writes an AL query object, BC compiles it on the fly and runs it read-only under the user's permissions. Works on any table the user can read, API page or not. Compile errors come back as DATA_QUERY_ERROR with BC's AL diagnostics, so the model fixes the query and retries. The bc-data-query skill holds the AL syntax that actually compiles (checked on BC 29.1).
APIs (bc_api_*). Every installed API page and query the user can read shows up as a List_* action (List_Customers_PAG30009). Writes appear only for API objects an admin added to the MCP Server Configuration. BC's OData errors come back as API_ACTION_ERROR with details.odataCode (Request_EntityChanged for a stale etag).
To turn them on: enable "MCP Server" on the Copilot & agent capabilities page, then in MCP Server Configuration switch on "Data Query Tools (Preview)" and/or the API tools. Until then the tools return BC_MCP_UNAVAILABLE with a reason (endpoint-missing, mcp-disabled, tools-disabled).
Honest limits, as of BC 29.1 (October 2026): semantic table and API search returned nothing on-prem, so keyword search is what works there. Microsoft has announced billing for the data query tools at general availability.
Configuration
The ones you are likely to touch:
Variable | Default | Description |
| required |
|
| On-prem credentials. Never set a password for BC Online | |
| Entra app for OData and BC's MCP server on BC Online (docs/saas.md) | |
| server default | Profile id, e.g. |
| first company | Company for |
| Also save report and export downloads to disk | |
|
| Cookies, tokens and the retry ledger |
All 30-odd variables, one config file for many BC instances (~/.bc-mcp/config.jsonc), and the FIN/NAV applicationId gotcha on on-prem containers: docs/configuration.md.
How it works
Agent (Claude, Copilot, ...)
| MCP over stdio or Streamable HTTP
v
business-central-mcp
|-- WebSocket /csh, JSON-RPC ------> BC web client endpoint (UI tools)
| (SignalR hub on BC26)
|-- OData v4 ----------------------> BC API v2.0 (bc_query)
|-- MCP Streamable HTTP -----------> BC 29 native MCP server (bc_data_*, bc_api_*)One WebSocket per session, every call serialised through a queue, because BC's protocol is stateful and concurrent sends corrupt it. BC26 to BC29 share one codec; BC26 only differs in framing.
What keeps it running:
A dead session reconnects with backoff (1, 2, 4, 8 s).
Long posts and reports stay alive as long as BC answers
IsExecutingwith yes. A hung call is killed when BC says no twice.Stale confirms and request pages left by a crash are closed with No / Cancel, never accepted. Anything else resets the session.
License popups on fresh databases are dismissed.
Downloads are fetched only from BC's own file handler on the same origin, never from an external URL.
Every protocol claim in this repo is checked against the decompiled BC source and a live container. CLAUDE.md has the long version, with the classes that confirmed each one.
Key files
Path | Purpose |
| npm |
| HTTP transport (MCP + REST) |
| Tool registry, schemas, handler, prompts, skills, retry ledger |
| One operation per tool, with its |
| Page, data, action, navigation and download logic |
| Wire codec, form state, captures |
| Session lifecycle, invoke queue, modal recovery |
| WebSocket, auth providers, HTTP |
| Client for BC 29's native MCP server |
| On a |
Development
git clone https://github.com/SShadowS/business-central-mcp
cd business-central-mcp
npm install
npm run typecheck
npm test # unit + protocol
npm run test:integration # needs a live BC container
npm run start:stdio-direct # run from sourceStill missing: Windows auth for on-prem, Cursor install badge, an init wizard. See ROADMAP.md. Found a page it can't drive? Open an issue with the page id and the log from LOG_DIR.
Author: Torben Leth (sshadows@sshadows.dk) License: MIT (see LICENSE)
Available Tools
25 toolsbc_api_describeARead-only
Returns the full schema of one Business Central API action found with bc_api_search: what it does, and the JSON schema of its parameters. For a List action the schema's "_availableFields" lists every field with its type and option values, and says which fields can be used in filter, orderby and select. For Create the properties are the editable fields; for Modify the required "id" and "If-Match" (the record's current "@odata.etag") plus the editable fields; for Delete the key; for a bound action the key and the action's own parameters.
Call it before bc_api_list (to know the exact field names for filter/select/orderby) and before every bc_api_invoke (to send exactly the parameters the action takes; bc_api_invoke's "parameters" object uses the property names from this schema). When NOT to use: to discover action names (bc_api_search) or to read data (bc_api_list). Runs on BC's own MCP server (BC 29+, API tools enabled); needs no web-client session.
| Name | Required | Description | Default |
|---|---|---|---|
| company | No | Company to run in (exact, case-sensitive name from bc_list_companies). Defaults to the current session company, then BC_ODATA_COMPANY, then the user's default company in BC. | |
| actionName | Yes | Exact, case-sensitive action name from bc_api_search, e.g. "List_Customers_PAG30009" or "Create_Customer_PAG30009". |
Output Schema
| Name | Required | Description |
|---|---|---|
| name | Yes | |
| schema | Yes | |
| company | Yes | |
| description | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so the description's main added value is the runtime context: it runs on BC's own MCP server, requires BC 29+ with API tools enabled, and needs no web-client session. It also discloses how each action type shapes the returned schema, but it does not discuss error behavior for an unknown or misspelled actionName.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core purpose, then procedural guidance, then exclusions — a sensible ordering. The per-action-type enumeration of schema contents is dense but each clause carries information the agent needs; only the List/Create/Modify/Delete breakdown borders on over-detailed.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be re-described, and annotations cover the safety profile. The description fills the remaining gap for a two-step discover-then-inspect workflow by naming the prerequisite tool and the downstream consumers.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both parameters are documented structurally; the description still adds cross-tool meaning by tying actionName to bc_api_search results and by explaining that bc_api_invoke's 'parameters' object uses the property names from this schema. The company parameter's fallback chain is left entirely to the schema, which is acceptable at full coverage.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Returns the full schema of one Business Central API action found with bc_api_search') and immediately enumerates what that schema contains for each action type (List, Create, Modify, Delete, bound action). It is clearly distinguishable from bc_api_search and bc_api_list, which are named.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly prescribes sequencing ('Call it before bc_api_list ... and before every bc_api_invoke') with the reason for each, and provides an explicit exclusion clause ('When NOT to use: to discover action names (bc_api_search) or to read data (bc_api_list)'). Nothing about when to reach for this tool is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_api_invokeADestructive
Runs a Business Central API action that changes data: Create_* (insert a record), Modify_* (update one), Delete_* (delete one), or a bound/unbound action such as posting a document. Get the action name from bc_api_search and its exact parameters from bc_api_describe, then pass them as the "parameters" object. Create and Modify return the record as BC saved it, including its new "@odata.etag"; Delete returns no result.
Modify needs the record's key ("id") and "If-Match" set to its current "@odata.etag" (read it with bc_api_list); a stale etag is refused with API_ACTION_ERROR "Request_EntityChanged" — re-read and retry. BC applies its own validation and business logic; a rejection is API_ACTION_ERROR with BC's message and nothing is changed. Write actions exist only for API objects an admin enabled with those permissions in BC's MCP Server Configuration.
Pass an operationId to make a retry safe: the same id replays the first result instead of running the action again. replaySafe is true for Modify and Delete (a repeat is refused by BC: stale etag, or record gone) and false for Create and bound actions (a repeat would create or post again). When NOT to use: to read (bc_api_list), or for changes that need the UI's dialogs and pages (bc_open_page, bc_write_data, bc_execute_action). Runs on BC's own MCP server (BC 29+); needs no web-client session.
| Name | Required | Description | Default |
|---|---|---|---|
| company | No | Company to run in (exact, case-sensitive name from bc_list_companies). Defaults to the current session company, then BC_ODATA_COMPANY, then the user's default company in BC. | |
| actionName | Yes | A Create_*, Modify_*, Delete_* or bound/unbound action name from bc_api_search. List_* actions go through bc_api_list. | |
| parameters | Yes | The action's parameters as an object, exactly as bc_api_describe's schema names them. Create: the field values, e.g. { "displayName": "Contoso" }. Modify: the key ("id"), "If-Match" with the record's current "@odata.etag" (from bc_api_list), and the fields to change. Delete: the key. Bound action: the key plus the action's own parameters. | |
| operationId | No | Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| result | No | |
| company | Yes | |
| actionName | Yes | |
| replaySafe | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only say destructiveHint=true, idempotentHint=false. The description adds far more: etag/If-Match preconditions for Modify, the API_ACTION_ERROR 'Request_EntityChanged' refusal mode, the guarantee that a rejected call changes nothing, return shapes per action type, permission gating via BC's MCP Server Configuration, and replay/operationId TTL behavior. Nothing here contradicts the annotations — the idempotentHint=false is consistent with the stated fact that a repeat Create or bound action would apply again.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose and workflow are front-loaded, but the operationId/replaySafe/TTL material is long and repeats almost verbatim what the operationId schema description already states, and the 'When NOT to use' clause is buried at the end. Roughly a third of the text is duplicated structured data.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive, nested-parameter, multi-mode write tool this covers everything an agent needs: required discovery steps, per-action parameter shapes, error semantics, permission prerequisites, retry/idempotency rules, and the environment constraint (BC 29+ MCP server, no web-client session). Return values are also characterized despite an output schema existing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3, but the description adds real workflow meaning: where actionName must come from (bc_api_search), where the If-Match etag must be read (bc_api_list), and what the parameters object must contain per action family. It largely mirrors the schema's own parameters description, so it earns 4 rather than 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Names a specific verb (runs/invokes) and resource (Business Central API action) and immediately enumerates the concrete action families: Create_* (insert), Modify_* (update), Delete_* (delete), bound/unbound actions such as posting. It also separates itself from bc_api_list, which the agent could otherwise confuse with the same API surface.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit prerequisites (get the action name from bc_api_search, parameters from bc_api_describe), explicit exclusions ('When NOT to use: to read (bc_api_list), or for changes that need the UI's dialogs and pages (bc_open_page, bc_write_data, bc_execute_action)'), and names each alternative. Retry guidance (pass operationId) is also stated conditionally.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_api_listARead-only
Reads records through any Business Central API List action found with bc_api_search, with server-side OData filter, select, orderby, top and skip. Rows come back as JSON objects with the API's field names plus "@odata.etag" for API pages (pass that etag as "If-Match" when modifying the record with bc_api_invoke). totalCount is the number of matching records and hasMore says whether another page exists; top defaults to 10.
Use it for any API page or API query — standard, ISV or custom — including ones bc_query does not know (bc_query covers only the Standard API v2.0 entities). Check field names with bc_api_describe first; a filter on a non-filterable field is rejected. When NOT to use: for tables without an API (bc_data_query runs an AL query over any table), or to change data (bc_api_invoke). Read-only. Runs on BC's own MCP server (BC 29+, API tools enabled); needs no web-client session.
| Name | Required | Description | Default |
|---|---|---|---|
| top | No | Rows to return (default 10). | |
| skip | No | Rows to skip, for paging with top. | |
| filter | No | OData filter, e.g. "city eq 'London'", "amount gt 1000 and open eq true", "contains(tolower(displayName),'contoso')". Operators: eq ne lt le gt ge and or; functions: contains startswith endswith tolower toupper. "or" only on the same field; no "not". Filterable fields are listed by bc_api_describe. | |
| select | No | Comma-separated fields to return, e.g. "number,displayName,balanceDue". Omit for all fields. | |
| company | No | Company to run in (exact, case-sensitive name from bc_list_companies). Defaults to the current session company, then BC_ODATA_COMPANY, then the user's default company in BC. | |
| orderby | No | OData orderby, e.g. "postingDate desc", "city, number". Not every API allows it: use it only when bc_api_describe lists "orderby" for the action. | |
| actionName | Yes | A List_* action name from bc_api_search, e.g. "List_CustomerLedgerEntries_QRY30302". |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| company | Yes | |
| hasMore | Yes | |
| rowCount | Yes | |
| actionName | Yes | |
| totalCount | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the readOnlyHint/openWorldHint annotations, it discloses return shape (JSON objects with API field names plus "@odata.etag"), paging signals (totalCount, hasMore), the top default of 10, and a concrete downstream behavior — pass the etag as "If-Match" with bc_api_invoke. It also states the runtime prerequisite (BC 29+, API tools enabled, no web-client session) and a failure mode (filter on a non-filterable field is rejected). Its "Read-only" statement is consistent with the annotations rather than contradicting them.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with what the tool does, then usage, then exclusions and environment — a logical order with dense, information-bearing sentences. Minor waste: "Read-only" repeats readOnlyHint, and "top defaults to 10" appears in both the prose and the schema; the three-paragraph block is longer than strictly necessary.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 7-parameter read tool with an output schema, annotations and 100% schema coverage, the description supplies everything else an agent needs: prerequisites (BC 29+, API tools enabled), discovery workflow (bc_api_search then bc_api_describe), paging model, and the exact alternative for non-API tables or writes. Nothing required to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so every parameter including actionName, filter, select, orderby and company is already documented in the schema, and the description largely restates those constraints (top default 10, filterable fields from bc_api_describe). It adds marginal cross-tool meaning by tying a returned "@odata.etag" to the "If-Match" parameter of bc_api_invoke, but does not explain parameter interactions or pitfalls beyond the schema. Baseline 3 in line with the high-coverage rule.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource: "Reads records through any Business Central API List action found with bc_api_search," and immediately scopes it via server-side OData filter/select/orderby/top/skip. It distinguishes itself from bc_query (Standard API v2.0 only), bc_data_query (tables without an API) and bc_api_invoke (writes), so an agent can pick it without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly covers the positive case ("Use it for any API page or API query — standard, ISV or custom — including ones bc_query does not know") and the negative cases ("When NOT to use: for tables without an API (bc_data_query...), or to change data (bc_api_invoke)"). It also routes the agent to bc_api_search and bc_api_describe as prerequisites, leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_api_searchARead-only
Searches the Business Central APIs available to this user and returns action names such as "List_Customers_PAG30009", "List_CustomerLedgerEntries_QRY30302" or "Create_Customer_PAG30009". It covers every installed API page and API query the user can read — Microsoft's standard APIs, ISV and custom extension APIs — not only the Standard API v2.0 entities bc_query knows. Names are <PAG|QRY>; List actions read, Create/Modify/Delete and bound/unbound actions change data.
Workflow: bc_api_search -> bc_api_describe (fields, filters, required parameters) -> bc_api_list to read, or bc_api_invoke to create, modify, delete or run an action. Matching is by name fragments, so search with entity words ("customer", "sales invoice", "ledger"); pass actionTypes ["List"] to find readable data only. Write actions appear only for API objects an admin enabled with those permissions in BC's MCP Server Configuration; reads are available for all installed APIs.
When NOT to use: to read tables that have no API (write an AL query with bc_data_query), or to work through the UI (bc_open_page). Runs on BC's own MCP server (BC 29+, API tools enabled), else BC_MCP_UNAVAILABLE; needs no web-client session.
| Name | Required | Description | Default |
|---|---|---|---|
| top | No | Maximum number of action names to return (default 15, range 5-50). | |
| company | No | Company to run in (exact, case-sensitive name from bc_list_companies). Defaults to the current session company, then BC_ODATA_COMPANY, then the user's default company in BC. | |
| searchText | Yes | Comma-separated fragments of the entity or action name to match, e.g. "customer, ledger" or "salesInvoice". Matching is by name (keyword search). | |
| actionTypes | No | Only these action types (default: all). "List" reads; "Create"/"Modify"/"Delete" write; "BoundAction" runs a record operation such as Post; "UnboundAction" runs a codeunit API. Write types exist only for API objects an admin enabled in BC's MCP Server Configuration. |
Output Schema
| Name | Required | Description |
|---|---|---|
| actions | Yes | |
| company | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=true and openWorldHint=false, and the description is consistent with that while adding substantive context the annotations cannot: write actions only surface when an admin enabled those permissions in BC's MCP Server Configuration, it requires BC 29+ with API tools enabled, and it fails as BC_MCP_UNAVAILABLE with no web-client session needed. Matching semantics (fragment-based keyword match, not fuzzy or wildcard) and result-count behavior are also disclosed.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and a labeled Workflow and "When NOT to use" section, which is good structure. Minor waste: the <Verb>_<EntitySet>_<PAG|QRY> naming convention is stated twice, and the read-vs-write mapping of List/Create/Modify/Delete repeats what the actionTypes schema description already says.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a four-parameter search tool with an output schema, this covers everything an agent needs: what is searched, how names are formed, the follow-up tool chain, permission gating on writes, and the unavailability failure mode. Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is already 100%, and the description still adds operational meaning: matching is by name fragments, entity-word queries work best, and actionTypes ["List"] yields readable-only data. It stops short of clarifying interactions such as how company scoping affects results, which is left entirely to the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Searches the Business Central APIs available to this user") and gives concrete output examples like "List_Customers_PAG30009". It explicitly draws the boundary against bc_query ("not only the Standard API v2.0 entities bc_query knows"), so an agent can discriminate it from siblings without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides an explicit workflow chain (bc_api_search -> bc_api_describe -> bc_api_list / bc_api_invoke) and names the alternative tools for the excluded cases (bc_data_query for tables without an API, bc_open_page for UI work). It also gives a concrete search tactic ("search with entity words", pass actionTypes ["List"] for reads only).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_close_pageAIdempotent
Closes an open Business Central page and frees its server-side resources including the WebSocket form session. Always call this when you are finished working with a page to prevent resource leaks on the BC server. Requires a pageContextId from bc_open_page.
After closing, the pageContextId becomes invalid -- any subsequent bc_read_data, bc_write_data, bc_execute_action, or bc_navigate calls using it will fail. It is safe to call this even if prior operations on the page encountered errors. If you opened a drill-down page via bc_navigate (which returns a new pageContextId), close both the drill-down page and the original list page when done.
Do NOT call this on a dialog's pageContextId (from dialogsOpened): answer the dialog with bc_respond_dialog instead, e.g. "cancel" to dismiss it. Closing a modal can accept it, so this tool refuses.
Do NOT call this in the middle of a multi-step workflow -- finish all reads, writes, and actions on the page first. Do NOT call this to "reset" a page; use bc_read_data to refresh data instead.
| Name | Required | Description | Default |
|---|---|---|---|
| operationId | No | Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes. | |
| pageContextId | Yes | Page context ID returned by bc_open_page. Becomes invalid after closing. |
Output Schema
| Name | Required | Description |
|---|---|---|
| success | Yes | |
| replaySafe | Yes | |
| dialogsOpened | Yes | |
| requiresDialogResponse | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations: it discloses that pageContextId is invalidated, that subsequent read/write/action/navigate calls on it will fail, that calling after errors is safe, that closing a modal could accept it so the tool refuses, and the drill-down two-page cleanup rule. Annotations already cover safety/idempotency, but this adds genuine lifecycle and side-effect context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well front-loaded, with the primary guidance first and exclusions grouped after. It is somewhat long, with mild redundancy between "frees its server-side resources" and the resource-leak warning, but nearly every sentence contributes an operative rule.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given an output schema, full annotation set, and 100% schema coverage, the description fills the remaining behavioral gap: invalidation, workflow timing, dialog handling, and drill-down cleanup. Nothing an agent needs to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and the schema itself documents operationId and pageContextId thoroughly. The description adds meaning the schema does not: that pageContextId must come from bc_open_page and becomes invalid after closing. Modest but real value above the schema baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Closes an open Business Central page and frees its server-side resources including the WebSocket form session"), and the effect on the session distinguishes it from siblings like bc_navigate and bc_open_page. An agent can identify this as the lifecycle-termination tool without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use ("Always call this when you are finished working with a page"), explicit when-not-to (not mid-workflow, not to reset a page, not on dialog contexts), and names the alternatives: bc_respond_dialog for dialogs, bc_read_data to refresh. This is the strongest dimension of the definition.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_data_find_tablesARead-only
Finds Business Central tables by name or by meaning and returns their table IDs and names. This is the first step of the data query workflow: bc_data_find_tables -> bc_data_get_table_schema (exact field names) -> bc_data_get_table_relations (join paths, when joining tables) -> bc_data_query (compile and run an AL query). Use searchMode "keyword" with comma-separated name fragments when you can guess the table name ("customer, ledger entry"), and "semantic" with a plain-language description when you cannot ("posted invoices that are still unpaid"); semantic search ranks the source tables of matching pages and returns a score. Semantic search relies on BC's Copilot metadata search and can come back empty where that is not set up (seen on an on-prem BC 29.1 container); fall back to "keyword" when it returns no tables.
When to use: before writing an AL query for bc_data_query, to discover the table behind a business concept, including tables from extensions and tables that have no API page. When NOT to use: to read records from a known Standard API entity (bc_query is simpler), or to find a page to open in the UI (use bc_search_pages). The data query tools run on BC's own MCP server (BC 29+) and need "Data Query Tools (Preview)" enabled in BC's MCP Server Configuration; otherwise they return BC_MCP_UNAVAILABLE. They need no web-client session and no open page.
| Name | Required | Description | Default |
|---|---|---|---|
| top | No | Maximum number of tables to return (default 20, max 50). | |
| company | No | Company to run in (exact, case-sensitive name from bc_list_companies). Defaults to the current session company, then BC_ODATA_COMPANY, then the user's default company in BC. | |
| searchMode | Yes | "keyword" matches table names by substring; "semantic" searches page metadata by meaning and returns the source tables of the best-matching pages. | |
| searchText | Yes | For searchMode "keyword": one or more comma-separated table name fragments (e.g., "customer, ledger entry"). For "semantic": a natural-language description of the data you need (e.g., "unpaid invoices from vendors"). |
Output Schema
| Name | Required | Description |
|---|---|---|
| tables | Yes | |
| company | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond readOnlyHint/openWorldHint by disclosing the runtime prerequisite ('Data Query Tools (Preview)' enabled, BC 29+), the concrete failure mode BC_MCP_UNAVAILABLE, the fact that no web-client session or open page is required, and that semantic search depends on Copilot metadata and can return empty on some deployments. That last point is an unusual and genuinely useful degradation behavior with a stated fallback.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then a chained workflow, mode guidance, and a clearly labeled when/when-not block — well structured and skimmable. It is dense and includes some detail (specific on-prem 29.1 container observation) that could be trimmed, but no sentence is pure filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 4-parameter discovery tool with an output schema and read-only annotations, this covers the missing pieces: prerequisites, failure handling, mode selection, workflow position, and sibling alternatives. An agent has everything needed to call it and to recover from an empty semantic result.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so baseline is 3; the description adds operational meaning by tying searchMode values to concrete usage patterns and giving contrasting example inputs ('customer, ledger entry' vs. 'posted invoices that are still unpaid'), plus explaining that semantic ranks source tables of matching pages and returns a score. It does not touch top or company, which is acceptable given the schema documents them.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opening sentence gives a specific verb (Finds) and resource (Business Central tables) plus the two search modes and the return payload (table IDs and names). It also explicitly distinguishes itself from bc_query and bc_search_pages, so an agent can route correctly without reading the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides an explicit ordered workflow (find_tables -> get_table_schema -> get_table_relations -> bc_data_query), a rule for choosing searchMode by whether the table name is guessable, and a dedicated 'When to use' / 'When NOT to use' block naming the alternatives bc_query and bc_search_pages. Nothing about selection is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_data_get_table_relationsARead-only
Lists the TableRelation links of a Business Central table, so you can join tables correctly in an AL query. direction "outgoing" returns the tables this table points to and the fields that link them (e.g. Customer -> Country/Region via "Country/Region Code"); "incoming" returns the tables that point to this one (e.g. which ledger tables reference Customer), capped at 50 source tables with incomingTruncated/incomingTotalCount when cut. Conditional relations carry a "conditions" text (e.g. "Type=CONST(Item)").
Use it before writing a multi-dataitem query for bc_data_query: the via-field gives the DataItemLink. Pass relatedToTableIds to ask only about specific tables, which is also how to get past a truncated incoming list. Follow up with bc_data_get_table_schema for exact field names and types. When NOT to use: for single-table queries, or to read data. Runs on BC's own MCP server (BC 29+, "Data Query Tools (Preview)" enabled); needs no web-client session.
| Name | Required | Description | Default |
|---|---|---|---|
| company | No | Company to run in (exact, case-sensitive name from bc_list_companies). Defaults to the current session company, then BC_ODATA_COMPANY, then the user's default company in BC. | |
| tableId | Yes | Numeric BC table ID (e.g., 18 Customer, 17 G/L Entry, 37 Sales Line). Find IDs with bc_data_find_tables. | |
| direction | Yes | "outgoing": tables this table points to through TableRelation fields (e.g., Customer -> Country/Region). "incoming": tables that point to this one (capped at 50 source tables). | |
| relatedToTableIds | No | Only relations to/from these table IDs. Use it to find the join path between two known tables, or when an incoming answer was truncated. |
Output Schema
| Name | Required | Description |
|---|---|---|
| hint | No | |
| company | Yes | |
| tableId | Yes | |
| incoming | No | |
| outgoing | No | |
| tableName | Yes | |
| incomingTruncated | No | |
| incomingTotalCount | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, and the description stays consistent while adding substantial behavior the annotations cannot express: the 50-source-table cap, the incomingTruncated/incomingTotalCount truncation markers, the shape of the "conditions" text for conditional relations, and the via-field that supplies the DataItemLink. It also discloses runtime prerequisites (BC 29+, "Data Query Tools (Preview)" enabled, no web-client session needed), which changes whether the agent should attempt the call at all.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then direction semantics, then the workflow rule, then exclusions and environment — a sensible order with no filler paragraphs. It is dense and slightly long, and the Customer/Country-Region example appears twice (once per direction), but each sentence still carries information an agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists so return structure needn't be explained, yet the description still flags the truncation fields and the conditions text format that an agent would otherwise miss. Combined with annotations, full schema coverage, prerequisites, and the handoff to bc_data_query and bc_data_get_table_schema, nothing needed to call this correctly is absent.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds genuine meaning: concrete direction examples (Customer -> Country/Region via "Country/Region Code" for outgoing; ledger tables referencing Customer for incoming) and two distinct use cases for relatedToTableIds (finding a join path between two known tables, escaping a truncated incoming list). These are usage semantics rather than restatements of the schema text.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Lists the TableRelation links of a Business Central table") plus the downstream purpose (joining tables in an AL query). It explicitly separates itself from bc_data_get_table_schema and bc_data_query by naming them as follow-up/precursor tools, so an agent can pick it out of a 24-tool sibling list immediately.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit trigger ("Use it before writing a multi-dataitem query for bc_data_query"), an explicit anti-trigger ("When NOT to use: for single-table queries, or to read data"), and names the alternative for the follow-up step (bc_data_get_table_schema). It also explains the recovery path when the incoming list is truncated (pass relatedToTableIds), which is a real decision the agent will face.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_data_get_table_schemaARead-only
Returns the fields of a Business Central table as compact pipe-delimited text: a header with the table name, ID and primary key, then one line per field with its exact name and type, option values for Option fields, the formula for FlowFields (e.g. "FlowField: Sum("Cust. Ledger Entry"."Amount")"), and TableRelation targets ("rel: Country/Region"). Field names differ between environments and installed extensions, so call this for EVERY table before writing an AL query for bc_data_query — a wrong name is a compile error (AL0345).
Use fieldClass to list only Normal fields, FlowFields or FlowFilters, and nameContains to keep the answer small on wide tables (e.g. ["No.", "Amount", "Date"]). Get table IDs from bc_data_find_tables. When NOT to use: to read field values (this returns metadata only; run a query with bc_data_query, or read a page with bc_open_page). Runs on BC's own MCP server (BC 29+, "Data Query Tools (Preview)" enabled); needs no web-client session.
| Name | Required | Description | Default |
|---|---|---|---|
| company | No | Company to run in (exact, case-sensitive name from bc_list_companies). Defaults to the current session company, then BC_ODATA_COMPANY, then the user's default company in BC. | |
| tableId | Yes | Numeric BC table ID (e.g., 18 Customer, 17 G/L Entry, 37 Sales Line). Find IDs with bc_data_find_tables. | |
| fieldClass | No | Only fields of this class. FlowFields are calculated (e.g., Customer "Balance (LCY)"); FlowFilters only narrow FlowField calculations. | |
| nameContains | No | Only fields whose name contains any of these fragments (case-insensitive), e.g. ["No.", "Amount"]. Use it on wide tables to keep the answer small. |
Output Schema
| Name | Required | Description |
|---|---|---|
| schema | Yes | |
| company | Yes | |
| tableId | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, so safety is known. The description goes beyond them usefully by disclosing that Option values, FlowField formulas, and TableRelation targets are included, plus the runtime prerequisite (BC 29+, "Data Query Tools (Preview)" enabled, no web-client session). However, much of the format detail is already carried by the output schema, so the added behavioral content is real but partly redundant.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the output contract, then practical routing, then exclusions and runtime prerequisites — a sensible order with no filler sentences. It is dense, and the formula/TableRelation examples are long, but each clause carries actionable information rather than padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present, the description needn't explain return values in full, yet it still characterizes the field-line format enough to parse the response. Combined with the prerequisite note, sibling routing, and filter strategy, an agent has everything needed to invoke this correctly on the first try.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds operational meaning: fieldClass is framed as a way to list only a given class, and nameContains is explained as a size-control mechanism for wide tables with a concrete example fragment list. That is guidance on how to combine filters, not just restatement of the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource ("Returns the fields of a Business Central table") and immediately specifies the output shape (pipe-delimited text with header, name, ID, primary key). It is unmistakably distinct from siblings bc_data_find_tables (ID lookup) and bc_data_query (value retrieval).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use: call this for EVERY table before writing an AL query for bc_data_query, with the consequence spelled out (wrong name is AL0345 compile error). It also names where table IDs come from (bc_data_find_tables) and gives a dedicated "When NOT to use" clause routing to bc_data_query and bc_open_page for values.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_data_queryARead-only
Compiles an AL query object you write and, with returnData: true, runs it against any Business Central table the user can read — including tables with no API page and tables from extensions — and returns a page of rows. BC compiles the query on the fly as a dynamic object (nothing is published) and always runs it ReadOnly with the user's permissions and security filters. Without returnData it only compiles and returns the columns and dataitems, which is a cheap way to validate a query; compile errors come back as DATA_QUERY_ERROR with AL diagnostics (e.g. AL0345 for an unknown field) that you can fix and retry.
Workflow: bc_data_find_tables (table IDs) -> bc_data_get_table_schema (exact field names, for every table) -> bc_data_get_table_relations (join fields) -> bc_data_query. Joins, filters, aggregations (Method = Sum/Count/...), date grouping and OrderBy all work, so prefer letting BC aggregate over fetching raw rows. FlowFields can only be filtered with ColumnFilter, not DataItemTableFilter (AL0672). The bc-data-query skill has syntax and worked examples. Results are paged: top is 1-50 (default 10), skip pages forward, totalCount comes on the first page, hasMore says whether another page exists; rows are arrays in column order.
When to use: ad-hoc reads, totals and joins over data the Standard API does not expose, or when bc_query's entities do not fit. When NOT to use: to change data (read-only; use bc_write_data / bc_execute_action), for simple reads of Standard API entities (bc_query), or to see what a page shows (bc_open_page). Needs BC 29+ with "Data Query Tools (Preview)" enabled in BC's MCP Server Configuration, else BC_MCP_UNAVAILABLE (Microsoft has announced billing for these tools at general availability). No web-client session needed.
| Name | Required | Description | Default |
|---|---|---|---|
| top | No | Rows per page when returnData is true (default 10, max 50). | |
| skip | No | Rows to skip for paging (max 100000). totalCount is only returned on the first page (skip 0). Paging needs a deterministic order: add OrderBy if BC says pagination is not supported. | |
| company | No | Company to run in (exact, case-sensitive name from bc_list_companies). Defaults to the current session company, then BC_ODATA_COMPANY, then the user's default company in BC. | |
| queryText | Yes | Complete source of ONE AL query object, e.g. `query 50100 TopCustomers { elements { dataitem(Cust; Customer) { column(No; "No.") { } column(Name; Name) { } } } }`. Object ID and name are free (any ID works; nothing is published). Supports AL query features: joined dataitems (DataItemLink), filters (DataItemTableFilter on normal fields, ColumnFilter for FlowFields), aggregations (Method = Sum/Count/Average/Min/Max), date grouping (Method = Year/Month/Day), OrderBy. Limit rows with `top`, not TopNumberOfRows. Field names must match bc_data_get_table_schema exactly. | |
| returnData | No | false (default): only compile and return the column/dataitem schema — use it to check a query cheaply. true: also run it and return a page of rows. |
Output Schema
| Name | Required | Description |
|---|---|---|
| top | No | |
| rows | No | |
| skip | No | |
| status | Yes | |
| columns | Yes | |
| company | Yes | |
| hasMore | No | |
| rowCount | No | |
| dataItems | No | |
| totalCount | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond readOnlyHint/openWorldHint, it discloses that nothing is published (dynamic compile), that execution is always ReadOnly under the user's permissions and security filters, that compile failures surface as DATA_QUERY_ERROR with AL diagnostics (e.g. AL0345), and the BC 29+ preview-flag requirement with BC_MCP_UNAVAILABLE as the failure mode. The read-only claim is consistent with the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose is front-loaded and the body is organized into labeled Workflow / When to use / When NOT to use segments, so it scans well. It is on the long side, and the parenthetical about Microsoft's future billing is useful context but the least load-bearing sentence.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool whose hard part is authoring AL query syntax, the description covers the query shape, supported features, aggregation preference, paging semantics (top/skip/totalCount/hasMore, rows as arrays in column order), and points to the skill for worked examples. An output schema exists, and the description does not waste space re-explaining return values.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description still adds real meaning — returnData:false as a cheap compile-only validation path, using `top` rather than AL TopNumberOfRows, ColumnFilter vs DataItemTableFilter for FlowFields (AL0672), and OrderBy being required for deterministic paging. It stops short of restating per-parameter defaults, which the schema already handles.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb and resource: compiles an AL query object and optionally runs it against any readable Business Central table. It explicitly carves out its niche versus siblings (bc_query's entities, bc_read_data, bc_write_data, bc_execute_action, bc_open_page), so an agent can route correctly without opening other schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides an explicit 'When to use' / 'When NOT to use' block naming the alternative tool for each excluded case, plus a concrete prerequisite workflow (find_tables -> get_table_schema -> get_table_relations -> data_query) and a version/feature-flag requirement. Very little is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_execute_actionADestructive
Executes either a named action OR a cue-tile drill-down on an open page. Pass action for header / line / system actions (Post, Delete, New, Release). Pass cue for Role Center cue tiles to open the underlying list (e.g. cue: "Sales Quotes" with section: "subpage:Activities" opens the Sales Quotes list). Requires a pageContextId from bc_open_page.
For cue drill-down, also pass section pointing at the subpage that owns the cuegroup. The returned openedPages array contains the targetPageContextId of the newly-opened list page.
For a named action: validates the action is enabled, sends the InvokeAction RPC, applies the resulting events, and returns updatedFields / changedSections / dialogsOpened / openedPages.
Use exactly one of "action" or "cue" -- passing both is an error.
If the action triggers a confirmation dialog or modal page, the response includes a dialogsOpened array with the dialog's formId and details. When requiresDialogResponse is true, you must follow up with bc_respond_dialog to confirm or cancel. Each dialogsOpened entry carries its own pageContextId: when the dialog has input fields (listed in its fields array), fill them with bc_write_data against that pageContextId before pressing OK with bc_respond_dialog. Texts the action shows through AL Message() (e.g. "3 documents were sent") come back in messages: [{ text, type }].
Row-scoped actions (Delete, Edit on a list row) require targeting a specific row. Use rowIndex (0-based) or bookmark to specify which row the action applies to. For Document pages, use section to disambiguate between header and line actions (e.g., "Delete" on header deletes the whole document, "Delete" on "lines" deletes one line).
For batch operations across multiple rows (e.g. deleting several lines at once), pass bookmarks (an array) instead of bookmark/rowIndex -- the first entry is the anchor row. Only actions that consume a selection (e.g. Delete) act on all listed rows; current-row-only actions (Edit, View, DrillDown, New) reject bookmarks[] and must use bookmark/rowIndex instead. bookmarks[] is mutually exclusive with bookmark, rowIndex, and cue.
Pass expectedStateVersion (from a prior bc_read_data or bc_open_page stateVersion field) to guard against acting on drifted state. If the page has been mutated by async events or a sibling operation since that read, the call is immediately rejected with code STALE_CONTEXT before touching BC. Re-read with bc_read_data to get the current stateVersion, then retry. Omit expectedStateVersion to skip the check.
Do NOT use this for writing field values -- use bc_write_data. Do NOT use this to open records from a list -- use bc_navigate with drill_down action instead.
Examples:
Drill into a cue tile: { "pageContextId": "rc1", "section": "subpage:Activities", "cue": "Sales Quotes" }
Post a sales order: { "pageContextId": "so1", "action": "Post" }
Delete a row: { "pageContextId": "list1", "action": "Delete", "bookmark": "..." }
Create new record: { "pageContextId": "abc", "action": "New" }
Delete a document line: { "pageContextId": "abc", "action": "Delete", "section": "lines", "rowIndex": 2 }
Delete multiple rows in one call: { "pageContextId": "list1", "action": "Delete", "bookmarks": ["bk1", "bk2"] } -- only selection-consuming actions like Delete act on all listed rows
Execute with staleness guard: { "pageContextId": "abc", "action": "Post", "expectedStateVersion": 5 }
If the action produces a file (Open in Excel, Print, export), its bytes appear in downloads[] (an operationId replay returns the metadata with error code NOT_REPLAYED instead of the bytes); links BC would open externally appear in externalUris[] and are never fetched by the server.
| Name | Required | Description | Default |
|---|---|---|---|
| cue | No | Cue tile name to drill down on (e.g. "Sales Quotes", "Pending Approvals"). Use with section pointing at the subpage that owns the cuegroup. Use action OR cue, not both. | |
| action | No | Action caption name to execute (case-insensitive). Use action OR cue, not both. Must match a visible, enabled action from bc_open_page response. | |
| section | No | Section context. Required when using cue; optional for action. Examples: "lines", "subpage:Activities". | |
| bookmark | No | Stable row identifier for row-scoped actions. | |
| rowIndex | No | 0-based row position for row-scoped actions. | |
| bookmarks | No | Stable row identifiers for a MULTI-ROW action (batch delete, apply-entries). The first bookmark is the anchor/current row. Mutually exclusive with bookmark, rowIndex, and cue. Bookmarks must come from a bc_read_data of the same section and must still be loaded. Only actions that consume a selection (e.g. Delete) act on all rows; Edit/View/DrillDown/New use the anchor only and are rejected with bookmarks[]. | |
| operationId | No | Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes. | |
| pageContextId | Yes | Page context ID returned by bc_open_page. | |
| expectedStateVersion | No | Opt-in staleness guard. Pass the stateVersion from a prior bc_read_data or bc_open_page response. If the page state has changed since that read (async events or sibling writes mutated it), the call is rejected immediately with code STALE_CONTEXT before touching BC. Omit to skip the check. |
Output Schema
| Name | Required | Description |
|---|---|---|
| dialog | No | |
| success | Yes | |
| messages | Yes | |
| downloads | Yes | |
| replaySafe | Yes | |
| openedPages | Yes | |
| externalUris | Yes | |
| dialogsOpened | Yes | |
| updatedFields | No | |
| changedSections | Yes | |
| requiresDialogResponse | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations, it discloses substantial behavior: validation and InvokeAction RPC execution, event application, returned dialogsOpened and messages, follow-up bc_respond_dialog requirements, STALE_CONTEXT rejection, operationId replay behavior including NOT_REPLAYED, and downloads/externalUris handling. These details are consistent with destructiveHint=true and idempotentHint=false.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded and organized, but it is very long for a tool definition and repeats details already present in the schema, such as bookmarks[] mutual exclusivity and expectedStateVersion semantics. It also describes return structures like dialogsOpened and openedPages even though an output schema exists, which adds length without adding proportional value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given a destructive, multi-mode 9-parameter tool with dialogs, batch operations, staleness guards, and replay semantics, the definition covers the behavioral context an agent needs to invoke it correctly. The presence of an output schema means it need not fully document return values, and the annotations already carry the safety profile.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3, but the description adds practical meaning beyond the schema: it explains how section disambiguates header vs. line actions on Document pages (e.g., Delete on header vs. Delete on lines) and provides combined parameter examples. Much of the remaining mutual-exclusivity and expectedStateVersion detail duplicates the already-detailed schema descriptions, so it does not earn a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: "Executes either a named action OR a cue-tile drill-down on an open page." It immediately distinguishes the two modes with concrete examples (Post, Delete, New, Release vs. cue tiles) and explicitly routes writing and navigation elsewhere, so an agent can tell this apart from siblings like bc_write_data and bc_navigate.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It states exactly when to use action vs. cue, that passing both is an error, when section is required, how row-scoped and multi-row operations differ, and when to pass expectedStateVersion. It also gives explicit exclusions: do not use for writing field values, use bc_write_data; do not use to open records, use bc_navigate with drill_down.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_get_operationARead-only
Look up what happened to an earlier mutating call made with an operationId (bc_write_data, bc_execute_action, bc_respond_dialog, bc_wizard_navigate, bc_close_page, bc_switch_company, bc_run_report, bc_upload_file). Returns the operation ledger entry: status (sent, completed, unknown), tool, timestamps, replaySafe, the stored result, and the target snapshot captured before the call ran.
When the outcome is unknown or replaySafe is false, this tool also re-reads the target in the current session: it re-opens the page by page id and record bookmark, reads the identified fields (the record's key fields and any written fields, plus the lines of a document such as a sales order) or the targeted row, closes the page again, and returns them as readBack plus evidenceHash, a SHA-256 of the canonical JSON of readBack. A key field marked source 'bookmark' has no control on the page and is decoded from the record bookmark: an Option key shows its ordinal (Sales Order "Document Type" = '1', meaning Order), not its caption. Use it after OPERATION_IN_DOUBT, a timeout, or SESSION_LOST to decide whether the change landed before acting again under a new operationId.
This tool changes nothing in BC. A completed, replay-safe operation is returned without a read-back. If the operation ran in another company, readBackError COMPANY_MISMATCH tells you to bc_switch_company first. If a dialog is open in the session, readBackError MODAL_OPEN: answer it with bc_respond_dialog first (opening a page would cancel it). If the record no longer exists (a fully posted document is deleted), readBackError RECORD_NOT_OPENED. If the re-opened page shows a different key value than the recorded one, readBackError KEY_MISMATCH returns readBack WITHOUT an evidenceHash: the page landed on another record, so the read-back proves nothing about the target. Do NOT use it to read arbitrary records -- use bc_open_page and bc_read_data for that.
| Name | Required | Description | Default |
|---|---|---|---|
| operationId | Yes | The operationId you passed to a mutating tool (bc_write_data, bc_execute_action, ...). |
Output Schema
| Name | Required | Description |
|---|---|---|
| entry | Yes | |
| readBack | No | |
| evidenceHash | No | |
| readBackError | No |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint/openWorldHint; the description goes far beyond, disclosing the conditional read-back behavior, that a completed replay-safe operation skips the read-back, that BC state is unchanged, and four distinct failure modes (COMPANY_MISMATCH, MODAL_OPEN, RECORD_NOT_OPENED, KEY_MISMATCH) with their remediation. The bookmark-decoding caveat for 'source: bookmark' keys is exactly the non-obvious behavioral detail that earns a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Dense but well front-loaded: it opens with what the tool does and the return shape, then behavior, then error routing. Some material (the full return-field enumeration) partly duplicates the output schema, but almost every sentence carries operational weight.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a lookup/diagnostic tool with an output schema, the description supplies all the decision-relevant context an agent needs: what triggers a call, what a normal vs. read-back response means, and how to resolve each readBackError. Nothing needed to invoke or interpret it is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There is a single parameter with 100% schema description coverage, so the schema already defines operationId's origin. The description restates the same idea (operationId from the listed mutating tools) without adding format/syntax meaning. Baseline 3 is correct.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a precise verb+resource ('look up what happened to an earlier mutating call made with an operationId') and enumerates the sibling tools that produce such operations. It also explicitly contrasts itself with bc_open_page/bc_read_data for arbitrary reads, so an agent can route correctly without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit trigger conditions ('after OPERATION_IN_DOUBT, a timeout, or SESSION_LOST') and an explicit exclusion plus alternative ('Do NOT use it to read arbitrary records -- use bc_open_page and bc_read_data'). This is the when/when-not/alternative trifecta.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_list_companiesARead-only
List all companies available in the current Business Central environment. Returns an array of company names along with the currently active company name. Use this before bc_switch_company to verify the target company exists and to discover available companies.
This tool opens the BC Companies system page internally, reads all entries, and closes it. It does not affect your currently open pages or session state. No parameters are required.
Do NOT use this if you already know the company name -- call bc_switch_company directly. If you need to work with data in a specific company, use bc_switch_company followed by bc_open_page.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| companies | Yes | |
| currentCompany | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=false, but the description goes further by disclosing the internal mechanism (opens the BC Companies system page, reads entries, closes it) and guaranteeing it won't disturb the currently open pages or session state. That side-effect disclosure is exactly the behavioral context an agent needs before chaining calls.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and return shape, then usage, then mechanism. It is slightly longer than necessary: the closing sentence ('If you need to work with data in a specific company, use bc_switch_company followed by bc_open_page') partially restates the DO-NOT guidance already given.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so return values need not be explained, and the description still summarizes the return (company names plus the active company). Combined with annotations covering the safety profile, the definition is complete for a zero-param, read-only listing tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters and the description states 'No parameters are required', which is the correct baseline for a 0-param tool. There is nothing further to disambiguate, so it sits at the expected ceiling without adding special insight.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List all companies available in the current Business Central environment') and clarifies scope with 'in the current Business Central environment' plus the return payload. It also names the sibling tool it complements (bc_switch_company), so the agent can place it relative to alternatives 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit when-to-use ('before bc_switch_company to verify the target company exists'), an explicit when-not ('Do NOT use this if you already know the company name'), and names the alternative path (bc_switch_company, then bc_open_page). Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_lookupARead-only
Enumerates candidate values for a related-table (FK) field by invoking BC's built-in Lookup on the field and returning the result rows. Use this when you need to see valid choices for a field before writing it with bc_write_data — for example, listing all Salesperson Codes before filling "Salesperson Code" on a Customer Card, or listing all Gen. Bus. Posting Groups before selecting one.
Use bc_lookup when the field has isLookup=true in the bc_open_page or bc_read_data response. The field must be on an open page (pageContextId from bc_open_page). The operation is non-mutating: it opens the lookup form and always cancels without selecting a value, leaving the source page field unchanged. Provide an optional search string to filter candidates (e.g., search:"AR" to narrow to codes starting with "AR").
Do NOT use bc_lookup for option/enum fields — those already expose their fixed choices in the options array of bc_open_page and bc_read_data responses. Do NOT use for fields where isLookup is false or absent. Do NOT use for fields that carry lookupCustom=true in the bc_open_page or bc_read_data response: isLookup=true together with lookupCustom=true means the field drives a custom AL OnLookup trigger that BC does not expose as an enumerable lookup form — bc_lookup returns a clear error for these. Use the field's own UI/AssistEdit instead.
Workflow: bc_open_page → inspect field isLookup=true → bc_lookup to list candidates → bc_write_data with chosen value.
| Name | Required | Description | Default |
|---|---|---|---|
| field | Yes | Caption of the field to enumerate lookup candidates for (e.g., "Salesperson Code", "Gen. Bus. Posting Group"). Must be an editable FK/related-table field that has a lookup (isLookup=true in bc_open_page or bc_read_data response). | |
| search | No | Optional search string to filter candidates (e.g., "AR" to narrow to codes starting with AR). Applied via BC's native search on the lookup list. Omit to return all rows up to maxRows. | |
| maxRows | No | Maximum number of candidate rows to return. Defaults to 50. Max 500. BC may return fewer if the table has fewer records. | |
| pageContextId | Yes | Page context ID of the open page (card or list) returned by bc_open_page. |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| totalFound | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only carry readOnlyHint/openWorldHint; the description adds substantial context beyond them: the operation opens the lookup form and 'always cancels without selecting a value, leaving the source page field unchanged' — a non-obvious side-effect disclosure. It also states that lookupCustom=true fields produce a clear error, which is behavioral information the annotations cannot convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well-front-loaded with purpose first, then usage, exclusions, and workflow. Slightly verbose with repeated field attribution ('in the bc_open_page or bc_read_data response' appears multiple times), but every sentence carries useful information and the structure is navigable.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers purpose, prerequisites, error cases, alternatives, and an end-to-end workflow. Output schema exists so return-value explanation is unnecessary. Nothing an agent needs to call this correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% and each parameter is described in the schema, so the baseline of 3 applies. The description adds the search filter example ('AR' for codes starting with 'AR') and the pageContextId requirement ('field must be on an open page'), but not rich semantic detail beyond what the schema provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Enumerates candidate values for a related-table (FK) field by invoking BC's built-in Lookup') and immediately distinguishes from siblings by naming bc_write_data, bc_open_page, and bc_read_data with concrete examples. An agent can pick it apart from bc_read_data or bc_query without opening schemas.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use ('field has isLookup=true'), when-not-to-use (option/enum fields, isLookup=false/absent, lookupCustom=true) with the exact error behavior noted, and names the correct alternative ('Use the field's own UI/AssistEdit instead'). A full workflow chain is provided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_open_pageARead-only
Opens a Business Central page by its numeric page ID and returns its complete state as a list of sections. Each section has a sectionId, kind (header / lines / factbox / subpage / requestPage), caption, and the appropriate content shape. Card-shape sections (most headers, factboxes, requestPages) carry fields[] (and headers also carry actions[]). List-shape sections (lines, list-bodied headers, repeater subpages) carry rows[] and totalRowCount. The header section adapts to its page: it is card-shape on Card pages and list-shape on List pages -- the kind stays "header" either way for path stability. This is the entry point for interactive, page-scoped work -- it returns a pageContextId that the page-scoped tools (bc_read_data, bc_write_data, bc_execute_action, bc_navigate, bc_respond_dialog, bc_close_page, bc_lookup) take as input, plus a stateVersion you can pass as expectedStateVersion to bc_write_data / bc_execute_action to guard against stale state. (bc_query, bc_run_report, bc_search_pages, bc_list_companies, and bc_switch_company do NOT need a pageContextId.) For bulk, read-only data over standard entities, prefer bc_query -- it needs no open page. Use bc_search_pages first if you do not know the page ID for an entity.
Card pages (single-record views like Customer Card=21) return one header (card-shape) plus any FactBox sections attached to the page. List pages (Customer List=22) return a header (list-shape, rows[] populated). Document pages (Sales Order=42) return a header (card-shape), a "lines" list-shape section with the document lines, and any FactBoxes.
Option/enum fields and boolean fields in card-shape sections carry two extra properties: "options" (the allowed choices as [{text, value}]) and "selectedOption" (the currently chosen entry). Always use the "value" string from "options" as the SaveValue payload when writing an enum field -- do NOT guess or invent values. Example: Item Card "Type" field returns options=[{text:"Inventory",value:"0"},{text:"Service",value:"1"},{text:"Non-Inventory",value:"2"}] and selectedOption={text:"Inventory",value:"0"}.
Typical workflow: bc_open_page -> bc_read_data (refresh / filter / paginate a section) -> bc_write_data (edit fields in any section) -> bc_execute_action (post / release / delete) -> bc_close_page. Always call bc_close_page when done. Do NOT call this if the page is already open -- reuse the existing pageContextId. On BC Online the first call opens a local Microsoft sign-in window (password and Authenticator stay in that window; never pass them as tool arguments). Retry after completing sign-in.
Optional bookmark parameter opens a Card page to a specific record. Bookmarks come from list rows in any prior section.
Examples:
{ "pageId": 22 } opens Customer List. Sections: [{ "sectionId": "header", "kind": "header", "rows": [...], "actions": [...] }] (no fields[] on a list-shape header).
{ "pageId": 21, "bookmark": "..." } opens Customer Card. Sections include the header card plus FactBoxes (e.g. { "sectionId": "factbox:Customer Statistics", "kind": "factbox", "fields": [...] }).
| Name | Required | Description | Default |
|---|---|---|---|
| pageId | Yes | Numeric BC page ID (e.g., 22 for Customer List, 21 for Customer Card). Use bc_search_pages to find IDs. | |
| bookmark | No | Open the page to a specific record. Bookmarks come from list row results in bc_open_page or bc_read_data. | |
| tenantId | No | BC tenant ID. Defaults to the server-configured tenant. Only needed in multi-tenant deployments. |
Output Schema
| Name | Required | Description |
|---|---|---|
| caption | Yes | |
| isModal | Yes | |
| pageType | Yes | |
| sections | Yes | |
| stateVersion | Yes | |
| pageContextId | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only give readOnlyHint=true and openWorldHint=false, so the description carries real weight: it discloses the BC Online sign-in window behaviour and the instruction never to pass credentials as arguments, the pageContextId/stateVersion lifecycle, the stale-state guard via expectedStateVersion, and the obligation to call bc_close_page. That is substantive operational context beyond the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and return shape, then workflow, then guardrails, with worked examples at the end. It is long, but nearly every paragraph maps to a distinct decision the agent must make; the enum/options narrative is the one stretch that could be tightened or deferred to bc_write_data.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a stateful, page-scoped entry point, the description covers the return structure, the shape variation across Card/List/Document pages, the lifecycle (open -> read/write/act -> close), and the auth edge case. An output schema exists, and the description still sufficiently frames how sections should be interpreted, so an agent can call and consume this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real semantics: bookmark 'opens a Card page to a specific record' and 'Bookmarks come from list rows in any prior section', which tells the agent where the value originates. It also documents the options/selectedOption value-string convention for enum fields, which is directly relevant to payloads derived from this tool's output.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Opens a Business Central page by its numeric page ID') and immediately defines the return shape (list of sections with sectionId/kind/caption/content). It explicitly differentiates itself from siblings, calling out that bc_query needs no open page and that bc_search_pages should be used first when the ID is unknown.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use ('entry point for interactive, page-scoped work'), when-not ('Do NOT call this if the page is already open -- reuse the existing pageContextId'), and named alternatives for adjacent jobs (bc_query for bulk read-only, bc_search_pages for ID lookup). It also states which sibling tools do and do not need a pageContextId, removing all routing ambiguity.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_queryARead-only
Reads records from Business Central in bulk using the Standard API v2.0 (OData/REST on port 7048). Use bc_query for efficient server-side filtered, sorted, and projected reads over many records — for example, fetching all open sales orders, listing customers in a city, or pulling G/L entries for a date range. This is far more efficient than using bc_open_page + bc_read_data for bulk reads because filtering and projection happen on the server before any data is transferred.
When to use bc_query: structured data retrieval over standard BC entities, when you need 2+ records with specific field selection, when you want server-side filter/sort/OData operators ($filter, $select, $top, $orderby, $expand), or when you need to inspect a large dataset without driving the BC UI. Entity names are BC Standard API v2.0 names (camelCase): customers, vendors, items, salesOrders, salesInvoices, purchaseOrders, purchaseInvoices, generalLedgerEntries, accounts, journals, journalLines, companies, employees, dimensions, dimensionValues, currencies, paymentTerms, shipmentMethods, paymentMethods, countriesRegions, unitsOfMeasure, taxGroups, contacts. Pass filter as OData $filter syntax (e.g., "city eq 'London'", "amount gt 1000", "postingDate ge 2024-01-01"). Pass select as comma-separated field names (e.g., "number,displayName,city") to limit response size. top defaults to 100 if omitted — pass explicitly to get more or fewer rows. Queries are company-scoped automatically; pass company to target a specific company (see bc_list_companies). The special "companies" entity is the one exception — it is the top-level environment list (not company-scoped), so the company parameter is ignored for it; query it to discover available companies.
When NOT to use bc_query: do not use for UI-driven flows (navigating pages, clicking buttons, filling forms — use bc_open_page + bc_execute_action for those). Do not use bc_query for posting, writing, or triggering BC business logic — OData reads are read-only; use bc_write_data and bc_execute_action for mutations. Do not use for custom/extension entities not in the Standard API v2.0 — those require the UI WebSocket tools. Auth: on-prem NavUserPassword uses HTTP Basic; BC Online (SaaS) uses device-code — when sign-in is needed the tool returns DEVICE_LOGIN_REQUIRED with a verification URL and code to show the user, and a retry after they sign in runs the query. bc_query does not need a /csh WebSocket session and does not open the SaaS sign-in window.
| Name | Required | Description | Default |
|---|---|---|---|
| top | No | Maximum number of rows to return. Defaults to 100 if omitted to prevent accidental full-table scans. Pass explicitly to get more rows. | |
| entity | Yes | BC Standard API v2.0 entity name (camelCase). Examples: customers, vendors, items, salesOrders, salesInvoices, purchaseOrders, generalLedgerEntries, accounts, companies, employees. See BC Standard API docs for the full list. | |
| expand | No | OData $expand for related entities. Examples: "salesLines", "customer($select=displayName)". Use sparingly — expanded entities increase response size significantly. | |
| filter | No | OData $filter expression for server-side filtering. Examples: "city eq 'London'", "amount gt 1000", "postingDate ge 2024-01-01 and postingDate le 2024-12-31", "contains(displayName, 'Contoso')". Applied by BC before returning data. | |
| select | No | Comma-separated OData $select field names to limit response size. Examples: "number,displayName,city", "id,amount,postingDate". Omit to return all fields. | |
| company | No | Override the BC company name for this query. Defaults to the server-configured company (BC_ODATA_COMPANY or first available company). Use when querying a specific company in a multi-company BC environment. | |
| orderby | No | OData $orderby expression. Examples: "displayName asc", "postingDate desc", "amount desc,number asc". |
Output Schema
| Name | Required | Description |
|---|---|---|
| rows | Yes | |
| count | No | |
| entity | Yes | |
| hasMore | Yes | |
| cappedAt | No | |
| rowCount | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations establish read-only/openWorld, but the description adds substantial behavior beyond them: the auth models (HTTP Basic for on-prem NavUserPassword vs device-code for BC Online), the DEVICE_LOGIN_REQUIRED return with verification URL/code, and the fact that it needs no /csh WebSocket session and does not open the SaaS sign-in window. This is exactly the extra context annotations cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Purpose, usage, exclusions, and auth are clearly front-loaded in labeled blocks, and every section carries information an agent needs. It is verbose, however, and the entity list and OData examples partially restate the schema, adding some redundancy to an already long description.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 7-parameter read tool with an output schema, the description covers everything needed to call it correctly: entity scope, filter/projection syntax, pagination default (top=100), company scoping, the 'companies' exception, auth flow, and explicit exclusions. Return values are correctly left to the output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the structured baseline is 3; the description still adds value with a full camelCase entity enumeration, OData $filter/$select/$top/$orderby usage examples, and the subtle 'companies' exception where the company parameter is ignored. These go beyond what the schema alone conveys, though some OData examples duplicate the schema descriptions.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a specific verb+resource+mechanism: 'Reads records from Business Central in bulk using the Standard API v2.0 (OData/REST on port 7048).' It explicitly contrasts itself with sibling tool combinations (bc_open_page + bc_read_data) and explains why the agent would choose it, so it is distinguishable without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Dedicated 'When to use' and 'When NOT to use' sections name concrete alternatives: bc_open_page + bc_execute_action for UI flows, bc_write_data/bc_execute_action for mutations, and the UI WebSocket tools for custom entities. The selection conditions (2+ records, field projection, server-side filter/sort) are explicit rather than inferred.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_read_dataARead-only
Refreshes a single section on an already-open page. Returns { section: { sectionId, kind, caption, fields?, rows?, actions?, totalRowCount? }, stateVersion }. Card-shape sections (header, factbox, requestPage) refresh their fields[]; list-shape sections refresh rows[]. The returned stateVersion can be passed as expectedStateVersion to bc_write_data / bc_execute_action to reject stale-state writes. Requires a pageContextId from a prior bc_open_page call.
Do NOT use this for bulk or analytical reads over standard entities (customers, items, ledger entries, ...) -- prefer bc_query, which reads server-side via OData with no open page and no UI paging. Use bc_read_data when you need the interactive page's exact rows, factboxes, or option metadata.
Pass section: "header" (default) to refresh the page's header. Pass section: "lines" to refresh document line items. Pass a factbox sectionId (e.g. "factbox:Customer Statistics", as listed in the bc_open_page response) to refresh the FactBox card.
Option/enum and boolean fields in card-shape sections carry "options" (allowed choices as [{text, value}]) and "selectedOption" (current choice). When writing an enum field with bc_write_data, use the "value" string from "options" -- do NOT guess values. Example: after opening Item Card, the "Type" field returns options=[{text:"Inventory",value:"0"},{text:"Service",value:"1"},{text:"Non-Inventory",value:"2"}]; to change to Service, write value "1".
Filtering applies to list-shape sections only. Pass an array of { column, value }; values use BC filter syntax (exact "10000", ranges "10000..20000", wildcards "consulting", expressions ">1000"). Multiple filters combine with AND. Filters stay on the page until clearFilters (an empty value is refused: BC cannot remove a filter that way); bc_close_page clears them, so the page opens unfiltered next time.
clearFilters: true resets agent-applied filters and restores the page to its default/native filtered state before reading. Note: page-defined SourceTableView filters (set in AL code) remain active -- this does NOT guarantee a completely empty filter set. Use before applying new filters to avoid stacking. Applies to list-shape sections only. Runs before any filters[] in the same call.
Sorting: pass sort: { column, direction } to sort the repeater before reading. Applied server-side after any filters. Resets BC viewport to top of sorted result. "asc" = A-Z / 0-9, "desc" = Z-A / 9-0. The column must be a visible repeater column on the section. Non-sortable columns (FlowFields, BLOBs) may be rejected by BC with an error. Applies to list-shape sections only.
Column selection: pass columns: ["No.", "Name"] to limit the cells in each row, or the fields[] entries on a card section.
Range slicing: { offset, limit } returns rows[offset..offset+limit] for list sections. Use with totalRowCount for pagination.
Examples:
Refresh header: { "pageContextId": "abc" }
Filter customer list: { "pageContextId": "abc", "filters": [{ "column": "City", "value": "London" }] }
Sort by Name ascending: { "pageContextId": "abc", "sort": { "column": "Name", "direction": "asc" } }
Sort by Name descending: { "pageContextId": "abc", "sort": { "column": "Name", "direction": "desc" } }
Filter and sort: { "pageContextId": "abc", "filters": [{ "column": "City", "value": "London" }], "sort": { "column": "Name", "direction": "asc" } }
Clear filters and re-read: { "pageContextId": "abc", "clearFilters": true }
Clear and re-filter: { "pageContextId": "abc", "clearFilters": true, "filters": [{ "column": "City", "value": "London" }] }
Read sales order lines: { "pageContextId": "abc", "section": "lines" }
Refresh a FactBox: { "pageContextId": "abc", "section": "factbox:Customer Statistics" }
| Name | Required | Description | Default |
|---|---|---|---|
| tab | No | Tab name to filter header fields by (e.g., "General", "Invoice Details", "Shipping and Billing"). Omit to return all header fields. | |
| sort | No | Sort the repeater by a column before reading. Applied after filters, resets BC viewport to top of sorted result. Applies to list-shape sections only. Non-sortable columns (FlowFields, BLOBs) may be rejected by BC. | |
| range | No | Slice a subset of repeater rows. Returns rows[offset..offset+limit]. Use with totalRowCount for pagination. | |
| columns | No | Column caption names to include in results. Omit to return all columns. Reduces output size. | |
| filters | No | Server-side filters to apply before reading. Multiple filters combine with AND logic. | |
| section | No | sectionId to refresh. Defaults to "header". Examples: "lines" (document line items), "factbox:Customer Statistics" (FactBox). Listed in the bc_open_page sections array. | |
| clearFilters | No | Clears agent-applied filters and restores the page to its default/native filtered state. Page-defined SourceTableView filters (set in AL code) remain active — this is NOT a guaranteed blank filter set. Use before applying new filters to avoid stacking. Applies to list-shape sections only. | |
| pageContextId | Yes | Page context ID returned by bc_open_page. |
Output Schema
| Name | Required | Description |
|---|---|---|
| section | Yes | |
| stateVersion | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true/openWorldHint=false, yet the description adds substantial behavior: stateVersion can be reused as expectedStateVersion to reject stale writes, agent-applied filters persist on the page until clearFilters, clearFilters does NOT remove AL-defined SourceTableView filters, sort resets the BC viewport, and non-sortable FlowField/BLOB columns may be rejected by BC. These are non-obvious operational traits not visible in the schema or annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and alternative routing, then organized by parameter topic with a examples block that reduces invocation error. It is long, and some content duplicates schema descriptions verbatim (sort direction, clearFilters caveat), and the examples list near-identical asc/desc entries that could be merged.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 8-parameter, nested-object tool operating on live UI state, the definition covers the prerequisite, section addressing, filter/sort/column/range semantics, and the stateVersion concurrency handshake. An output schema exists, yet the description still sketches the return shape, so nothing critical is left ambiguous.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds integration-level meaning beyond the schema: BC filter syntax with concrete examples (ranges, wildcards, expressions), AND combination semantics, that clearFilters runs before filters[] in the same call, and that range/limit pairs with totalRowCount for pagination. Some of this duplicates the schema text, keeping it below a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The opening sentence gives a specific verb and resource with scope: 'Refreshes a single section on an already-open page.' It immediately distinguishes the resource from siblings by naming the read shapes (card vs list sections) and later contrasts explicitly with bc_query.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-not guidance: 'Do NOT use this for bulk or analytical reads over standard entities... prefer bc_query.' then states the positive selection criterion: 'Use bc_read_data when you need the interactive page's exact rows, factboxes, or option metadata.' It also names the prerequisite (a pageContextId from a prior bc_open_page call).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_respond_dialogADestructive
Responds to an open Business Central dialog or confirmation prompt. Dialogs are triggered by bc_execute_action, bc_write_data, or bc_run_report when BC requires user input (e.g., "Do you want to post?", "Delete this record?", validation warnings, or a report request page). When those tools return a dialogsOpened array with requiresDialogResponse: true, or bc_run_report returns a requestPage, you MUST call this tool (response: "ok" for a report request page) to continue the workflow.
The dialogFormId comes from the dialogsOpened array in the triggering tool's response. The response parameter accepts: "ok" (confirm/accept), "cancel" (dismiss/abort), "yes" or "no" (answer a yes/no question), "abort" (only for a dialog that shows an Abort button; on any other dialog BC ignores it and the call fails with INPUT_VALIDATION_ERROR, the dialog still open), "close" (close a modal information page), or "print" / "preview" (press Print or Preview on a report request page that an action opened, e.g. Print... on a posted document: it has no OK button, so "ok" does nothing there). The tool presses the dialog's own button for the response (e.g. "ok" also matches a page's OK that BC models as LookupOk). Choose the response that matches the dialog's intent -- confirmation dialogs typically need "yes", acceptance dialogs need "ok".
After responding, check the changedSections array in the result to see which page sections were affected. For example, posting a Sales Order may change all sections. If the dialog response triggers another dialog (chained confirmations), the response will include a new dialogsOpened array -- respond to each dialog in sequence.
Do NOT call this without a preceding dialog -- there is no dialog to respond to unless a tool returned dialogsOpened (bc_execute_action, bc_write_data, bc_wizard_navigate, bc_respond_dialog, bc_close_page, bc_upload_file), or a requestPage was returned by bc_run_report. Do NOT guess the dialogFormId -- always use the exact value from the dialogsOpened array (or requestPage.formId).
This tool only presses a button; it never sets values. To fill a dialog's input fields (e.g. a request page or a dialog with entry fields), call bc_write_data with the pageContextId from the dialogsOpened entry or requestPage (bc_run_report) -- that is the dialog's own page context -- and then call this tool to press OK. If a write fails, fix it or cancel the dialog; do not press "ok" over a failed field. Pass either that dialog pageContextId or the opening page's; changedSections and openedPages always describe the opening page.
Example: { "pageContextId": "abc", "dialogFormId": "dialog-123", "response": "yes" }
If the action produces a file (Open in Excel, Print, export), its bytes appear in downloads[] (an operationId replay returns the metadata with error code NOT_REPLAYED instead of the bytes); links BC would open externally appear in externalUris[] and are never fetched by the server.
| Name | Required | Description | Default |
|---|---|---|---|
| response | Yes | "ok" confirms, "cancel" dismisses, "yes"/"no" answers a question, "abort" force-closes, "close" closes a modal info page, "print"/"preview" press the Print or Preview button of a report request page. | |
| operationId | No | Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes. | |
| dialogFormId | Yes | Dialog form ID from a dialogsOpened entry, or requestPage.formId returned by bc_run_report. | |
| pageContextId | Yes | Page context ID of the page that triggered the dialog. |
Output Schema
| Name | Required | Description |
|---|---|---|
| success | Yes | |
| messages | Yes | |
| downloads | Yes | |
| replaySafe | Yes | |
| openedPages | Yes | |
| externalUris | Yes | |
| dialogsOpened | Yes | |
| changedSections | Yes | |
| requiresDialogResponse | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The annotations (destructiveHint: true, readOnlyHint: false, idempotentHint: false) only give a coarse safety profile, and the description adds substantial specifics beyond that: abort fails with INPUT_VALIDATION_ERROR on dialogs without an Abort button, print/preview is needed on request pages that have no OK button, pressing ok over a failed field write is discouraged, and changedSections/openedPages always describe the opening page. The operationId/replaySafe explanation and downloads/externalUris behavior further clarify side effects and failure modes.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
It is front-loaded with purpose and the mandatory-call rule, and nearly every sentence carries operational information. It is long and somewhat dense in paragraph form, with a little overlap between the description and the enum's own description of abort, but no sentence is pure filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutating, workflow-critical tool with a rich schema and an output schema, the description covers prerequisites, sequencing, failure modes, and what to inspect afterward (changedSections, new dialogsOpened, downloads). An agent has everything needed to invoke it correctly in the dialog workflow.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description adds real meaning: dialogFormId must come from dialogsOpened or requestPage.formId and must never be guessed, pageContextId may be either the dialog's own context or the opening page's, and response semantics are qualified by dialog type (abort only where an Abort button exists, print/preview only on report request pages).
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence states a specific verb and resource: respond to an open Business Central dialog or confirmation prompt. It immediately names the three sibling tools that create dialogs (bc_execute_action, bc_write_data, bc_run_report), so an agent can tell this apart from its siblings without reading any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives an explicit trigger (dialogsOpened with requiresDialogResponse: true, or a requestPage from bc_run_report), an explicit prohibition ("Do NOT call this without a preceding dialog"), and a named alternative for the adjacent task (bc_write_data to fill dialog fields before pressing OK). It even covers chained dialogs and the ordering requirement.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_run_reportADestructive
Execute a Business Central report by its numeric report ID. If the report has a request page (parameter/filter dialog), the response's requestPage carries its fields plus a requestPage.pageContextId and requestPage.formId. Fill parameters with bc_write_data against that pageContextId, then run the report with bc_respond_dialog { dialogFormId: requestPage.formId, response: "ok" }. The report runs server-side on the BC service tier.
Pass format: "pdf", "excel", or "word" to capture the rendered output as base64-encoded bytes (this path auto-drives the request page, so no bc_write_data/bc_respond_dialog is needed). The tool drives the BC "Send to..." flow (SystemAction 410) internally: opens the format-selection dialog, selects the requested format by SaveValue-ing the matching text label into the SelectionControl, confirms with OK (300), then fetches the file from DynamicFileHandler.axd. When format is set, rendered file bytes are returned in downloads[] (base64 in bytes, or savedPath when BC_DOWNLOAD_DIR is set); an oversized file reports a per-entry error instead of bytes, and an operationId replay reports NOT_REPLAYED (the ledger keeps no bytes).
Format availability depends on the report's installed layouts -- not all reports offer all three formats. If the report does not offer the requested format, an error is returned listing the available option texts. "pdf" is always BC's default and requires no SaveValue; "excel" prefers the "data only" variant; "word" targets any option containing "Word".
Use this tool for reports that perform server-side actions (batch posting via Report 295, inventory adjustments, data processing) or to inspect and fill request page parameters. Common reports: 1306 (Customer Statement), 120 (Aged Accounts Receivable), 6 (Trial Balance), 295 (Batch Post Sales Orders).
Do NOT use this for viewing data -- use bc_open_page and bc_read_data for data retrieval. Do NOT confuse reports with pages -- reports are processing/printing objects, pages are UI views.
Example (open request page): { "reportId": 6 } Example (capture PDF): { "reportId": 6, "format": "pdf" } Example (capture Excel): { "reportId": 6, "format": "excel" } Example (capture Word): { "reportId": 6, "format": "word" }
| Name | Required | Description | Default |
|---|---|---|---|
| format | No | Rendered output format to capture via the BC "Send to..." flow. "pdf" captures a PDF (BC default); "excel" captures Excel (prefers "data only" layout); "word" captures a Word document. Format availability depends on the report's installed layouts -- reports without the requested layout return an error listing available formats. Omit to open the request page only without executing. | |
| reportId | Yes | Numeric BC report ID to execute (e.g., 1306 for Customer Statement, 6 for Trial Balance). | |
| operationId | No | Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| success | Yes | |
| reportId | Yes | |
| downloads | Yes | |
| replaySafe | Yes | |
| requestPage | No | |
| externalUris | Yes | |
| dialogsOpened | Yes | |
| requiresDialogResponse | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the destructiveHint/readOnlyHint annotations: it discloses the internal 'Send to...' SystemAction 410 flow, per-entry error on oversized files, NOT_REPLAYED on replayed downloads, format-availability errors listing option texts, and the requestPage/pageContextId mechanism. This is unusually rich behavioral disclosure for a mutating tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, mode, then when-to-use, and closes with concrete examples. It is long and the format-selection mechanics and the four near-identical example lines partly duplicate the schema's format description, but nearly every sentence carries operational value.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With an output schema present and annotations covering safety, the description supplies everything an agent additionally needs: request-page handling, format availability caveats, download/replay semantics, and sibling routing. Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
With 100% schema coverage the schema already documents all three params, so the baseline is 3; the description earns a bump by explaining the cross-tool workflow the requestPage fields feed into (bc_write_data against pageContextId, then bc_respond_dialog with formId) and clarifying that pdf is the default requiring no SaveValue.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Execute a Business Central report by its numeric report ID') and names concrete examples (1306, 120, 6, 295). It explicitly distinguishes reports from pages, so an agent can tell it apart from bc_open_page/bc_read_data 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use ('reports that perform server-side actions... or to inspect and fill request page parameters') and when-not ('Do NOT use this for viewing data -- use bc_open_page and bc_read_data'). It also spells out the two operating modes and routes to the correct sibling tools (bc_write_data, bc_respond_dialog) for each.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_run_testsADestructive
Run AL test codeunits in Business Central through Microsoft's AL Test Runner (page 130455 "Command Line Test Tool", the same path BcContainerHelper uses) and return one structured row per test method. Runners 130450/130451 run the full before-test hook, so the Permissions Mock, TestPermissions and test personas apply; a dev-endpoint test hub does not, and restrictive tests can pass there as SUPER.
Select tests with exactly one of codeunits (a BC filter: "50490..50494", "50490|50492") or extensionId (app GUID). Narrow to methods with a name filter ("Persona"). The suite (default "MCP") is rebuilt on every call and stays in BC with the last run's results, so it does not touch BcContainerHelper's "DEFAULT" suite unless you name it.
The tool runs one codeunit per round trip until all are done. Each row has codeunitId, codeunitName, method, status (passed/failed/skipped/notRun), durationMs, output (the failure message) and callStack for failures, the same row shape as bc-dev-mcp's bcdev_test_run. outcome is "passed", "failed", "noTests" (the filter matched nothing) or "aborted" (the run stopped early, e.g. a test opened an unhandled dialog or the session dropped; results holds the codeunits that finished).
Use this to run, check or debug AL tests. It wraps the manual route (bc_open_page 130455, bc_write_data on "Suite Name" / "Test Codeunit Range", bc_execute_action "Run Next Test" until "All tests executed."); use those tools directly to inspect the suite's lines afterwards. Do NOT use it on production data with runner 130451 (isolation disabled): test writes are committed there. Returns TEST_RUNNER_NOT_INSTALLED when the Test Runner app is missing. Not idempotent: every call runs the tests again.
Example: { "codeunits": "50490..50494" } Example: { "extensionId": "6c3f0b8e-4f49-4b49-9a49-0049c0de4949", "methods": "Persona" } Example: { "codeunits": "134000", "testRunnerCodeunitId": 130451, "suiteName": "NOISOL" }
| Name | Required | Description | Default |
|---|---|---|---|
| methods | No | Optional test procedure name filter in BC filter syntax, e.g. "TestPostInvoice" or "*Persona*|NoPersona*". Only matching methods run. | |
| testType | No | Optional TestType filter (1 UnitTest, 2 IntegrationTest, 3 Uncategorized, per the CodeUnit Metadata option). | |
| codeunits | No | Test codeunit ID filter in BC filter syntax: a range "50490..50494", a list "50490|50492", or one ID "50490". Provide exactly one of `codeunits` or `extensionId`. | |
| suiteName | No | Test suite to (re)build and run. Default "MCP". Up to 10 characters. The suite's lines are replaced on every run; pick a different name to keep another suite (e.g. BcContainerHelper's "DEFAULT") untouched. | |
| extensionId | No | App ID (GUID) of the extension whose test codeunits to run. Provide exactly one of `codeunits` or `extensionId`. | |
| operationId | No | Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes. | |
| testRunnerCodeunitId | No | Test runner codeunit. Default 130450 ("Test Runner - Isol. Codeunit", rolls back after each codeunit). 130451 runs with isolation disabled, so test writes are committed. | |
| requiredTestIsolation | No | Optional RequiredTestIsolation filter (0 None/Codeunit, 1 Disabled, 2 Function, per the CodeUnit Metadata option). Only applies together with testType. |
Output Schema
| Name | Required | Description |
|---|---|---|
| aborted | No | |
| outcome | Yes | |
| results | Yes | |
| summary | Yes | |
| suiteName | Yes | |
| replaySafe | Yes | |
| failedTests | Yes | |
| testRunnerCodeunitId | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations: discloses that the suite is rebuilt every call and persists in BC, that runners 130450/130451 run the before-test hook so Permissions Mock/TestPermissions/personas apply while a dev-endpoint hub does not (restrictive tests can falsely pass as SUPER), the round-trip-per-codeunit execution model, the outcome vocabulary including 'aborted', and the TEST_RUNNER_NOT_INSTALLED error. The isolation-disabled write-commit warning is consistent with destructiveHint=true and the non-idempotency note with idempotentHint=false.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and scope, then layered warnings and examples; most sentences earn their place. There is some redundancy — filter syntax examples are repeated from the schema, and 'not idempotent' is restated after being implied earlier — which keeps it short of a 5 given the overall length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For an 8-parameter, destructive, non-idempotent test runner, the description covers selection semantics, suite persistence, execution model, result row shape and outcome values, failure modes, and the manual-route fallback. With an output schema present it did not need to enumerate row fields but did so anyway, leaving no obvious gap for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description adds value beyond it by showing concrete BC filter literals ('50490..50494', '50490|50492', '*Persona*') and by explaining the consequence of suiteName — that its lines are replaced on each run and a different name leaves BcContainerHelper's DEFAULT suite untouched. It does not add much on testType/requiredTestIsolation beyond what the schema already states.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource: run AL test codeunits in Business Central via the Command Line Test Tool (page 130455) and return one structured row per test method. It distinguishes itself from the sibling manual route by naming bc_open_page/bc_write_data/bc_execute_action as the equivalent workflow, so an agent can tell which tool to pick 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit selection rules (exactly one of codeunits or extensionId, methods/name filter to narrow), an explicit when-to-use ('run, check or debug AL tests'), an explicit when-not ('do NOT use it on production data with runner 130451'), and an explicit alternative (drive the page manually to inspect the suite lines).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_search_pagesARead-only
Searches BC's Tell Me index for pages, reports, codeunits, and other run-targets matching the query. Each result is { name, objectType, runTarget, departmentPath?, category?, score? } where objectType is "page" / "report" / "codeunit" / etc., runTarget is the BC AL object name (e.g. "Customer List"), and category is the BC department (e.g. "Lists", "Tasks"). Use this when you do not know the page ID for an entity — search by keyword first, then resolve. Do NOT use it when you already know the numeric page ID (call bc_open_page directly), and do NOT use it to read data — it only discovers objects.
Tell Me is PROFILE-SCOPED on the BC server. If the search returns no rows in an env where the BC web client finds matches, set the BC_PROFILE environment variable on bc-mcp's startup config to a profile that indexes the relevant objects (BUSINESS MANAGER, ACCOUNTANT, SALES ORDER PROCESSOR, etc.). The default profile may have an empty Tell Me index.
Note that BC's Tell Me identifies pages by AL name, not by numeric ID. The runTarget is therefore a string like "Customer List" rather than "22". To open the result, the caller currently still needs the numeric page ID: match the runTarget AL name to a known page ID (e.g. "Customer List" = 22), or try bc_open_page with a candidate ID.
Empty-result behavior: response includes a "note" string explaining the likely cause and suggesting BC_PROFILE remediation.
Examples:
{ "query": "customer" } returns rows like { "name": "Customers", "objectType": "page", "runTarget": "Customer List", "category": "Lists", "score": 9 }.
Empty case: { "results": [], "note": "No results. Tell Me is profile-scoped..." }.
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search term matching BC page names and keywords (e.g., "customer", "sales order", "chart of accounts"). Fuzzy matching supported. |
Output Schema
| Name | Required | Description |
|---|---|---|
| note | No | |
| results | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the readOnlyHint/openWorldHint annotations by disclosing that Tell Me is profile-scoped, that the default profile may have an empty index, the BC_PROFILE remediation, and the empty-result 'note' behavior. It also warns that runTarget is an AL name, not a numeric ID, which is critical operational context.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the purpose, then usage, then profile caveats and examples. Every block is useful, though the runTarget-versus-numeric-ID caveat is repeated in two places and the response-shape sentence overlaps with the examples, adding mild redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, and the description still covers the two things an agent must know that the schema cannot convey: profile-scoped empty results and the AL-name/numeric-ID mismatch requiring a follow-up bc_open_page call. Nothing needed to call or follow up on this tool is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one parameter with 100% schema coverage (including the fuzzy-matching note and example terms), and the description reinforces it with a concrete { "query": "customer" } example and the returned row it produces. The description matches the schema rather than contradicting it.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (searches BC's Tell Me index) plus the exact set of target object types and the result shape. It explicitly distinguishes itself from siblings by declaring it only discovers objects and never reads data, unlike bc_read_data.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit when-to-use ('when you do not know the page ID... search by keyword first, then resolve') and two explicit when-not cases with named alternatives: use bc_open_page when the numeric ID is known, and don't use it to read data. That is a complete routing rule.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_switch_companyAIdempotent
Switch to a different company within the current Business Central session. All currently open pages will be invalidated and their pageContextIds will become unusable -- you must call bc_open_page to re-open any pages you need in the new company context.
Use bc_list_companies first to see the available company names and verify the target company exists. The companyName must be an exact, case-sensitive match (a wrong-case or unknown name returns COMPANY_NOT_FOUND and leaves the session on its current company). After switching, all subsequent bc_open_page, bc_read_data, bc_write_data, and bc_execute_action calls will operate against the new company's data.
Do NOT switch companies in the middle of a multi-step workflow (e.g., between creating a Sales Order and posting it). Complete all operations in the current company first, then switch.
Example: { "companyName": "CRONUS International Ltd." }
| Name | Required | Description | Default |
|---|---|---|---|
| companyName | Yes | Exact company name to switch to. Use bc_list_companies to see available company names. | |
| operationId | No | Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes. |
Output Schema
| Name | Required | Description |
|---|---|---|
| newCompany | Yes | |
| replaySafe | Yes | |
| previousCompany | Yes | |
| invalidatedPageContextIds | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations by disclosing the real side effect: all open pages are invalidated and their pageContextIds become unusable, requiring bc_open_page to re-open them. It also documents the failure mode (wrong-case or unknown name returns COMPANY_NOT_FOUND and leaves the session on the current company) and the downstream effect on subsequent bc_open_page/bc_read_data/bc_write_data/bc_execute_action calls.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and its consequence, then guidance, then a minimal example — no filler sentences. The only minor redundancy is repeating the bc_list_companies advice already present in the schema's companyName description, though here it is paired with the verification rationale.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a session-mutating switch tool, the description covers the side effects, sequencing constraint, error behavior, and follow-up actions; an output schema exists and the operationId/replay contract is fully specified in the schema, so no further return-value detail is needed.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents both parameters (including operationId's idempotency semantics in detail), which sets a baseline of 3. The description adds meaningful semantics beyond the schema for companyName: the match is exact and case-sensitive, and a mismatch returns COMPANY_NOT_FOUND without changing session state, plus a concrete example value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource with scope: 'Switch to a different company within the current Business Central session.' It is immediately distinguishable from siblings like bc_list_companies (which enumerates companies) and bc_open_page/bc_read_data (which read data).
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit when-to-use (verify via bc_list_companies first), when-not-to-use (do NOT switch mid-workflow, e.g. between creating and posting a Sales Order; complete operations in the current company first), and it names the alternative tool for discovery. Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_upload_fileADestructive
Upload a local file into a Business Central file-upload dialog, driving the page's AL Upload/UploadIntoStream code on the uploaded stream. This is a TWO-STEP flow: first invoke the page's upload action (bc_execute_action, e.g. "Import Layout" on Custom Report Layouts / page 9650) so BC opens the file-picker dialog (a FileUploadForm, returned in dialogsOpened), then call bc_upload_file with that page's pageContextId. The file is sent as a multipart POST to BC's DynamicFileUploadHandler over the same authenticated session, and the dialog is closed afterwards — exactly what the web client does — which resumes the suspended AL code on your bytes.
The upload targets the page's open FileUploadForm dialog automatically; optionally pass formId (the dialog formId from bc_execute_action's dialogsOpened) to target it explicitly. The call FAILS with a clear error instead of uploading when no FileUploadForm dialog is open — BC would otherwise silently discard the bytes (HTTP 200 with no effect), e.g. when the action failed and opened an error dialog instead, or when the invoked action is a declarative fileuploadaction (wire type fla, such as Extension Management's "Upload Extension..."), which uses a different protocol this tool does not serve.
Provide the file as either path (an absolute path to a local file) or base64 (inline content, with fileName).
The response reports the uploaded fileName, sizeBytes, the target formId, and any dialogsOpened the AL code raised in reaction to the file (e.g. a validation error for a bad file — respond to those with bc_respond_dialog, or re-read the page with bc_read_data to see the imported rows). This tool is for pushing a file TO BC; to retrieve files FROM BC (reports, "Open in Excel"), use bc_run_report / bc_execute_action, which return downloads[].
Example (local path): { "pageContextId": "session:page:9650:ab12", "path": "C:/tmp/layout.docx" } Example (base64): { "pageContextId": "session:page:9650:ab12", "base64": "UEsDBAo...", "fileName": "layout.docx" }
| Name | Required | Description | Default |
|---|---|---|---|
| path | No | Absolute path to a local file to upload. Provide exactly one of `path` or `base64`. | |
| base64 | No | Base64-encoded file content, as an alternative to `path`. Provide exactly one of `path` or `base64`. | |
| formId | No | Override the target dialog form id. By default the open FileUploadForm dialog on the page is used. Pass the form id from bc_execute_action's dialogsOpened to target a specific upload dialog; it must be a FileUploadForm. | |
| fileName | No | File name BC should see (e.g. "extension.app"). Required with `base64`; defaults to the basename of `path`. | |
| operationId | No | Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes. | |
| pageContextId | Yes | Page context ID of the host page whose upload action you just invoked (e.g. page 9650 after "Import Layout"). The open FileUploadForm dialog on that page is targeted automatically. |
Output Schema
| Name | Required | Description |
|---|---|---|
| formId | Yes | |
| success | Yes | |
| fileName | Yes | |
| sizeBytes | Yes | |
| replaySafe | Yes | |
| dialogsOpened | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well beyond the annotations: it discloses the multipart POST to BC's DynamicFileUploadHandler over the existing authenticated session, that the dialog is closed afterwards and AL code resumes, and that a missing dialog would otherwise yield a silent HTTP 200 no-op. It also explains the failure mode this tool forces instead of silent byte discard and summarizes what the response contains.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Well front-loaded: the two-step flow is stated in the first sentence before the mechanics, and the two usage examples make the call shape concrete. It is long and repeats some schema content (path/base64 exclusivity), but almost every paragraph carries new decision-relevant information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Covers the prerequisite step, the targeting default and override, the failure conditions specific to this protocol, how to react to dialogsOpened (bc_respond_dialog / bc_read_data), and the alternative for downloads. With an output schema present, it does not need to enumerate return values, and it points to the right follow-up tools for anything after the upload.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents all six parameters including the path/base64 mutual exclusion, fileName defaults, formId targeting and the operationId/replay semantics. The description restates the path-or-base64 choice and adds only modest framing (pageContextId must come from the page whose action was just invoked), which is consistent with the baseline when the schema carries the weight.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Upload a local file into a Business Central file-upload dialog") and explicitly scopes it as pushing a file TO BC, naming the siblings used for the opposite direction (bc_run_report / bc_execute_action returning downloads[]). An agent can distinguish it from every sibling 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.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit two-step protocol with the prerequisite action (bc_execute_action, e.g. "Import Layout" on page 9650) and the pageContextId handoff, plus a clear when-not: no FileUploadForm open, the action opened an error dialog, or the action is a declarative fileuploadaction. The retrieve-files alternative is named explicitly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_write_dataADestructive
Writes one or more field values on an already-open Business Central page. Pass a fields object keyed by the field names a read lists (the caption, or the AL control name of a caption-less control such as a wizard input) and string values. BC validates each field and returns the server-confirmed value, which may differ from input due to formatting, auto-completion, or lookups (e.g., entering a partial customer name resolves to the full match). Requires a pageContextId from bc_open_page.
A field (or list column) that BC publishes as hidden, disabled or not editable is not written: that result has success:false and an error saying why (a hidden list column is reported as hidden), and nothing is sent for it, because BC silently ignores such a write. Other fields in the same call are still written. A result with echoed:false and a warning means BC raised no error but sent no stored value back (BC echoes only a changed value, so rewriting the current value looks the same): the write is not confirmed, read the field back to verify. Write related fields together in one call (e.g., quantity and unit price), but avoid writing unrelated groups together because BC validation cascades may change dependent fields in unexpected order. Check the returned confirmed values to see what BC actually stored.
For Document page line items (Sales Order lines, Purchase Order lines), specify section: "lines" to write to the lines repeater. Use rowIndex (0-based row position) or bookmark (stable row identifier from bc_read_data results) to target a specific line. Prefer bookmark over rowIndex when rows may have been reordered or inserted since the last read.
Pass expectedStateVersion (from a prior bc_read_data or bc_open_page stateVersion field) to guard against acting on drifted state. If the page has been mutated by async events or a sibling operation since that read, the call is immediately rejected with code STALE_CONTEXT before touching BC. Re-read with bc_read_data to get the current stateVersion, then retry. Omit expectedStateVersion to skip the check.
Do NOT use this for triggering actions like Post, Delete, or Release -- use bc_execute_action instead. Do NOT use this for navigating to records -- use bc_navigate instead.
Examples:
Write to Card header: { "pageContextId": "abc", "fields": { "Name": "Contoso Ltd", "Address": "123 Main St" } }
Write to Sales Order line: { "pageContextId": "abc", "section": "lines", "rowIndex": 0, "fields": { "Quantity": "5", "Unit Price": "100" } }
Write with bookmark targeting: { "pageContextId": "abc", "section": "lines", "bookmark": "XXXX", "fields": { "Description": "Consulting Services" } }
Write with staleness guard: { "pageContextId": "abc", "fields": { "Name": "Contoso" }, "expectedStateVersion": 3 }
| Name | Required | Description | Default |
|---|---|---|---|
| fields | Yes | Key-value pairs of field names (the name a read lists: the caption, or for a caption-less control its AL control name, e.g. "CompanyName") or controlPaths, and string values to write (e.g., { "Name": "Contoso", "City": "London" }). | |
| section | No | Section to write to (e.g., "lines" for document line items). Omit for header fields. | |
| bookmark | No | Stable row identifier from bc_read_data results. Preferred over rowIndex when rows may be reordered. | |
| rowIndex | No | 0-based row position in the repeater to write to. Use for line items. Prefer bookmark for stability. | |
| operationId | No | Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes. | |
| pageContextId | Yes | Page context ID returned by bc_open_page. To fill a dialog or request page, use the pageContextId of its dialogsOpened entry (any tool that returns one) or requestPage (bc_run_report). | |
| expectedStateVersion | No | Opt-in staleness guard. Pass the stateVersion from a prior bc_read_data or bc_open_page response. If the page state has changed since that read (async events or sibling writes mutated it), the call is rejected immediately with code STALE_CONTEXT before touching BC. Omit to skip the check. |
Output Schema
| Name | Required | Description |
|---|---|---|
| results | Yes | |
| messages | Yes | |
| replaySafe | Yes | |
| allSucceeded | Yes | |
| dialogsOpened | Yes | |
| changedSections | Yes | |
| validationWarnings | Yes | |
| requiresDialogResponse | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations flag a destructive, non-idempotent write, and the description adds substantial context beyond that: hidden/disabled/read-only fields are silently ignored by BC so nothing is sent and success:false is returned, other fields in the same batch still apply, and echoed:false plus a warning means the write is unconfirmed and must be read back. It also documents STALE_CONTEXT pre-flight rejection, cascade validation ordering, and value normalization (partial customer name resolving to full match), none of which appears in the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Long but front-loaded: capability, then write-failure semantics, then targeting, then the staleness guard, then exclusions, then four copy-pasteable examples. Each paragraph carries a distinct decision the agent must make, and the examples compress multiple parameters into concrete calls rather than prose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 7-parameter destructive mutation with a nested fields object and an output schema, the definition covers the risky edges: partial batch success, unconfirmed writes, staleness rejection, and row targeting stability. Return-value explanation is partly redundant with the output schema but the echoed/replaySafe interpretation is essential to acting correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3; the description still adds operational meaning the schema does not, such as preferring bookmark over rowIndex for stability and the cascade/order caution when batching fields. The operationId and expectedStateVersion explanations largely restate the schema text, which keeps this from a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Opens with a precise verb+resource+scope: writing field values on an already-open Business Central page, keyed by field names from a read. It explicitly names the two sibling tools it is not (bc_execute_action for Post/Delete/Release, bc_navigate for record navigation), so an agent can discriminate it without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives positive routing (use bc_execute_action for actions, bc_navigate for navigation), scenario guidance (section: "lines" for document line items), a bookmark-vs-rowIndex preference rule with the reason (rows may be reordered), and an explicit opt-in/opt-out for expectedStateVersion. Guidance about grouping related fields together versus avoiding unrelated groups is a genuine when-to-use rule.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
14 tool updates
v2.4.7- Added
bc_api_describe - Added
bc_api_invoke - Added
bc_api_list - Added
bc_api_search - Changed
bc_close_page2 fields changed- added
Output schema / properties / dialogsOpened / items / properties / pageContextIdAdded value: +{ + "type": "string" +} - changed
Output schema / properties / dialogsOpened / items / requiredPrevious value: -[ - "formId" -]New value: +[ + "formId", + "pageContextId" +]
- Added
bc_data_find_tables - Added
bc_data_get_table_relations - Added
bc_data_get_table_schema - Added
bc_data_query - Changed
bc_execute_action2 fields changed- added
Output schema / properties / messagesAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "success", - "changedSections", - "openedPages", - "dialogsOpened", - "requiresDialogResponse", - "downloads", - "externalUris", - "replaySafe" -]New value: +[ + "success", + "changedSections", + "openedPages", + "dialogsOpened", + "requiresDialogResponse", + "messages", + "downloads", + "externalUris", + "replaySafe" +]
- Changed
bc_respond_dialog7 fields changed- changed
Input schema / properties / dialogFormId / descriptionPrevious value: -"Dialog form ID from the dialogsOpened array returned by bc_execute_action or bc_write_data, or requestPage.formId returned by bc_run_report."New value: +"Dialog form ID from a dialogsOpened entry, or requestPage.formId returned by bc_run_report." - changed
Input schema / properties / response / descriptionPrevious value: -"\"ok\" confirms, \"cancel\" dismisses, \"yes\"/\"no\" answers a question, \"abort\" force-closes, \"close\" closes a modal info page."New value: +"\"ok\" confirms, \"cancel\" dismisses, \"yes\"/\"no\" answers a question, \"abort\" force-closes, \"close\" closes a modal info page, \"print\"/\"preview\" press the Print or Preview button of a report request page." - changed
Input schema / properties / response / enumPrevious value: -[ - "ok", - "cancel", - "yes", - "no", - "abort", - "close" -]New value: +[ + "ok", + "cancel", + "yes", + "no", + "abort", + "close", + "print", + "preview" +] - added
Output schema / properties / dialogsOpened / items / properties / pageContextIdAdded value: +{ + "type": "string" +} - changed
Output schema / properties / dialogsOpened / items / requiredPrevious value: -[ - "formId" -]New value: +[ + "formId", + "pageContextId" +] - added
Output schema / properties / messagesAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "success", - "changedSections", - "dialogsOpened", - "requiresDialogResponse", - "openedPages", - "downloads", - "externalUris", - "replaySafe" -]New value: +[ + "success", + "changedSections", + "dialogsOpened", + "requiresDialogResponse", + "openedPages", + "messages", + "downloads", + "externalUris", + "replaySafe" +]
- Changed
bc_upload_file2 fields changed- added
Output schema / properties / dialogsOpened / items / properties / pageContextIdAdded value: +{ + "type": "string" +} - changed
Output schema / properties / dialogsOpened / items / requiredPrevious value: -[ - "formId" -]New value: +[ + "formId", + "pageContextId" +]
- Changed
bc_wizard_navigate2 fields changed- added
Output schema / properties / dialogsOpened / items / properties / pageContextIdAdded value: +{ + "type": "string" +} - changed
Output schema / properties / dialogsOpened / items / requiredPrevious value: -[ - "formId" -]New value: +[ + "formId", + "pageContextId" +]
- Changed
bc_write_data6 fields changed- changed
Input schema / properties / fields / descriptionPrevious value: -"Key-value pairs of field caption names and string values to write (e.g., { \"Name\": \"Contoso\", \"City\": \"London\" })."New value: +"Key-value pairs of field names (the name a read lists: the caption, or for a caption-less control its AL control name, e.g. \"CompanyName\") or controlPaths, and string values to write (e.g., { \"Name\": \"Contoso\", \"City\": \"London\" })." - changed
Input schema / properties / pageContextId / descriptionPrevious value: -"Page context ID returned by bc_open_page. To fill a dialog or request page, use the pageContextId of its dialogsOpened entry (bc_execute_action) or requestPage (bc_run_report)."New value: +"Page context ID returned by bc_open_page. To fill a dialog or request page, use the pageContextId of its dialogsOpened entry (any tool that returns one) or requestPage (bc_run_report)." - added
Output schema / properties / dialogsOpened / items / properties / pageContextIdAdded value: +{ + "type": "string" +} - changed
Output schema / properties / dialogsOpened / items / requiredPrevious value: -[ - "formId" -]New value: +[ + "formId", + "pageContextId" +] - added
Output schema / properties / messagesAdded value: +{ + "items": { + "additionalProperties": false, + "properties": { + "text": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "type": "array" +} - changed
Output schema / requiredPrevious value: -[ - "results", - "allSucceeded", - "changedSections", - "dialogsOpened", - "requiresDialogResponse", - "validationWarnings", - "replaySafe" -]New value: +[ + "results", + "allSucceeded", + "changedSections", + "dialogsOpened", + "requiresDialogResponse", + "validationWarnings", + "messages", + "replaySafe" +]
3 tool updates
v2.3.0- Changed
bc_execute_action4 fields changed- added
Output schema / properties / dialog / properties / pageContextIdAdded value: +{ + "type": "string" +} - changed
Output schema / properties / dialog / requiredPrevious value: -[ - "formId" -]New value: +[ + "formId", + "pageContextId" +] - added
Output schema / properties / dialogsOpened / items / properties / pageContextIdAdded value: +{ + "type": "string" +} - changed
Output schema / properties / dialogsOpened / items / requiredPrevious value: -[ - "formId" -]New value: +[ + "formId", + "pageContextId" +]
- Added
bc_run_tests - Changed
bc_write_data1 field changed- changed
Input schema / properties / pageContextId / descriptionPrevious value: -"Page context ID returned by bc_open_page."New value: +"Page context ID returned by bc_open_page. To fill a dialog or request page, use the pageContextId of its dialogsOpened entry (bc_execute_action) or requestPage (bc_run_report)."
16 tool updates
v2.0.0- Changed
bc_close_page2 fields changed- added
Input schema / properties / operationIdAdded value: +{ + "description": "Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "dialogsOpened": { + "items": { + "additionalProperties": {}, + "properties": { + "formId": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "formId" + ], + "type": "object" + }, + "type": "array" + }, + "replaySafe": { + "type": "boolean" + }, + "requiresDialogResponse": { + "type": "boolean" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "success", + "dialogsOpened", + "requiresDialogResponse", + "replaySafe" + ], + "type": "object" +}
- Changed
bc_execute_action2 fields changed- added
Input schema / properties / operationIdAdded value: +{ + "description": "Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "changedSections": { + "items": { + "type": "string" + }, + "type": "array" + }, + "dialog": { + "additionalProperties": {}, + "properties": { + "formId": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "formId" + ], + "type": "object" + }, + "dialogsOpened": { + "items": { + "additionalProperties": {}, + "properties": { + "formId": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "formId" + ], + "type": "object" + }, + "type": "array" + }, + "downloads": { + "items": { + "additionalProperties": {}, + "properties": { + "contentType": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "savedPath": { + "type": "string" + }, + "sizeBytes": { + "type": "number" + }, + "style": { + "type": "string" + } + }, + "required": [ + "fileName", + "contentType", + "sizeBytes", + "style" + ], + "type": "object" + }, + "type": "array" + }, + "externalUris": { + "items": { + "additionalProperties": false, + "properties": { + "style": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "uri", + "style" + ], + "type": "object" + }, + "type": "array" + }, + "openedPages": { + "items": { + "additionalProperties": false, + "properties": { + "caption": { + "type": "string" + }, + "pageContextId": { + "type": "string" + } + }, + "required": [ + "pageContextId", + "caption" + ], + "type": "object" + }, + "type": "array" + }, + "replaySafe": { + "type": "boolean" + }, + "requiresDialogResponse": { + "type": "boolean" + }, + "success": { + "type": "boolean" + }, + "updatedFields": { + "items": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "success", + "changedSections", + "openedPages", + "dialogsOpened", + "requiresDialogResponse", + "downloads", + "externalUris", + "replaySafe" + ], + "type": "object" +}
- Added
bc_get_operation - Changed
bc_list_companies1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "companies": { + "items": { + "additionalProperties": false, + "properties": { + "displayName": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "name", + "displayName" + ], + "type": "object" + }, + "type": "array" + }, + "currentCompany": { + "type": "string" + } + }, + "required": [ + "currentCompany", + "companies" + ], + "type": "object" +}
- Changed
bc_lookup1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "rows": { + "items": { + "additionalProperties": {}, + "properties": {}, + "type": "object" + }, + "type": "array" + }, + "totalFound": { + "type": "number" + } + }, + "required": [ + "rows", + "totalFound" + ], + "type": "object" +}
- Changed
bc_navigate1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "changedSections": { + "items": { + "type": "string" + }, + "type": "array" + }, + "dialogsOpened": { + "items": { + "additionalProperties": {}, + "properties": { + "formId": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "formId" + ], + "type": "object" + }, + "type": "array" + }, + "pageType": { + "type": "string" + }, + "requiresDialogResponse": { + "type": "boolean" + }, + "sections": { + "items": { + "additionalProperties": {}, + "properties": {}, + "type": "object" + }, + "type": "array" + }, + "targetPageContextId": { + "type": "string" + } + }, + "required": [ + "changedSections", + "dialogsOpened", + "requiresDialogResponse" + ], + "type": "object" +}
- Changed
bc_open_page1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "caption": { + "type": "string" + }, + "isModal": { + "type": "boolean" + }, + "pageContextId": { + "type": "string" + }, + "pageType": { + "type": "string" + }, + "sections": { + "items": { + "additionalProperties": {}, + "properties": {}, + "type": "object" + }, + "type": "array" + }, + "stateVersion": { + "type": "number" + } + }, + "required": [ + "pageContextId", + "pageType", + "caption", + "isModal", + "sections", + "stateVersion" + ], + "type": "object" +}
- Changed
bc_query1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "cappedAt": { + "type": "number" + }, + "count": { + "type": "number" + }, + "entity": { + "type": "string" + }, + "hasMore": { + "type": "boolean" + }, + "rowCount": { + "type": "number" + }, + "rows": { + "items": {}, + "type": "array" + } + }, + "required": [ + "entity", + "rows", + "rowCount", + "hasMore" + ], + "type": "object" +}
- Changed
bc_read_data1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "section": { + "additionalProperties": {}, + "properties": {}, + "type": "object" + }, + "stateVersion": { + "type": "number" + } + }, + "required": [ + "section", + "stateVersion" + ], + "type": "object" +}
- Changed
bc_respond_dialog2 fields changed- added
Input schema / properties / operationIdAdded value: +{ + "description": "Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "changedSections": { + "items": { + "type": "string" + }, + "type": "array" + }, + "dialogsOpened": { + "items": { + "additionalProperties": {}, + "properties": { + "formId": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "formId" + ], + "type": "object" + }, + "type": "array" + }, + "downloads": { + "items": { + "additionalProperties": {}, + "properties": { + "contentType": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "savedPath": { + "type": "string" + }, + "sizeBytes": { + "type": "number" + }, + "style": { + "type": "string" + } + }, + "required": [ + "fileName", + "contentType", + "sizeBytes", + "style" + ], + "type": "object" + }, + "type": "array" + }, + "externalUris": { + "items": { + "additionalProperties": false, + "properties": { + "style": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "uri", + "style" + ], + "type": "object" + }, + "type": "array" + }, + "openedPages": { + "items": { + "additionalProperties": false, + "properties": { + "caption": { + "type": "string" + }, + "pageContextId": { + "type": "string" + } + }, + "required": [ + "pageContextId", + "caption" + ], + "type": "object" + }, + "type": "array" + }, + "replaySafe": { + "type": "boolean" + }, + "requiresDialogResponse": { + "type": "boolean" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "success", + "changedSections", + "dialogsOpened", + "requiresDialogResponse", + "openedPages", + "downloads", + "externalUris", + "replaySafe" + ], + "type": "object" +}
- Changed
bc_run_report2 fields changed- added
Input schema / properties / operationIdAdded value: +{ + "description": "Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "dialogsOpened": { + "items": { + "additionalProperties": {}, + "properties": { + "formId": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "formId" + ], + "type": "object" + }, + "type": "array" + }, + "downloads": { + "items": { + "additionalProperties": {}, + "properties": { + "contentType": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "savedPath": { + "type": "string" + }, + "sizeBytes": { + "type": "number" + }, + "style": { + "type": "string" + } + }, + "required": [ + "fileName", + "contentType", + "sizeBytes", + "style" + ], + "type": "object" + }, + "type": "array" + }, + "externalUris": { + "items": { + "additionalProperties": false, + "properties": { + "style": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "uri", + "style" + ], + "type": "object" + }, + "type": "array" + }, + "replaySafe": { + "type": "boolean" + }, + "reportId": { + "type": "number" + }, + "requestPage": { + "additionalProperties": {}, + "properties": { + "formId": { + "type": "string" + }, + "pageContextId": { + "type": "string" + } + }, + "required": [ + "pageContextId", + "formId" + ], + "type": "object" + }, + "requiresDialogResponse": { + "type": "boolean" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "success", + "reportId", + "dialogsOpened", + "requiresDialogResponse", + "downloads", + "externalUris", + "replaySafe" + ], + "type": "object" +}
- Changed
bc_search_pages1 field changed- changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "note": { + "type": "string" + }, + "results": { + "items": { + "additionalProperties": {}, + "properties": {}, + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "results" + ], + "type": "object" +}
- Changed
bc_switch_company2 fields changed- added
Input schema / properties / operationIdAdded value: +{ + "description": "Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "invalidatedPageContextIds": { + "items": { + "type": "string" + }, + "type": "array" + }, + "newCompany": { + "type": "string" + }, + "previousCompany": { + "type": "string" + }, + "replaySafe": { + "type": "boolean" + } + }, + "required": [ + "previousCompany", + "newCompany", + "invalidatedPageContextIds", + "replaySafe" + ], + "type": "object" +}
- Added
bc_upload_file - Changed
bc_wizard_navigate2 fields changed- added
Input schema / properties / operationIdAdded value: +{ + "description": "Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "availableNav": { + "items": { + "type": "string" + }, + "type": "array" + }, + "caption": { + "type": "string" + }, + "changedSections": { + "items": { + "type": "string" + }, + "type": "array" + }, + "closed": { + "type": "boolean" + }, + "dialogsOpened": { + "items": { + "additionalProperties": {}, + "properties": { + "formId": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "formId" + ], + "type": "object" + }, + "type": "array" + }, + "downloads": { + "items": { + "additionalProperties": {}, + "properties": { + "contentType": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "savedPath": { + "type": "string" + }, + "sizeBytes": { + "type": "number" + }, + "style": { + "type": "string" + } + }, + "required": [ + "fileName", + "contentType", + "sizeBytes", + "style" + ], + "type": "object" + }, + "type": "array" + }, + "externalUris": { + "items": { + "additionalProperties": false, + "properties": { + "style": { + "type": "string" + }, + "uri": { + "type": "string" + } + }, + "required": [ + "uri", + "style" + ], + "type": "object" + }, + "type": "array" + }, + "fields": { + "items": { + "additionalProperties": false, + "properties": { + "editable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "editable" + ], + "type": "object" + }, + "type": "array" + }, + "replaySafe": { + "type": "boolean" + }, + "success": { + "type": "boolean" + } + }, + "required": [ + "success", + "caption", + "fields", + "availableNav", + "closed", + "changedSections", + "dialogsOpened", + "downloads", + "externalUris", + "replaySafe" + ], + "type": "object" +}
- Changed
bc_write_data2 fields changed- added
Input schema / properties / operationIdAdded value: +{ + "description": "Optional idempotency key (e.g. a UUID you generate). Retrying with the same operationId and the same arguments returns the first call's stored result instead of running the change again, so a retry after a dropped connection or timeout cannot double-post. Reusing it with different arguments is rejected (OPERATION_ID_REUSED). If the first call's outcome is unknown, the retry returns OPERATION_IN_DOUBT: check the record's state before acting again under a new operationId. Every result of this tool carries replaySafe: true means BC's response proved that re-running the same call (even under a new operationId, after an unknown outcome) cannot apply the change twice -- the write was echoed as an absolute value, or the form it acted on is closed so BC rejects a re-run; false means a re-run may double-apply or nothing proved otherwise, so read the record back before retrying. Replay is time-limited: a completed entry is kept for BC_OPERATION_TTL (default 24h), after which the same operationId runs the call again; check with bc_get_operation before a late retry. A replayed result carries download metadata (fileName, contentType, sizeBytes, savedPath) but not the file bytes.", + "maxLength": 200, + "minLength": 1, + "type": "string" +} - changed
Output schema / (root)Previous value: -nullNew value: +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "allSucceeded": { + "type": "boolean" + }, + "changedSections": { + "items": { + "type": "string" + }, + "type": "array" + }, + "dialogsOpened": { + "items": { + "additionalProperties": {}, + "properties": { + "formId": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": [ + "formId" + ], + "type": "object" + }, + "type": "array" + }, + "replaySafe": { + "type": "boolean" + }, + "requiresDialogResponse": { + "type": "boolean" + }, + "results": { + "items": { + "additionalProperties": {}, + "properties": {}, + "type": "object" + }, + "type": "array" + }, + "validationWarnings": { + "items": { + "additionalProperties": {}, + "properties": {}, + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "results", + "allSucceeded", + "changedSections", + "dialogsOpened", + "requiresDialogResponse", + "validationWarnings", + "replaySafe" + ], + "type": "object" +}
2 tool updates
v1.7.0- Changed
bc_open_page2 fields changed- removed
Input schema / properties / pageId / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "number" - } -] - added
Input schema / properties / pageId / typeAdded value: +[ + "string", + "number" +]
- Changed
bc_run_report2 fields changed- removed
Input schema / properties / reportId / anyOfRemoved value: -[ - { - "type": "string" - }, - { - "type": "number" - } -] - added
Input schema / properties / reportId / typeAdded value: +[ + "string", + "number" +]
14 tool updates
v1.5.0- First observed
bc_close_page - First observed
bc_execute_action - First observed
bc_list_companies - First observed
bc_lookup - First observed
bc_navigate - First observed
bc_open_page - First observed
bc_query - First observed
bc_read_data - First observed
bc_respond_dialog - First observed
bc_run_report - First observed
bc_search_pages - First observed
bc_switch_company - First observed
bc_wizard_navigate - First observed
bc_write_data
TDQS
Scored across 25 tools
Descriptions are unusually explicit about boundaries, with each tool stating when NOT to use it and pointing at the correct alternative (page reads vs bc_query vs bc_api_list vs bc_data_query, bc_lookup vs bc_navigate, bc_write_data vs bc_execute_action). The remaining soft spot is the read surface: standard entities are reachable via both bc_query and bc_api_list, and bc_data_query can also touch API-exposed tables, so an agent has three plausible entry points distinguished mainly by prose rather than by name.
Consistent lowercase snake_case with a uniform bc_ prefix and verb-first names (bc_read_data, bc_open_page, bc_execute_action, bc_search_pages). Sub-namespaces (bc_data_*, bc_api_*) follow internal patterns, but the 'data' word is overloaded — bc_read_data/bc_write_data are page operations while bc_data_* are AL table queries — a minor naming wrinkle despite overall predictability.
25 tools sits at the heavy end of the scale; the scope genuinely spans four distinct access modes (UI WebSocket pages, Standard API OData, BC 29 native API/data-query MCP tools, AL test runner), which explains but does not shrink the surface. An agent must choose among many entry points before doing anything, making the set borderline heavy.
Coverage is exceptional across the full workflow lifecycle: discovery (pages, tables, APIs), reads at three levels, writes, actions, dialogs, wizards, file upload/download, reports, company switching, and operation replay/read-back for doubted calls. The notable gap is that bc_search_pages returns AL names but the caller still needs a numeric page ID to open a page, an admitted dead end that forces guesswork.
Maintenance
Related MCP Connectors
BNex lets AI securely access your workspace data like contacts and campaigns via MCP.
weclapp ERP in your AI assistant: reads instantly, writes only after a preview you approve.
AI agents for CRM, mail, calendar, ERP and accounting - you approve. EU-hosted, OAuth.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceEnables AI clients to interact with Microsoft Dynamics 365 Business Central through MCP, providing access to customers, items, sales orders, and allowing creation of new records via natural language.2-
- FlicenseNot gradedqualityBmaintenanceConnects AI clients like VS Code Copilot and Claude Desktop to Microsoft Dynamics 365 Business Central via the Cloud Events API.-
- AlicenseAqualityDmaintenanceEnables AI assistants to directly access and interact with Microsoft Dynamics 365 Business Central using native WebSocket protocol.141,456 npmMIT
- AlicenseAqualityDmaintenanceGives AI assistants direct access to Microsoft Dynamics 365 Business Central via the native WebSocket protocol, replacing OData, APIs, and browser automation. Enables page navigation, data reading/writing, actions, searches, and report execution.141,456 npmMIT