business-central-mcp
This server lets AI assistants read and operate Microsoft Dynamics 365 Business Central directly through MCP tools, using BC's native WebSocket protocol without OData or browser automation.
Open BC pages by ID (lists, cards, documents, role centers) and get structured sections (header, lines, factboxes, cues)
Refresh, filter, sort, paginate, and project page sections
Write field values to cards, headers, factboxes, and document lines
Execute page actions (Post, Delete, Release, New, etc.) and drill down on Role Center cue tiles
Respond to confirmation dialogs, validation prompts, and report request pages
Navigate between records: select rows, drill down from lists to cards, use bookmarks
Search BC's Tell Me index to discover pages and reports
Close pages to free server resources
Switch companies and list available companies
Run reports and capture output as PDF/Excel/Word, or fill request page parameters
Drive wizard / NavigatePage flows (next, back, finish, cancel)
Enumerate lookup candidates for related-table fields
Run bulk read-only OData queries over standard BC entities (customers, sales orders, G/L entries, etc.) with server-side filtering, sorting, and projection
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.
Overview
Property | Value |
Language | TypeScript / Node 20+ |
npm package | |
BC versions | BC27, BC28 (wire-compatible) |
Auth | On-prem NavUserPassword. BC Online: ESTS cookie session for |
Tools | 16 |
Tests | 1117 unit/protocol + 164 integration |
License | MIT |
Related MCP server: origo-bc-mcp-server
Install
BC Online (sandbox / production): do not put a password in env. Copy the portal URL from your browser and follow SaaS sandbox setup. The snippets below are for on-prem NavUserPassword.
VSCode
Click the badge. VSCode opens and prompts for your BC URL, username, and password (on-prem), then writes the configured entry to your user mcp.json. For BC Online, skip the badge and use the SaaS sandbox env (URL + optional email only).
Workspace: create .vscode/mcp.json:
{
"servers": {
"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"
}
}
}
}BC Online — same file, no password:
{
"servers": {
"business-central": {
"command": "npx",
"args": ["-y", "business-central-mcp"],
"env": {
"BC_BASE_URL": "https://businesscentral.dynamics.com/<aad-tenant-id>/DEV",
"BC_USERNAME": "you@tenant.com"
}
}
}
}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-mcpScope it to the current project with --scope project. See claude mcp --help for scoping options.
BC Online (no password):
claude mcp add business-central \
-e BC_BASE_URL=https://businesscentral.dynamics.com/<aad-tenant-id>/DEV \
-e BC_USERNAME=you@tenant.com \
--scope project \
-- npx -y business-central-mcpClaude Desktop
Download the latest
.dxtfrom Releases.Double-click. Claude Desktop opens Settings → Extensions and prompts for BC URL, username, and password (on-prem). For BC Online, use the manual snippet instead — do not store a SaaS password.
Restart Claude Desktop.
Edit claude_desktop_config.json:
macOS:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:
%APPDATA%\Claude\claude_desktop_config.jsonLinux:
~/.config/Claude/claude_desktop_config.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"
}
}
}
}Restart Claude Desktop.
Configuration
Variable | Required | Default | Description |
| Yes | — | BC server base URL, e.g. |
| NavUserPassword | — | On-prem username. On SaaS this is only an email prefill for the local sign-in window. |
| NavUserPassword | — | On-prem password. Ignored on SaaS (never put a SaaS password in env). |
| No |
|
|
| OAuth (if not in URL) | — | Entra tenant GUID. Taken from a SaaS |
| No | from URL | SaaS environment name ( |
|
| — | Multi-tenant public Entra app for device-code sign-in (see bc_query on SaaS). UI tools do not need it |
| No |
| Override the Entra scope for |
| No | server default | Profile id, e.g. |
| No |
| On-prem multi-tenant id. SaaS uses the Entra tenant from the URL. |
| No |
| Version reported to BC during session open. |
| No | detected |
|
| No |
| HTTP transport port (stdio transport ignores this). |
| No |
|
|
| No |
| Directory for log files. |
| No |
| Per-repo directory for |
| No |
| How long (ms) a completed |
| No |
| How long (ms) |
| No |
| RPC timeout in ms for OpenSession and modal-recovery calls. |
| No |
| While a call runs, ask BC every N ms whether it is still processing. A long post or report stays alive as long as BC says yes; a hung call is killed once BC says no. |
| No |
| Hard cap in ms for one call, even while BC is still processing. |
| No |
| Reconnect attempts after session death. |
| No |
| Base delay (ms) for exponential reconnect backoff. |
Central connection config
Running several Claude Code sessions against several BC instances no longer
requires a full BC_* env block in every repo's .mcp.json. Register the
server once at user scope and define the connections in one file.
Register the server globally:
claude mcp add business-central -s user -- node U:/git/bc-mcp/node_modules/tsx/dist/cli.mjs U:/git/bc-mcp/src/stdio-server.tsCreate
~/.bc-mcp/config.jsonc(seeconfig.jsonc.example): a set of namedconnections, an optionaldefault, and an optionalmap[]from repo path to connection.Each session picks its connection, highest priority first:
an explicit
BC_*env var (e.g.BC_BASE_URL) always wins for that field;BC_CONNECTION=<name>selects a named connection;a
map[]entry whosepathmatches the session's working directory;the
defaultconnection.
Keep secrets out of the file with ${ENV} references (expanded from the
process environment); on macOS/Linux the file should be mode 0600. SaaS
connections carry no password — sign in via the local window or
npx business-central-mcp login. With no config file present, the server runs
exactly as before from plain BC_* environment variables.
A <cwd>/.env (or the file at BC_ENV_FILE) is also auto-loaded at startup,
before connection resolution — real environment variables set outside the
file still win (override:false).
On-prem containers: applicationId (FIN vs NAV)
OpenSession must carry the applicationId the server expects. SaaS and cronus images expect
FIN; on-prem BcContainerHelper containers (the onprem artifact type) generally expect NAV.
With BC_APPLICATION_ID unset, bc-mcp reads the value the web client would use from
{BC_BASE_URL}/boot/browser/desktop?tenant=… (served without sign-in) before the first session,
and falls back to FIN if that fails (logged as a warning).
If the session still dies at OpenSession with NavCancelCredentialPromptException although
sign-in and the WebSocket upgrade both succeeded, set it explicitly:
BC_APPLICATION_ID=NAVThe failure is misleading because authentication and the /csh upgrade complete first (you get a
101); BC only rejects the applicationId inside the OpenSession RPC body. Verified against BC
27.1 onprem (see issue #10).
SaaS sandbox setup
You only need the URL from the browser address bar — the same one you use to open Business Central Online:
https://businesscentral.dynamics.com/<aad-tenant-id>/<environment><environment> is usually DEV, sandbox, or production. Do not set BC_PASSWORD. Company policy and this server both treat a SaaS password in env as wrong.
Copy that portal URL into
BC_BASE_URL(no extra path, no query string).Optionally set
BC_USERNAMEto your work email — that only prefills the sign-in form.Point the MCP at this project (stdio, Grok
.grok/config.toml, Claude--scope project, or a workspacemcp.json). LeaveSTATE_DIRunset so cookies land in{project}/.state/.Start the agent on a machine with a display (Linux needs
DISPLAYorWAYLAND_DISPLAY). Headless CI cannot complete MFA.Ask the agent to open a page (
bc_open_page, e.g. Customer List = 22). A local window (127.0.0.1) opens. Sign in with Microsoft and complete Authenticator there. Do not paste the password into chat or tool arguments.Retry the tool. Cookies are saved as
{project}/.state/saas-web-cookies.json(mode 0600). Later sessions in the same repo reuse them; another repo needs its own sign-in.
Human shortcut (same working directory as the MCP):
npx business-central-mcp login
# from a source checkout:
npx tsx src/stdio-server.ts loginGrok (project-scoped, no password):
# .grok/config.toml — not committed if it holds a tenant URL you do not want shared
[mcp_servers.business-central]
command = "npx"
args = ["-y", "business-central-mcp"]
[mcp_servers.business-central.env]
BC_BASE_URL = "https://businesscentral.dynamics.com/<aad-tenant-id>/DEV"
BC_USERNAME = "you@tenant.com"From a source checkout, point command / args at node + node_modules/tsx/dist/cli.mjs + src/stdio-server.ts instead of npx.
Claude Desktop / VS Code (no BC_PASSWORD):
{
"mcpServers": {
"business-central": {
"command": "npx",
"args": ["-y", "business-central-mcp"],
"env": {
"BC_BASE_URL": "https://businesscentral.dynamics.com/<aad-tenant-id>/DEV",
"BC_USERNAME": "you@tenant.com"
}
}
}
}The WebSocket is not on the portal host. After sign-in the server discovers the cluster and uses Origin: https://businesscentral.dynamics.com. You never put a cluster URL in config.
bc_query (OData) on SaaS
bc_query does not use the /csh cookie session. When sign-in is needed the first call returns DEVICE_LOGIN_REQUIRED with a https://microsoft.com/devicelogin URL and user code — complete it in a browser and retry; the retry picks up the pending sign-in and runs the query. The refresh token is stored in STATE_DIR/oauth-tokens.json (mode 0600).
bc_query talks to https://api.businesscentral.dynamics.com/v2.0/{tenant}/{environment}/api/v2.0 with the Bearer token. If BC_CLIENT_ID is not configured it returns OAUTH_NOT_CONFIGURED (device-code that has not been completed returns DEVICE_LOGIN_REQUIRED, above); it never sends Basic.
Which client id signs in (BC_CLIENT_ID)
BC_CLIENT_ID is required for bc_query on BC Online: a multi-tenant public Entra app with delegated Dynamics 365 Business Central / user_impersonation. The publisher registers it ONE time in their own tenant; customer tenants register nothing — each user consents at first sign-in (user_impersonation is user-consentable), and tenants that disable user consent need a one-time admin-consent click.
Do not borrow a Microsoft first-party client (Azure PowerShell 1950a258-… as New-BcAuthContext does, Azure CLI, …): on tenants with Entra first-party hardening the sign-in fails in the browser with AADSTS65002 ("consent between first party application and first party resource must be configured via preauthorization"), which no tenant admin can consent around. Verified live 2026-08-16 — the same sign-in succeeds on one tenant and fails with 65002 on another. A third-party multi-tenant app is structurally immune (65002 only gates Microsoft-owned client/resource pairs).
Create the app (once, in the publisher tenant):
az ad app create --display-name "business-central-mcp" \
--is-fallback-public-client true \
--sign-in-audience AzureADMultipleOrgs \
--required-resource-accesses '[{"resourceAppId":"996def3d-b36c-4153-8607-a6fd3c01b89f","resourceAccess":[{"id":"bce0976a-cb0b-473b-8800-84eda9f8e447","type":"Scope"}]}]' \
--query appId -o tsv(996def3d… is the Dynamics 365 Business Central resource; bce0976a… is its delegated user_impersonation scope.) Put the printed appId in BC_CLIENT_ID.
Known wart: when the browser sign-in fails (65002, blocked consent), Entra keeps the device code authorization_pending, so retries re-serve the same doomed code until it expires (~15 min). Fix the client id / consent, wait out or ignore the old code, and retry for a fresh one.
What can it do?
Tool | What it does |
| Open any page by ID -- lists, cards, documents, role centers. Returns the page as |
| Refresh a single section: filter, paginate, slice, project tab/columns. Returns the same |
| Write field values; BC validates and echoes confirmed values. Section-aware (lines, factboxes, header). |
| Run header / row / wizard actions, OR drill down on Role Center cue tiles via |
| Handle confirmation prompts and request pages |
| Select rows and drill down into records |
| Tell Me search. Returns |
| Close a page and free server resources |
| Switch to a different company mid-session |
| Discover available companies |
| Execute reports and fill request page parameters |
| Drive NavigatePage / wizard flows (back / next / finish / cancel) |
| List the valid values for a lookup field (e.g. Salesperson Code) |
| Bulk read-only OData queries against the BC Standard API v2.0 |
| Upload a local file into a BC file-upload dialog (e.g. Import Layout) |
| Look up an earlier |
Every tool carries MCP annotations (readOnlyHint, destructiveHint, idempotentHint), so clients can auto-approve the read-only ones and ask before writes, actions, dialogs, reports and uploads.
The eight tools that change state accept an optional operationId (an idempotency key you generate). A retry with the same id and arguments returns the first call's stored result instead of running the change again, so retrying after a timeout or dropped connection cannot double-post. The same id with different arguments returns OPERATION_ID_REUSED. If the first call's outcome is unknown (timeout, session loss), the retry returns OPERATION_IN_DOUBT: check the record before acting under a new id. That response carries a target snapshot taken before the first call ran (company, page id and caption, section, the targeted rows' cell values, dialog), so you can find the record again even though the original pageContextId died with the session. Calls are recorded in STATE_DIR/operations/, so this survives a server restart.
Replay is time-limited. The server prunes the ledger at start and hourly: a completed entry is deleted after BC_OPERATION_TTL (default 24 hours), after which a retry with the same id runs the call again, so check with bc_get_operation before a late retry. sent/unknown entries are kept for BC_OPERATION_INDOUBT_TTL (default 30 days). Download bytes are not stored: a replayed result lists each download's fileName, contentType, sizeBytes and savedPath (set when BC_DOWNLOAD_DIR is configured), but not its bytes; each such entry carries error.code: "NOT_REPLAYED" so it is never mistaken for a successful capture.
Every result from those eight tools (and its ledger entry) 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. false means a re-run may double-apply, or nothing in the response proved otherwise: read the record back first. The server only says true when it can prove it:
Tool |
|
| BC echoed every written value (it echoes only a value that changed, and a write to a read-only field is silently dropped), and no dialog is pending |
| the action was Refresh, or BC closed the form the action ran on (a re-run on a closed form is rejected with |
| BC closed the dialog form |
| BC closed the wizard form (finish/cancel); next/back never |
| no save/discard dialog blocked the close (closing a closed form is a no-op) |
| the session ends on the requested company (a switch to the current company is a no-op that keeps open pages) |
| never: a re-run reruns the report or the import |
An OPERATION_IN_DOUBT response is always replaySafe: false, and so is the first error response to an ambiguous failure (TIMEOUT_ERROR, SESSION_LOST, CONNECTION_ERROR, PROTOCOL_ERROR, ABORTED_ERROR, or an unclassified error), with or without an operationId: it is in structuredContent next to the error code. Definite failures (validation, business errors) carry no verdict, because nothing was applied.
bc_get_operation { operationId } (read-only) returns the ledger entry: status, tool, timestamps, replaySafe, the stored result and the target snapshot. When the status is not completed or replaySafe is false, it also re-reads the target in the current session: it re-opens the page by page id on the recorded record bookmark, reads the record's primary-key fields (by table field number, from the page's own key metadata; a key field with no control on the page, such as a Sales Order's "Document Type", is decoded from the record bookmark and marked source: "bookmark", with an option as its ordinal; a key that no longer matches returns readBackError: KEY_MISMATCH without an evidenceHash) plus any written fields (or the targeted list row, null if it is gone; a document header also gets its loaded lines, since a posting changes line state such as "Quantity Shipped" but not the header key fields), closes the page, and returns that as readBack with evidenceHash, the SHA-256 of its sorted-key JSON. It does not switch company for you (readBackError: COMPANY_MISMATCH), does not open a page while a dialog is open (MODAL_OPEN: opening would cancel it; answer it first), and never re-opens a page without a recorded record bookmark (NO_TARGET). A card in edit mode keeps a written value until the page is closed or leaves the record, so a read-back taken while the original page is still open can show the old value.
The HTTP transport's REST API (POST /api/v1/...) uses the same ledger. Its three mutating routes take the key in an Idempotency-Key header and record it under their MCP tool name (pages/write = bc_write_data, pages/action = bc_execute_action, pages/close = bc_close_page), so a key means the same operation on both transports and bc_get_operation can look up a REST call. Responses:
Case | HTTP status |
success |
|
replay of a completed call |
|
|
|
|
|
definite failure (validation, business error) |
|
sign-in required |
|
|
|
other ambiguous failure ( |
|
unclassified error |
|
Ambiguous failures (503/504/500) of a mutating route carry replaySafe: false, with or without a key. Read-only routes ignore the header.
How it works
This server speaks BC's internal WebSocket protocol directly -- the same protocol the browser-based web client uses. It was reverse-engineered from decompiled BC server assemblies. No OData endpoints, no SOAP services, no Selenium.
One WebSocket connection per session. All operations serialized through a promise queue. BC27 and BC28 are wire-compatible.
LLM (Claude / Copilot / etc.)
|
v MCP (stdio or HTTP)
business-central-mcp
|
v WebSocket + JSON-RPC
BC Web Service Tier (BC27 / BC28)
|
v internal calls
BC Serverbc_open_page returns the page as a flat list of sections:
{
"pageContextId": "session:page:21:abc",
"pageType": "Card",
"caption": "Customer Card",
"isModal": false,
"sections": [
{ "sectionId": "header", "kind": "header", "fields": [...], "actions": [...] },
{ "sectionId": "factbox:Customer Statistics", "kind": "factbox", "fields": [...] }
]
}Each section carries its own content shape:
Card-style (
headeron Card pages,factbox,requestPage):fields[]and (forheader)actions[]List-style (
lineson Documents,headeron List pages, repeater subpages):rows[]andtotalRowCountCue tiles (Role Center hosted CardParts):
cues[]with each tile'sname,value,groupCaption,synopsis,hasAction. Drill down withbc_execute_action { section, cue }.
bc_read_data returns a single Section for the requested sectionId (defaults to "header"). The section ID for a FactBox or subpage comes from the bc_open_page response.
Automatic reconnect with exponential backoff after session death
Handles BC's ~15s NTLM auth slot hold after crashes
Auto-dismisses license popups on fresh databases
Invoke timeout kills hung sessions and triggers recovery
Auto-recovery from
LogicalModalityViolationExceptionmid-session: closes stale confirms, messages and report request pages (CloseForm, which answers a confirm with No and cancels a request page) and retries transparently; any other stale modal (aPage.RunModalpage, a lookup, a menu), or a dialog BC does not close, resets the session instead, because closing it could accept it
Key files
File | Purpose |
| npm |
| HTTP MCP transport entry |
| MCP tool registry, schemas, request handler |
| One handler per tool ( |
| Page, data, action, navigation, search business logic |
| WebSocket transport, wire types, captures |
| Session lifecycle, modal stack, reconnect |
| Claude Desktop Extension manifest |
| Builds |
| Builds + attaches |
| Deferred work (Cursor, init wizard) |
Development
git clone https://github.com/SShadowS/business-central-mcp
cd business-central-mcp
npm install
npm run start:stdio-direct # Run from source
npm test # unit + protocol tests
npm run test:integration # Cronus28 integration tests (requires running BC server)
npm run test:saas # BC Online smoke (needs a signed-in STATE_DIR cookie file)Roadmap
Cursor support, an interactive init wizard, and a few protocol gaps.
See ROADMAP.md for the full list and priorities.
Author: Torben Leth (sshadows@sshadows.dk) License: MIT (see LICENSE)
Available Tools
16 toolsbc_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 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?
Well beyond the annotations, the description discloses post-condition state (pageContextId becomes invalid, downstream bc_read_data/bc_write_data/bc_execute_action/bc_navigate will fail), error tolerance (safe after failed operations), and a drill-down cleanup obligation. Annotations only give generic safety hints (readOnly=false, idempotent=true, destructive=false); the description supplies the lifecycle consequences the agent actually needs.
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 the key post-condition, then the when-not rules. Three paragraphs are justified by the number of distinct behaviors, though the resource-leak rationale is stated twice ('frees its server-side resources' / 'prevent resource leaks'), which is slightly redundant.
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 lifecycle-closing tool, the description covers triggering conditions, invalidation consequences, error safety, and nested drill-down cleanup. An output schema exists, so return values need not be described, and 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 already documents pageContextId's origin ('returned by bc_open_page') and its invalidation. The description's statement that a pageContextId is required and comes from bc_open_page largely restates structured data, so the baseline 3 applies; the lifecycle emphasis adds marginal value only.
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 (closes) and resource (Business Central page), and names the concrete effect: freeing server-side resources including the WebSocket form session. An agent can distinguish it from siblings like bc_open_page or bc_read_data 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?
Explicit when-to-use (always when finished with a page), when-not (not mid multi-step workflow, not as a reset -- use bc_read_data instead), and an edge case (safe even after prior errors; close drill-down plus original page). Alternatives are named by tool name, 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_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.
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 | |
| 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?
Annotations cover the safety profile (destructive, non-idempotent), and the description adds substantial context beyond them: the InvokeAction RPC flow, dialog handling via bc_respond_dialog, STALE_CONTEXT rejection semantics, operationId idempotency/replay behavior, downloads[] and NOT_REPLAYED handling, and externalUris[] externalization.
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 and the action-vs-cue split, followed by worked examples. It is long but each block (row scoping, batch, staleness, downloads) earns its place for a 9-param tool; minor redundancy in repeating mutual-exclusivity rules.
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?
Complete for a complex mutation tool: covers both invocation modes, side effects (dialogs, downloads, externalUris), error codes (STALE_CONTEXT, OPERATION_IN_DOUBT, NOT_REPLAYED, OPERATION_ID_REUSED), and sibling routing. An output schema exists, but the description still adds needed follow-up and error-handling guidance.
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. The description goes further by explaining the mutual-exclusivity and semantic distinctions the schema only partially encodes -- bookmarks[] anchor semantics, selection-consuming vs current-row actions, expectedStateVersion drift guard, dialog follow-up -- adding real meaning beyond field 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?
States a specific verb and two distinct modes (named action vs cue drill-down), and distinguishes itself from siblings bc_write_data and bc_navigate by name with explicit 'Do NOT use this for' routing.
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 for both modes, mutual exclusivity rules (action vs cue, bookmarks[] vs bookmark/rowIndex), and named alternatives for the write and navigate cases. 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_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.
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 only declare readOnlyHint/openWorldHint, so the description carries the real behavioral load: it documents the return envelope, that stateVersion can be fed to bc_write_data/bc_execute_action as expectedStateVersion to reject stale writes, that clearFilters does NOT clear AL SourceTableView filters, that sort resets the BC viewport, and that FlowField/BLOB columns may be rejected. Missing only things like rate limits or what happens if the page was closed server-side.
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 and organized under clear topic labels (filtering, sorting, column selection, range slicing), but it is long and repeats schema-documented details such as filter syntax and sort direction semantics. Several near-duplicate examples (sort asc, sort desc, filter+sort) could be collapsed without losing 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 full lifecycle an agent needs: prerequisite context id, which sections exist, card vs list refresh behavior, stateVersion round-trip into write/action tools, filtering/sorting/pagination semantics, and enumerated failure modes. Even though an output schema exists, the description's inline return-shape summary makes the tool usable end-to-end.
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 genuine cross-parameter semantics: execution order (clearFilters runs before filters[]), the AND-combination of multiple filters, and the enum-option workflow (read options[].value, then write that value rather than guessing). Worked examples reinforce the syntax the schema already lists.
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 ("Refreshes a single section on an already-open page") and immediately contrasts itself with bc_query for bulk/analytical reads. An agent can distinguish it from the other 15 siblings 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 an explicit when-not ("Do NOT use this for bulk or analytical reads over standard entities ... prefer bc_query") and an explicit when ("when you need the interactive page's exact rows, factboxes, or option metadata"), plus the precondition that a pageContextId from a prior bc_open_page is required.
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), or "close" (close a modal information page). 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 dialogsOpened was returned by bc_execute_action / bc_write_data, 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).
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. | |
| 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 the dialogsOpened array returned by bc_execute_action or bc_write_data, 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 | |
| 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?
Annotations only give the coarse profile (destructive=true, idempotent=false); the description adds substantial behavior beyond that: the abort value's failure mode (INPUT_VALIDATION_ERROR and the dialog staying open), which response string matches which dialog intent, chained-dialog sequencing, changedSections inspection, and the operationId replay/OPERATION_IN_DOUBT semantics. This is far more than the annotations 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?
Front-loaded with purpose and the mandatory trigger before any edge-case detail, and the example invocation is placed usefully. The trailing paragraph about downloads[]/externalUris[] is only loosely connected to responding to a dialog and could be trimmed, keeping it just short of a 5.
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-explained, yet the description still surfaces the two result fields an agent must act on (changedSections, a new dialogsOpened). For a destructive, multi-step, stateful tool this is complete enough to call 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 operational meaning the schema does not: the exact provenance of dialogFormId (dialogsOpened vs requestPage.formId), the anti-guessing rule, and the mapping of "ok" specifically to a report request page. It adds modest but real value over the enum descriptions already present.
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 (respond to an open Business Central dialog/confirmation prompt) and immediately names the sibling tools that produce the dialogs it consumes (bc_execute_action, bc_write_data, bc_run_report). An agent can distinguish it from bc_wizard_navigate or bc_close_page 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 trigger condition (dialogsOpened with requiresDialogResponse: true, or requestPage from bc_run_report) and an explicit exclusion ("Do NOT call this without a preceding dialog"). It also covers the sequencing case of chained dialogs, so when-to-use is fully specified.
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_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?
Annotations already mark this as destructive, non-read-only, and not idempotent, but the description adds substantial behavioral context: multipart POST to DynamicFileUploadHandler, authenticated session use, dialog closure, resumption of suspended AL code, failure conditions instead of silent discard, unsupported fla actions, and optional idempotency/replay semantics. These details go well beyond the annotations without 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?
The description is long, but it is front-loaded with the core action and flow before details. Most sentences earn their place given the complexity, though the operationId/idempotency explanation is lengthy and overlaps with the schema's own parameter 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?
Given the tool's complexity, the rich input schema, the annotations, and the presence of an output schema, the description is complete enough for correct invocation. It covers prerequisites, failure modes, response shape, and next actions such as responding to raised dialogs or re-reading the page.
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 schema already documents the parameters thoroughly; baseline would be 3. The description adds practical meaning through examples, the relationship between formId and bc_execute_action's dialogsOpened, and the path/base64/fileName usage patterns, though much of this is also present in 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 states a specific verb and resource: upload a local file into a Business Central file-upload dialog, driving the page's AL Upload/UploadIntoStream code. It clearly distinguishes this tool from its pre-step sibling bc_execute_action and from retrieval siblings bc_run_report/bc_execute_action by explaining that this tool pushes files TO BC.
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 explicit when-to-use guidance, including the required two-step flow: first invoke the page's upload action with bc_execute_action, then call bc_upload_file with that page's pageContextId. It also states when the call fails and why, and routes the agent to alternatives for retrieving files FROM BC.
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 with caption-name keys 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.
Fields must be editable -- writing to a read-only field returns an error. 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 caption names 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. | |
| 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 | |
| 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 only say this is a destructive, non-idempotent, non-read-only write; the description goes far beyond by disclosing that BC validates and returns server-confirmed values which may differ from input (formatting/auto-completion/lookups), that read-only fields error, that validation cascades can reorder dependent field changes, and that stale state returns STALE_CONTEXT before touching BC. This is exactly the behavioral 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?
Well front-loaded with purpose, then targeting rules, then guards, then exclusions, and closes with four concrete input examples that map to the schema. It is long and repeats expectedStateVersion guidance in prose and again in an example, but nearly every sentence carries actionable 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?
For a 7-parameter mutation tool with an output schema, the description covers prerequisites, targeting, error codes (STALE_CONTEXT), grouping hazards, and sibling alternatives; return values are appropriately left to the output schema. Nothing an agent needs to invoke 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% so the baseline is 3, but the description adds real routing meaning: when to use bookmark versus rowIndex, what section:'lines' targets, and the opt-in semantics of expectedStateVersion (rejected with STALE_CONTEXT, omit to skip). The fields-key/value shape and operationId contract are already fully documented in the schema, so the description largely complements rather than extends 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?
Opens with a specific verb+resource ('Writes one or more field values on an already-open Business Central page') and explicitly separates itself from siblings by naming bc_execute_action for actions and bc_navigate for navigation. An agent can tell exactly what class of operation this is versus the other 15 sibling tools.
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 and when-not-to-use rules, states the prerequisite (pageContextId from bc_open_page), and prescribes calling patterns: write related fields together, avoid unrelated groups, use section:'lines' for document lines, prefer bookmark over rowIndex. It even names the two sibling alternatives for the excluded cases.
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.
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 16 tools
Most tools have distinct roles (bc_query for bulk reads, bc_read_data for page refresh, bc_write_data for field edits, bc_execute_action for actions, etc.). Potential confusion exists between bc_read_data and bc_query for reading data, and between bc_execute_action and bc_navigate for drill-down, but the extensive descriptions clarify when to use each.
All 16 tools follow the exact same bc_verb_noun pattern (bc_read_data, bc_write_data, bc_execute_action, bc_navigate, etc.), providing a highly predictable and consistent naming convention throughout.
With 16 tools, the count is well-suited for the complex domain of Business Central automation, covering page interaction, data querying, session management, and file operations without excessive redundancy.
The toolset provides comprehensive coverage for interacting with Business Central via MCP: page lifecycle (open/close), read/write, action execution, navigation, dialogs, reports, file upload, bulk queries, company switching, and operation tracking. It addresses both UI-driven and server-side workflows, leaving no obvious gaps for the stated purpose.
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.
1
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.-
- AlicenseAqualityCmaintenanceEnables AI assistants to directly access and interact with Microsoft Dynamics 365 Business Central using native WebSocket protocol.14285 npmMIT
- AlicenseAqualityCmaintenanceGives 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.14285 npmMIT