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 "Install 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 | 12 |
Tests | 901 unit/protocol + 111 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 |
|
|
| No |
| HTTP transport port (stdio transport ignores this). |
| No |
|
|
| No |
| Directory for log files. |
| No |
| Per-repo directory for |
| No |
| Per-invoke timeout in ms. Kills hung sessions. |
| 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: set BC_APPLICATION_ID=NAV
If sign-in and the WebSocket upgrade both succeed but the session dies at OpenSession with
NavCancelCredentialPromptException, the server is rejecting the default applicationId (FIN).
On-prem BcContainerHelper containers (the onprem artifact type) generally expect NAV:
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. SaaS and cronus images
keep the FIN default. 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, drill down into records, field lookups |
| 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) |
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: reconciles the modal stack and retries transparently; falls back to session reset when BC keeps a confirm dialog sticky
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
14 toolsbc_close_pageA
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 |
|---|---|---|---|
| pageContextId | Yes | Page context ID returned by bc_open_page. Becomes invalid after closing. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, so the description carries the full burden. It thoroughly explains that the pageContextId becomes invalid, subsequent calls will fail, it is safe to call after errors, and when to close drill-down pages.
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 concise, front-loaded, and every sentence adds value. It could be slightly more terse but is well-structured.
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 no annotations, no output schema, and one parameter, the description is very complete, covering purpose, usage, behavior, and pitfalls.
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% with one parameter already described well. The description adds minimal extra meaning (e.g., confirms it's from bc_open_page), so baseline 3 is appropriate.
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 clearly states the tool closes an open Business Central page and frees server resources, distinguishing it from siblings like bc_read_data or bc_navigate.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly states when to call (after finishing work), when not to call (mid-workflow or to reset), and alternatives (use bc_read_data to refresh). It also covers special cases like drill-down pages.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_execute_actionA
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[]; 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[]. | |
| 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. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite no annotations, the description fully discloses behavior: validates action, sends RPC, applies events, returns various response fields, handles batch operations, staleness guard, file downloads, and dialog requirements.
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 well-structured with clear sections and examples, but somewhat lengthy. It earns its length with valuable information, though could be slightly more concise.
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 all necessary aspects given 8 parameters and no output schema: prerequisites, mutually exclusive fields, batch behavior, dialog handling, staleness guard, and file outputs.
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%, yet description adds substantial context beyond schema definitions: examples for cue, section, bookmarks; explains mutual exclusivity; describes staleness guard behavior for expectedStateVersion.
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 explicitly states it executes a named action or cue-tile drill-down on an open page, using strong verbs and specific resource references. It distinguishes from siblings like bc_write_data and bc_navigate.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides clear when-to-use guidance, including explicit exclusions for writing fields (use bc_write_data) and navigating (use bc_navigate). Also explains when to follow up with bc_respond_dialog.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_list_companiesA
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 | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Despite no annotations, the description discloses that the tool opens, reads, and closes the BC Companies page internally, and does not affect open pages or session state.
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?
Description is clear and front-loaded, but includes a slightly redundant sentence about not using if company name is known. Still concise overall.
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, return details, usage context, and behavioral side effects. No output schema exists, but description adequately explains what is returned.
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?
No parameters exist; schema coverage is 100%. Description states no parameters are required, which adds clarity beyond the empty 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 clearly states the tool lists all companies and returns company names plus the active company. It distinguishes from sibling tools by specifying it is used before bc_switch_company.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to use (before bc_switch_company to verify target company) and when not to use (if company name is known). Provides workflow guidance: use bc_switch_company then bc_open_page.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_lookupA
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. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations, but the description discloses key behaviors: non-mutating, cancels without selecting, leaves field unchanged, returns error for lookupCustom fields. Does not cover edge cases like closed pages but covers major aspects.
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 main purpose and workflow, but includes some repetitive explanations. Still clear and well-organized despite moderate length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given 4 parameters, no output schema, and no annotations, the description is thorough: explains preconditions, workflow, and behavioral results. Lacks detail on return format but sufficient for a lookup 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?
Schema coverage is 100%, and the description adds meaning: field must be editable FK, search uses BC's native search, maxRows default and max. Provides context beyond 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 clearly states the tool enumerates candidate values for a related-table (FK) field using BC's built-in Lookup. It provides specific examples like listing Salesperson Codes, and distinguishes itself from sibling tools by focusing on field enumeration.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly details when to use (field has isLookup=true) and when not to use (option/enum fields, isLookup false, lookupCustom=true). Provides workflow and alternative suggestions, making the decision clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_open_pageA
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. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden, and it does so thoroughly: it explains the returned section structure, page-shape variations, pageContextId and stateVersion semantics, enum/options handling, the need to close the page, and the BC Online sign-in window. It also details what NOT to pass as arguments during sign-in. Very transparent.
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 well-structured and front-loaded with the core behavior. Every section earns its place: output shape, workflow, sign-in warning, and examples. It loses one point because it is verbose and partially repeats guidance already present in the schema, but the complexity of the tool justifies most of the length.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given no output schema and no annotations, this description is remarkably complete for a complex, stateful tool. It covers return section kinds, shapes for card/list/document pages, enum field payload guidance, sibling-tool routing, workflow sequencing, resource cleanup, and authentication behavior. An agent has enough context to call this tool correctly and know what to expect.
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 input schema already describes all three parameters at 100% coverage, so the baseline is 3. The description adds useful examples and workflow context, such as pageId examples and 'bookmarks come from list rows in any prior section,' but most of this reinforces rather than extends the schema. There is no significant new parameter semantic beyond what the schema already 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?
The description states a precise verb and resource ('Opens a Business Central page by its numeric page ID') and immediately distinguishes this tool from siblings: it is the entry point for page-scoped work, while bc_query, bc_search_pages, bc_list_companies, etc. do not need a pageContextId. The examples for Customer List and Customer Card further disambiguate what the tool does.
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?
Usage guidance is explicit: prefer bc_query for bulk read-only data, use bc_search_pages first if the page ID is unknown, do not call this if the page is already open, always call bc_close_page when done, and a typical workflow is spelled out. It also warns about the sign-in window and retry behavior. This is strong, actionable when-vs-alternative guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_queryA
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". |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden of disclosing behavior. It explicitly states the tool is read-only, describes the auth flow (Basic auth for on-prem, DEVICE_LOGIN_REQUIRED for SaaS), explains the default top=100, and notes company-scoping with the special exception for the 'companies' entity. It also warns that $expand increases response size. This is thorough and transparent.
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?
Despite being lengthy, the description is tightly organized with clear topic headers and no filler. Each sentence adds distinctive value: core functionality, entity list, parameter guidance, auth notes, and exclusions. The front-loading of purpose and the structured sections make it easy to scan.
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?
The tool is complex with 7 parameters and many integration details, but the description covers all essentials: what it reads, how filtering/projection work, entity naming, auth modes, default behavior, sibling alternatives, and the one special-case entity. It even addresses pagination via the top parameter. The absence of an output schema is mitigated by the clear 'reads records' semantics.
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?
Although schema coverage is 100%, the description enriches each parameter with concrete OData syntax examples and operational semantics (e.g., 'top defaults to 100 if omitted', 'pass company to target a specific company'). It goes well beyond the schema's simple field explanations, making the parameter usage immediately actionable.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with a specific verb and resource: 'Reads records from Business Central in bulk using the Standard API v2.0 (OData/REST on port 7048).' It distinguishes from siblings by explicitly stating it is more efficient than bc_open_page + bc_read_data for bulk reads and by noting it does not use a WebSocket session. This leaves no ambiguity about what the tool does.
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?
The description contains dedicated 'When to use' and 'When NOT to use' sections. It names specific alternatives for UI flows (bc_open_page + bc_execute_action) and mutations (bc_write_data and bc_execute_action), and even points to bc_list_companies for company discovery. This is exemplary guidance for tool selection.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_read_dataA
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. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, so description carries full burden. It thoroughly discloses return shape (section with fields/rows, stateVersion), behavior differences between card and list sections, filtering/sorting/clearFilters nuances, and limitations like SourceTableView filters remaining active. StateVersion usage for stale-state rejection is clearly explained.
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?
Description is well-structured with clear sections and examples, but it is quite lengthy. While every part adds value, some redundancy exists (e.g., filter syntax repeated in schema and description). Front-loaded with core purpose, but could be slightly more concise.
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 no output schema, description adequately explains return values, all parameters, prerequisites, and error conditions (non-sortable columns). Includes practical examples covering common use cases. Covers all necessary aspects for correct tool invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, but description adds substantial context beyond schema: default section='header', how options work for enum fields, filter syntax, sorting details, clearFilters caveats. This significantly aids correct parameter usage.
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 exactly what the tool does: 'Refreshes a single section on an already-open page.' Clearly distinguishes from sibling tools like bc_query and bc_write_data by specifying when to use each.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit when-to-use and when-not-to-use guidance: 'Do NOT use this for bulk or analytical reads... prefer bc_query' and 'Use bc_read_data when you need the interactive page's exact rows, factboxes, or option metadata.' Also notes prerequisite of pageContextId from bc_open_page.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_respond_dialogA
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" (force-close), 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[]; 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. | |
| 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. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, so description carries full burden. Discloses effect on workflow, chained dialogs, side effects (changedSections, downloads, externalUris). Warns against guessing dialogFormId. No contradictions.
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?
Description is detailed but front-loaded with main purpose. All sentences are necessary for complex workflow; could be slightly more concise but not overly verbose.
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 all aspects: workflow usage, parameter sources, chained dialogs, side effects like downloads/URIs. No output schema but explains expected result fields adequately.
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%, but tool description adds context: how to obtain dialogFormId and pageContextId from previous responses, and clarifies response enum options beyond schema. Adds meaningful 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?
Clearly states it responds to dialogs triggered by other tools (bc_execute_action, bc_write_data, bc_run_report). Differentiates from siblings by specifying its role as a follow-up to dialog triggers.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states when to call (upon dialogsOpened or requestPage) and when not to call (without preceding dialog). Provides examples, mentions chained dialogs, and explains response options.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_run_reportA
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.
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). |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden, and it delivers: it discloses server-side execution, internal driving of the BC 'Send to...' flow (SystemAction 410), request-page auto-driving when format is set, oversized-file error behavior, and layout-dependent format availability. This is a high-transparency description for a complex mutation-like 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?
The description is long but every section earns its place: summary, request-page workflow, format mechanics, layout caveats, usage boundaries, and concrete examples. The structure front-loads the core behavior and then layers detail logically, making it easy for an agent to extract the critical decision points quickly.
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 there is no output schema, the description adequately explains return behavior (downloads[] with base64 bytes or savedPath, oversized-file errors, requestPage details). It also covers edge cases like unavailable formats and defaults, making it complete enough for an agent to invoke the tool correctly in most scenarios.
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?
Although input schema coverage is 100%, the description adds substantial meaning beyond the schema: examples of valid invocations, what omitting format does, how format maps to BC's 'Send to...' flow, and how errors surface when a format is unavailable. This goes well beyond the baseline set by 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 action—execute a Business Central report by numeric report ID—and clearly distinguishes reports from pages and from data-viewing tools. It also names sibling tools (bc_open_page, bc_read_data) as the alternatives for data retrieval, so the tool's role is unambiguous.
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?
The description explicitly says when to use this tool (server-side report actions, inspecting/filling request page parameters) and when not to use it (viewing data—use bc_open_page/bc_read_data instead). It also provides common report IDs and explains the format-selection behavior, leaving little ambiguity about correct invocation.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_search_pagesA
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. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description fully discloses behavioral traits: profile scoping, return format including a note for empty results, AL names vs numeric IDs, and the need to map runTarget to page IDs for opening. This is comprehensive and transparent.
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 well-organized with front-loaded purpose and clear sections, but it is somewhat lengthy. Each paragraph adds important context, so no waste, but could be slightly more concise.
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 complexity (Tell Me index, profile scoping, AL name resolution) and lack of output schema, the description covers return structure, empty result behavior, and example use cases, making it fully complete for an agent to use 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?
Only one parameter (query) with schema coverage 100%. The description adds value by providing example queries ('customer', 'sales order') and noting fuzzy matching, going beyond the schema's basic description.
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 clearly states that the tool searches BC's Tell Me index for pages and other run-targets, specifying the resource and return types. It distinguishes from siblings like bc_open_page and bc_read_data, making the purpose specific and unambiguous.
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 guidance on when to use (unknown page ID) and when not (known numeric page ID or reading data). It also explains profile scoping and suggests BC_PROFILE remediation for empty results, providing clear context for usage.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_switch_companyA
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 match. 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. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations are provided, so the description carries full burden. It fully discloses that all open pages are invalidated, pageContextIds become unusable, and subsequent operations target the new company. The mid-workflow warning adds critical behavioral 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?
The description is concise and well-structured: first the purpose, then behavioral effects, usage guidelines, and an example. Every sentence adds value, and the length is appropriate for the tool's complexity.
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 impact (session state change), the description covers all necessary context: prerequisites (list companies), exact match requirement, effect on pages, and workflow restrictions. No output schema needed; the description is complete.
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% with a basic description. The description adds value by stating 'exact match', directing to use bc_list_companies, and providing an example. This goes beyond the schema's minimal description, though not dramatically so.
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 clearly states 'Switch to a different company within the current Business Central session' using a specific verb and resource. It distinguishes itself from siblings like bc_list_companies (which lists companies) and bc_open_page (which opens pages), making the tool's unique role clear.
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?
The description provides explicit when-to-use guidance: use bc_list_companies first, require exact match, and warns against switching mid-multi-step workflow. It also explains the effect on pages, offering clear alternatives and restrictions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
bc_write_dataA
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. | |
| 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. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, but description fully covers behavioral traits: BC validation, server-confirmed values, read-only field errors, cascading updates, and staleness guard with expectedStateVersion.
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-structured with clear sections, explicit do/don't, and four relevant examples. Every sentence is informative without 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?
Despite no output schema, description explains return value (server-confirmed value) and staleness rejection. Complex tool (6 params, nested objects) is fully covered with usage details.
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%, but description adds significant context beyond schema, such as how to use section, rowIndex, bookmark, and expectedStateVersion, plus examples. Baseline 3, increased due to extra guidance.
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 clearly states the tool writes field values on an already-open BC page, distinguishes from siblings like bc_execute_action and bc_navigate, and includes specific usage cases.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly tells when to use (write fields) and when not to (actions, navigation), with alternative tools named. Provides guidance on grouping related fields and using bookmark over rowIndex.
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. Dates show when Glama detected each change.
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
Each tool has a distinct and unique purpose with no functional overlap. For example, bc_read_data refreshes UI sections while bc_query performs bulk OData reads, and bc_execute_action triggers actions while bc_wizard_navigate drives wizard steps. Descriptions clearly differentiate when to use each tool.
All tools follow the 'bc_' prefix with consistent snake_case naming. The pattern is verb_noun (e.g., bc_open_page, bc_write_data, bc_list_companies) with clear, descriptive names. No mixed conventions or ambiguous abbreviations.
With 14 tools, the set covers the essential operations for interacting with Business Central (page management, data reading/writing, actions, reports, company switching, lookup, search) without being bloated or sparse. Each tool serves a well-defined role in the workflow.
The tool surface provides comprehensive coverage of Business Central interactions: opening, reading, writing, navigating, executing actions, running reports, handling dialogs, switching companies, and querying via OData. The workflow from discovery (bc_search_pages) through lifecycle is complete with no obvious gaps.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Connect your business apps to your MCP compatible AI in 1 click
Let AI agents query data and act across all your business apps via MCP.
Connect Exact Online to your AI assistant via MCP. Manage Exact Online with natural language.
Give AI agents the LinkedIn tools to find, qualify, engage, and follow up with prospects.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables 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.1457MIT
- 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.1457MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/SShadowS/business-central-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server