Skip to main content
Glama
businesscentralal

origo-bc-mcp-server

origo-bc-mcp-server

Origo Business Central MCP server — connects AI clients (VS Code Copilot, Claude Desktop, etc.) to Business Central via the Cloud Events API.

Setting this up for the first time? Start at the installation overview — it takes four parts, two of which are inside Business Central.

Features

Area

Tools

Description

Discovery

who_am_i, bc_list_tenants, bc_list_environments, bc_list_companies, bc_select, bc_get_selection

Auth check and tenant/environment/company selection

Table metadata

list_tables, get_table_info, get_table_fields, get_table_relations, get_table_permissions, get_page_url

AL table/field schema introspection

Data records

get_records, set_records, get_record_ids, get_document_lines, batch_records

Read/write any BC table via Cloud Events

Search

search_customers, search_vendors, search_items, search_gl_accounts, search_bank_accounts, search_employees, search_contacts, search_resources, search_fixed_assets, search_projects, search_records

Full-text search across common master data

Totals & aging

get_record_count, get_decimal_total, compute_customer_aging, compute_vendor_aging, compute_period_breakdown, compute_period_to_date

Aggregations without pulling raw rows

Message types

list_message_types, get_message_type_help, call_message_type, invoke_message_type (lite)

Generic access to any BC Cloud Event message type

Queue

queue_get_status, queue_retry, queue_cancel

Manage async Cloud Event queue tasks

Translations

list_translations, get_field_translations, get_field_translation, set_field_translation, set_translations

Multi-language field translation management

Integration timestamps

get_integration_timestamp, set_integration_timestamp, reverse_integration_timestamp

Track last-sync watermarks for external integrations

Memory & config

list_company_memory, get_company_memory, set_company_memory, list_user_memory, get_user_memory, set_user_memory, get_config, set_config

Persistent notes/config stored in BC's Cloud Event Config Store

Incoming documents

create_incoming_document, extract_incoming_document_attachments, process_incoming_document

Upload and process incoming document attachments

Crypto

encrypt_data, encode_base64, decode_base64

AES-256-GCM encryption and base64 helpers

Business events

list_bc_business_event_definitions, list_bc_business_event_subscriptions, create_bc_business_event_subscription, delete_bc_business_event_subscription, renew_bc_business_event_subscription

Manage [ExternalBusinessEvent] subscriptions

API/OData testing

bc_list_api_endpoints, bc_get_api_metadata, bc_api_request

Discover, inspect, and test any BC API v2.0 or custom API page endpoint — full CRUD (GET/POST/PATCH/DELETE), $metadata parsing (fields, keys, nav properties), and OData query support ($filter, $select, $top, $orderby, $expand)

Developer Services

bc_dev_get_metadata, bc_dev_get_symbols, bc_dev_publish_app, bc_dev_publish_artifact, bc_dev_uninstall_app, bc_dev_unpublish_app, bc_dev_run_tests

AL publish/cleanup against BC Developer Services + Automation API; Cosmo SSH unit tests

Cosmo Alpaca

cosmo_list_containers, cosmo_get_container, cosmo_create_container, cosmo_update_container, cosmo_delete_container, cosmo_deploy_app, cosmo_get_app_info, cosmo_ssh_info, cosmo_ensure_ssh, cosmo_restart_nst, cosmo_whoami_config

Cosmo Alpaca container lifecycle, feed deploy, SSH info and recovery, NST restart (Bearer; independent of BC auth)

Skills

get_cloud_events_api_skill

Bundled reference docs for the Cloud Events API

Related MCP server: business-central-mcp

Installation overview

A working connection has four parts, and all of them have to be right. The most common failure is finishing Part A and assuming that is the install — Parts C and D are inside Business Central and are easy to miss.

Part

Where

What

A

Your machine

Install the MCP server and configure the connection

B

Entra (Azure)

App registration with access to BC

C

Business Central

Install the Origo Cloud Events Core extension

D

Business Central

Register the app and grant it permissions

Part A on its own gives you a server that starts, authenticates, and returns nothing useful. When you are done, run the verification sequence — each step proves one part.


Part A — Your machine

1. Prerequisites

node --version

2. Install from GitHub

npm install -g github:businesscentralal/origo-bc-mcp

npm clones the repository with git, so you need access to it (an SSH key or a signed-in credential helper) before this command will succeed.

Verify:

origo-bc-mcp-server --help

3. Configure the connection

Run the interactive setup wizard:

origo-bc-mcp-server setup

The wizard walks you through:

  1. Connection type (SaaS or on-prem)

  2. Credentials (client secret, refresh token, or web service key)

  3. Secret storage (DPAPI on Windows, Keychain on macOS)

  4. Connection validation

  5. MCP client configuration (mcp.json for VS Code)

  6. Desktop shortcut (optional)

To add a single connection without running the whole wizard:

origo-bc-mcp-server add sandbox1

4. The settings file

Settings land in ~/.origo-bc-mcp/local.settings.json (macOS/Linux) or %USERPROFILE%\.origo-bc-mcp\local.settings.json (Windows). A SaaS connection looks like this:

{
  "devConnection": {
    "tenantId": "<entra-tenant-guid>",
    "clientId": "<app-client-id>",
    "authType": "s2s",
    "clientSecret": "keychain:origo-bc-mcp-default-secret",
    "environment": "Sandbox1",
    "companyId": "Vorpun"
  }
}

That is everything a stdio connection needs. devConnection is the only BC credential — the app authenticates service-to-service (S2S) with a client ID and a secret.

  • companyId must be current. A stale company name gives Company '<name>' not found on the first call.

  • Store secrets behind a prefix — keychain: (macOS), dpapi: (Windows), env: or aes:. Never in plain text.

  • Validate the JSON after any manual edit. The server reads this file inside a try/catch and falls back to empty settings when it cannot parse it. No error mentions the file — you only see No auth context later, from a tool call. A single stray comma is enough. Quick check:

    python3 -m json.tool ~/.origo-bc-mcp/local.settings.json

About basicAuth

The setup wizard usually also writes a basicAuth block. It does not apply to stdio connections and can be left alone:

"basicAuth": { "enabled": true, "username": "dev", "password": "<password>" }

basicAuth protects the server's HTTP endpoints and the dashboard (/dashboard) when it runs in HTTP mode. Over stdio there is no HTTP traffic and no headers are sent, so the credentials are never used.

The one visible effect: the username becomes a label that shows up as the principal in who_am_i. That is not a BC user and it does not exist in BC. The real identity is user.userName in the who_am_i response — the name of the Entra app as registered in BC.

5. Connect your MCP client

Create .mcp.json in the project folder:

{
  "mcpServers": {
    "origo-bc-mcp-sandbox1": {
      "command": "origo-bc-mcp-server",
      "args": ["--stdio"]
    }
  }
}

This is a stdio connection — the server runs as a child process, with no network service and no port. That is why it does not show up under Connections in the app: it is scoped to that folder.

To use a connection other than devConnection, set MCP_CONNECTION in an env block. See Stdio auth for BC tools.

VS Code

The setup wizard writes VS Code's mcp.json for you. For other clients over HTTP, see Configure an MCP client.


Part B — Entra (Azure)

An app registration must exist with:

  • Client ID and Tenant ID — these go into local.settings.json

  • Client secret — stored in Keychain / DPAPI / an environment variable

  • API permission: Dynamics 365 Business Central → API.ReadWrite.All (Application)

  • Admin consent granted

This is access to the door only. What the app may actually do inside BC is decided in Part D.


Part C — Origo Cloud Events Core extension

The server runs every data call and every action through the custom API that this extension publishes:

/api/origo/{bifrost|cloudEvent}/v1.0/companies(<guid>)/tasks

(The server tries the bifrost group first and falls back to the legacy cloudEvent group — cloudevent on-prem.)

Without the extension almost nothing works — not who_am_i, get_records, list_message_types, nor any message type.

Install it in BC: Extension Management → Manage → Extension Marketplace (AppSource), or Upload Extension for a .app file.

Silent failure warning. The standard BC API (/api/v2.0/) keeps working perfectly without the extension. So bc_api_request returns correct data while get_records returns empty results and no error at all. It looks like an empty company, not a broken install. When those two disagree, suspect the extension.


Part D — Register the app in BC and grant permissions

This is the part that gets forgotten most often.

1. Register the app

Search BC for the Microsoft Entra Applications page (formerly AAD Applications).

  • Create an entry with the app's Client ID

  • Set State = Enabled

This is inside Business Central, not the Entra portal. The app does not appear under Users — S2S apps live on their own page.

2. Grant permissions

Under User Permission Sets on that entry:

Test environments — one set is enough:

Set

Name

CE FULL ACCESS ORI

Full Access

Production — least privilege. The base set plus whichever gates you need:

Set

Name

Opens

CE API ACCESS ORI

API Access

Base — always required

CE JOB POST ORI

Project Posting Gate

Posting to projects

CE WHSE POST ORI

Warehouse Posting Gate

Picks and warehouse postings

CE ITEM POST ORI

Item Posting Gate

Item ledger entries

CE G/L POST ORI

G/L Posting Gate

General ledger postings

CE READ ALL ORI

Read-Only

Read access only

The app also needs standard BC permissions (D365 FULL ACCESS or equivalent).

CE READ ALL ORI is not sufficient on its own — not even for pure reading. Every call writes a record to the Cloud Event Message table, so the base access has to be there.

3. Change Log Write Guard (writes only)

The extension protects writes with an allowlist. A write to a field that is not on it stops with:

Field "1" in table 167 must be included in the change log write guard setup
to be updated via Cloud Events.

If the MCP connection needs to change particular fields, add them to the Change Log Write Guard setup in BC. This is a deliberate safety valve — do not work around it without a reason.


Verify the whole chain

Run these in order. Each step proves one part:

#

Call

Should return

Points at

1

who_am_i

status: Success and the app's BC user name

Part C or D missing

2

list_message_types

~22 namespaces (Help, Data, Sales, Warehouse …)

Part D — permissions

3

get_records on Customer

Real records

Part C — empty result, no error

4

bc_api_request on customers

The same records

Part A or B

If step 4 returns data but step 3 does not, the extension (Part C) is missing or the permission sets are short.

What the connection can and cannot do

Can: read and write any BC table through get_records / set_records, run ~80 message types (posting sales and purchase documents, approvals, warehouse documents, and so on), and call the standard API directly.

Cannot: run arbitrary UI actions. An action that is a button on a BC page is only reachable if it has been published as a message type. For example, Create Inventory Pick on a project does not exist as a message type — the only pick action is Warehouse.Pick.Create, from a warehouse shipment. Actions like that have to be run in the UI.

List what is available with list_message_types, and get usage details with get_message_type_help.

Managing connections

Add a connection

Add a new connection without running the full setup wizard:

origo-bc-mcp-server add production
origo-bc-mcp-server add sandbox
origo-bc-mcp-server add           # prompts for name

This asks for connection details, validates, saves to local.settings.json, and registers the MCP entry in VS Code's mcp.json.

List connections

origo-bc-mcp-server remove        # lists available connections without removing anything

Remove a connection

origo-bc-mcp-server remove production

Removes the named connection from local.settings.json and its entry from VS Code's mcp.json. Prompts for confirmation.

Create a desktop shortcut

origo-bc-mcp-server shortcut              # shortcut for default server
origo-bc-mcp-server shortcut production   # shortcut for a named connection

Creates a double-clickable shortcut on your Desktop to start the server:

  • Windows: .lnk file (opens cmd)

  • macOS: .command file (executable shell script)

  • Linux: .desktop file

Clean all config

origo-bc-mcp-server clean

Removes the entire local.settings.json, all origo-bc-* entries from VS Code's mcp.json, and all desktop shortcuts. Use this to start fresh. Prompts for confirmation.

Server modes (full vs lite)

By default the server runs in full mode — all tool groups from the Features table above are registered (~89 tools). This is best for capable models (GPT-4o, Claude Sonnet, etc.) that handle large tool sets well.

Lite mode registers a reduced set (~25 tools) built around invoke_message_type as a universal entry point, plus data records, aging, period breakdown, crypto, memory, and the Cloud Events skill doc. Use this for local/smaller LLMs that get confused or slow down with too many tool definitions. The API endpoint testing tools (bc_list_api_endpoints, bc_get_api_metadata, bc_api_request) are not included in lite mode.

Enable lite mode with an environment variable:

MCP_LITE=1 origo-bc-mcp-server          # macOS/Linux
$env:MCP_LITE="1"; origo-bc-mcp-server   # Windows PowerShell

The startup banner confirms which mode is active:

origo-bc-mcp listening on :3000 (development, LITE)
  LITE MODE:       reduced tool set for local LLMs

To use lite mode with a named connection or PM2, set MCP_LITE=1 in that process's environment (e.g. ecosystem.config.cjs env block for a second PM2 app entry).

Transport

When to use

Network

stdio (--stdio)

Grok Bot / Cursor local command MCP on the Architect box (shared by all his agents)

None — process stdin/stdout only

HTTP (default)

Local dashboard, health checks, Docker on a private host

Binds 127.0.0.1 by default (MCP_HOST). Docker/PM2 set MCP_HOST=0.0.0.0 for container publish. Do not expose publicly (no public Caddy).

Remote HTTP url MCP from Cursor’s backend cannot reach ORI1058/E4-212 localhost. Stdio runs the server on the Grok Bot computer (Architect box), not on ORI1058.

Run stdio

origo-bc-mcp-server --stdio
# or, after build:
npm run start:stdio

Same full tool set as HTTP (bc_dev_*, cosmo_*, and all tools from buildServer). Optional: MCP_LITE=1 for the reduced set.

Stdio auth for BC tools

HTTP Basic middleware does not run on --stdio. Auth is installed and re-bound as follows:

  1. Startup — MCP_STDIO_AUTH=1 plus a process auth context from local.settings.json (devConnection or connections[MCP_CONNECTION]).

  2. Every tools/call — the MCP request handler is wrapped with ensureAuthBound so ALS is re-entered for that invocation (Cursor AddMcpServer can otherwise run handlers outside the startup ALS/enterWith tree).

  3. getAuthContext fallback — if ALS and the process fallback are both missing, read globalThis (shared across duplicate ESM graphs) then rebuild from MCP_CONNECTION / local.settings.

  4. registerTool wrap — every tool callback is wrapped with withStdioAuth at registration so Cursor handlers re-bind even when outside the startup ALS tree.

Source

Role

MCP_LOCAL_SETTINGS_PATH (or ~/.origo-bc-mcp/local.settings.json)

Loads settings

devConnection

Used when MCP_CONNECTION is unset or default

connections.<name>

Used when MCP_CONNECTION=<name> (e.g. bc28-is-grok)

basicAuth

Optional; username becomes the stdio principal label. Credentials are not required on stdio (no HTTP headers).

Cosmo (cosmo_*)

Independent — Bearer via COSMO_BEARER_TOKEN / gh; does not need BC auth context (binder is a no-op when BC settings are absent)

Without a resolvable devConnection / named connection, who_am_i and bc_dev_* fail with No auth context — request reached a tool without authentication.

# default → local.settings.devConnection
origo-bc-mcp-server --stdio

# named connection (e.g. Cosmo Alpaca container wired as bc28-is-grok)
MCP_CONNECTION=bc28-is-grok \
MCP_LOCAL_SETTINGS_PATH=~/.origo-bc-mcp/local.settings.json \
  origo-bc-mcp-server --stdio

Prefer env: / env vars for secrets in local.settings (user/key, client secrets, Cosmo token).

Grok Bot / Cursor local command (mcp.json)

Prefer env for secrets (do not put tokens in tool args). Example:

{
  "mcpServers": {
    "origo-bc-mcp": {
      "command": "origo-bc-mcp-server",
      "args": ["--stdio"],
      "env": {
        "COSMO_BEARER_TOKEN": "<from Cosmo Alpaca session>",
        "ADO_PAT": "<optional Azure DevOps PAT for bc_dev_publish_artifact>",
        "GITHUB_TOKEN": "<optional for GitHub artifacts>",
        "MCP_ENCRYPTION_KEY": "<64 hex chars if local.settings uses aes: secrets>",
        "MCP_LOCAL_SETTINGS_PATH": "/path/to/local.settings.json",
        "MCP_CONNECTION": "bc28-is-grok"
      }
    }
  }
}

Notes:

  • command is resolved on the Grok Bot / Architect box (where the agent runs), not on ORI1058.

  • Install the package on that box (npm install -g github:businesscentralal/origo-bc-mcp or from the Azure Artifacts feed).

  • BC tools over stdio need devConnection (or connections[MCP_CONNECTION]) in local.settings.json — see Stdio auth for BC tools.

  • Connection secrets belong in env or local.settings.json with env: / aes: prefixes — tool pat / token args are optional overrides only.

  • HTTP url pointing at ORI1058 localhost is not usable from Cursor’s remote MCP path; use stdio instead.

Start the server

origo-bc-mcp-server

Expected output:

origo-bc-mcp listening on 127.0.0.1:3000 (development)
  MCP endpoint:    http://localhost:3000/mcp
  Dashboard:       http://localhost:3000/dashboard
  Health:          http://localhost:3000/healthz
  Bind:            127.0.0.1 (local only — set MCP_HOST=0.0.0.0 for Docker)

For local HTTP only on loopback (default). Prefer --stdio for Grok Bot / Cursor local command.

Dashboard

The server includes a web dashboard at /dashboard:

  • Real-time logs — SSE stream with filtering, auto-scroll, clear

  • Active sessions — connected MCP clients

  • Server stats — uptime, memory, PID, Node version

  • Debug toggle — enable/disable MCP_DEBUG at runtime without restart

  • Setup UI (/dashboard/setup) — manage connections, Basic Auth credentials, validate endpoints

  • Restart / Stop — PM2-aware controls (in Docker containers)

The dashboard is protected by Basic Auth credentials. On first start with no config, it's open to allow initial setup.

Custom port

PORT=3001 origo-bc-mcp-server          # macOS/Linux
$env:PORT="3001"; origo-bc-mcp-server   # Windows PowerShell

Verify

Check server health:

curl http://localhost:3000/healthz

Validate BC connections:

origo-bc-mcp-server verify             # all connections
origo-bc-mcp-server verify production   # specific connection

CLI reference

origo-bc-mcp-server [command] [options]

Commands:
  setup                 Guided wizard to configure connections and VS Code mcp.json
  add [name]            Add a single connection (streamlined)
  verify [name]         Validate a connection (default: all connections)
  remove <name>         Remove a specific connection (or list available)
  shortcut [name]       Create a desktop shortcut to start the server
  clean                 Remove ALL connections, config, and shortcuts
  init                  Create ~/.origo-bc-mcp/local.settings.json from template

Options:
  --stdio               MCP over stdin/stdout (recommended for Grok Bot / Cursor local)
  --config <path>       Start with a specific local.settings.json
  --debug               Verbose logging (stdio → stderr)
  -h, --help            Show help

Configure an MCP client

Recommended (stdio): see Transports: stdio (recommended) vs HTTP for Grok Bot / Cursor command + args + env.

The setup wizard writes VS Code's mcp.json automatically (HTTP). For other clients using local HTTP (loopback only):

{
  "servers": {
    "origo-bc-mcp": {
      "url": "http://127.0.0.1:3000/mcp",
      "headers": {
        "Authorization": "Basic <base64-encoded username:password>"
      }
    }
  }
}

Generate the Basic auth value:

echo -n 'username:password' | base64                                          # macOS/Linux
[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes("username:password")) # Windows

The credentials are the basicAuth.username and basicAuth.password from your local.settings.json.

Update

npm install -g github:businesscentralal/origo-bc-mcp

Your local.settings.json is preserved across updates.

Uninstall

Remove the npm package

npm uninstall -g origo-bc-mcp-server

Remove configuration files

Windows (PowerShell):

# Remove local settings
Remove-Item "$env:USERPROFILE\.origo-bc-mcp" -Recurse -Force -ErrorAction SilentlyContinue

# Remove MCP entries from VS Code (or use 'origo-bc-mcp-server clean' before uninstalling)

macOS/Linux:

rm -rf ~/.origo-bc-mcp

Remove VS Code MCP entries

Either run origo-bc-mcp-server clean before uninstalling, or manually edit your VS Code mcp.json:

  • Windows: %APPDATA%\Code\User\mcp.json

  • macOS: ~/Library/Application Support/Code/User/mcp.json

  • Linux: ~/.config/Code/User/mcp.json

Remove any "origo-bc-*" entries from the "servers" object.

Remove desktop shortcut (if created)

Delete the "Origo BC MCP" shortcut from your Desktop manually.

Remove stored secrets

If you used DPAPI or Keychain during setup, the encrypted values are embedded in the config files (already deleted above). Environment variables you set manually (e.g. BC_DEV_CLIENT_SECRET) should be removed separately:

# Windows — remove a user-level env var
[Environment]::SetEnvironmentVariable('BC_DEV_CLIENT_SECRET', $null, 'User')
# macOS — remove Keychain entry
security delete-generic-password -a mcp-encrypted-conn -s origo-bc-mcp-default-secret

Troubleshooting

Real messages from installs, and what is actually behind them:

Message

Cause

Fix

No HTTP resource was found … /cloudEvent/v1.0/…/tasks (404)

Cloud Events Core extension not installed

Part C

Sorry, the current permissions prevented the action. (TableData 10075497 Cloud Event Message IndirectInsert)

App has no CE permission sets

Part D.2

Field "N" in table X must be included in the change log write guard setup

Field is not on the write allowlist

Part D.3

Company '<name>' not found

Stale companyId in the settings file

Part A.4

get_records returns 0 rows but bc_api_request returns data

Extension missing — silent failure

Part C

No auth context — request reached a tool without authentication

devConnection missing or unresolvable — or local.settings.json is invalid JSON

Part A.4

Invalid expression of type: table view

Wrong filter syntax

Use BC format: WHERE(Job No.=FILTER(000573))

Authentication_InvalidCredentials

Bad or expired credentials

origo-bc-mcp-server verify

ECONNREFUSED when calling BC

Connection settings wrong

origo-bc-mcp-server verify

command not found / not recognized

npm global bin not on PATH

Restart the terminal; check npm bin -g

Unsupported engine

Node.js older than 22

Install Node.js 22+

Port 3000 in use

HTTP mode port conflict

Use a different port (see above)

SSL errors against on-prem BC

Self-signed certificate

NODE_TLS_REJECT_UNAUTHORIZED=0 origo-bc-mcp-server (dev only)

Custom config path

origo-bc-mcp-server --config /path/to/local.settings.json

Or set MCP_LOCAL_SETTINGS_PATH environment variable.

Develop

cp .env.example .env   # fill in BC_CLIENT_ID/SECRET, MCP_ENCRYPTION_KEY, ...
npm install
npm run dev            # tsx watch
# or
npm run build && npm start

Smoke check:

curl localhost:3000/healthz
curl localhost:3000/.well-known/oauth-protected-resource

Basic auth

Basic auth secures MCP endpoints and the dashboard. It works in all environments (local dev, Docker, production). Configure it in one of three ways:

  1. Dashboard Setup UI — open /dashboard/setup, fill in credentials (recommended for Docker)

  2. Environment variables — set MCP_ADMIN_USER + MCP_ADMIN_PASSWORD at startup

  3. Config file — set basicAuth in local.settings.json:

cp config/local.settings.example.json config/local.settings.json
# edit: basicAuth.username/password + devConnection
npm run dev

Then call the server with Basic credentials:

curl -u admin:yourpass -X POST localhost:3000/mcp \
  -H 'content-type: application/json' \
  -H 'accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

The same credentials protect the web dashboard at /dashboard.

The Basic-auth connection is locked to its configured tenant (it cannot cross tenants), exactly like x-origo-token.

devConnection supports two shapes:

  • On-prem (onPrem: true + baseUrl, onPremTenant, user, key, companyId, companyName) — Basic auth against an on-prem BC REST base URL. Mirrors the legacy BC_ONPREM_* mode. bc_list_companies returns the configured company; data calls use Basic base64(user:key) against {baseUrl}/api/origo/cloudevent/v1.0/...?tenant=....

  • SaaS (tenantId, clientId, authType, clientSecret or refreshToken, environment, companyId) — Entra. authType: "s2s" uses the client secret (the normal case); authType: "user" uses a refresh token. See Part A.4 for a complete example.

On-prem data calls (message types) are wired during tool migration; the connection, auth header (onPremAuthHeader) and company listing are in place.

Developer Services (bc_dev_*)

Tools for AL publish/cleanup against BC Developer Services and the Automation API. These talk to a container's {id}dev / Automation endpoints — not the Cosmo Alpaca control plane.

Tool

Purpose

bc_dev_get_metadata

GET {dev}/dev/metadata — package metadata from Developer Services

bc_dev_get_symbols

GET {dev}/dev/packages?… — writes large binaries to disk (path + size; no base64 dump)

bc_dev_publish_app

Multipart POST {dev}/dev/apps?tenant&SchemaUpdateMode=… (local appPath or appBase64)

bc_dev_publish_artifact

Download .app / zip from HTTPS, Azure DevOps build artifact, or GitHub Actions artifact/release; publish in dependency order

bc_dev_uninstall_app

Automation API Microsoft.NAV.uninstall (not DELETE /dev/apps)

bc_dev_unpublish_app

Automation API Microsoft.NAV.unpublish (BC 25.4+; uninstall first)

bc_dev_run_tests

Run AL unit tests via Cosmo SSH (scp + pwsh/powershell -File). Cosmo SSH is in-container — uses Client Services / PsTestFunctions (not host BcContainerHelper); structured passed/failed/skipped + failure messages

developerBaseUrl: set optional developerBaseUrl on devConnection, or derive it from on-prem baseUrl by replacing a trailing rest with dev (Alpaca: …/f0a4d51d4d47rest → …/f0a4d51d4d47dev). After cosmo_get_container, wire the derived restBaseUrl / developerBaseUrl into devConnection (company typically CRONUS IS). Verified publish path on Cosmo Alpaca: multipart /dev/apps → HTTP 200.

Uninstall / unpublish: prefer Automation API (bc_dev_uninstall_app / bc_dev_unpublish_app). If those fail or the container only allows SSH ops, use cosmo_ssh_info then Uninstall-NavApp / Unpublish-NavApp over SSH.

bc_dev_run_tests: prefers Cosmo SSH (GET /Container/Ssh/{id}). SSH is usable when ipAddress and privateKey are present — do not require available===true (available=false is expected while Starting after Stop→Start). Connects as sshuser with privateKey (never logged). SSH recovery: the Cosmo SSH endpoint can drop out while the container keeps running (the container record has no ssh flag). Before a run bc_dev_run_tests (and bc_dev_build_runtime_package) therefore enable SSH and wait for it (ensureSsh, default on; sshWaitSeconds, default BC_DEV_SSH_ENSURE_WAIT_S or 300; allowRestart=true also permits Stop → Start). The result's sshEnsure lists what was done. Remote invoke is scp of local run-tests.ps1 (+ vendored PsTestFunctions.ps1 / ClientContext.ps1) → C:\Windows\Temp\…, then pwsh -NoProfile -File <remote> (fallback powershell.exe -File); best-effort remote delete afterward. Do not pipe the script on stdin to pwsh -Command - (Cosmo Windows OpenSSH aborts after the first Write-Host).

Cosmo SSH is inside the BC container (not a Docker host). Host-side Invoke-NavContainerTests / Run-TestsInBcContainer / Run-AlTests usually do not exist there. The remote script therefore:

  1. Dot-sources C:\Run\Prompt.ps1 when present.

  2. If host helpers are missing → in-container Client Services path (same approach Run-TestsInBcContainer uses inside the container): locate Service-folder Newtonsoft + Microsoft.Dynamics.Framework.UI.Client.dll, load PsTestFunctions (scp upload or Install-Module BcContainerHelper), New-ClientContext to local NST /cs?tenant=…, Run-Tests (tries pages 130455 → 130202 → 130203 → 130409).

  3. Option B (BC 27.5+/28): if Client Services pages fail (page 130455 removed), best-effort Invoke-NAVCodeunit 130201 (CLI Test Runner / TestRunner-Internal) — documented clearly; JUnit may be absent.

  4. Soft diagnostics via Get-NAVAppInfo if runners fail (toolkit is often already present on Cosmo; do not treat publish as a hard gate).

Brief SSH connect retries while Starting. When ip/key are missing, returns a blocked error (no silent fallback) with Stop→Start recreate + cosmo_create_container/sshEnabled=true hints. Cosmo OpenAPI has no /Container/Exec/{id}/… test-runner endpoint. mode=helper is local-docker only (containerName). Reuses stdio/devConnection NavUserPassword credentials. Returns structured counts + failure messages (truncated previews; never logs privateKey / Nav passwords beyond truncated previews).

Follow-up (out of scope here): alc compile orchestration.

Cosmo Alpaca (cosmo_*)

Same MCP server as bc_dev_*. Cosmo tools call the Alpaca API with a Bearer token (container lifecycle, feed deploy, SSH info, NST restart). App publish of arbitrary .app / CI artifacts stays on bc_dev_*.

Config (origo-bc-mcp)

Auth and backend resolve in order: tool arg → env → ~/.origo-bc-mcp/local.settings.json → built-in default. Prefer env for secrets; never commit tokens.

Setting

Env / file

Notes

Bearer

COSMO_BEARER_TOKEN or cosmo.bearerToken

Required for cosmo_* calls

API base

COSMO_BACKEND_URL or cosmo.backendUrl

Must be Alpaca API base, not the bare public host

Default backendUrl (after this fix): https://cosmo-alpaca-enterprise.westeurope.cloudapp.azure.com/api/alpaca/release

That matches Cosmo Alpaca VS Code 1.27 OpenAPI basePath {host}/api/alpaca/release. A bare host (https://cosmo-alpaca-enterprise.westeurope.cloudapp.azure.com) yields nginx 404 on POST /Container/Container/filter. Paths are joined as ${backendUrl}/Container/... — do not append /api/alpaca/release again if the caller already passes the full API base.

Example cosmo block in ~/.origo-bc-mcp/local.settings.json (prefer env for the token):

{
  "cosmo": {
    "backendUrl": "https://cosmo-alpaca-enterprise.westeurope.cloudapp.azure.com/api/alpaca/release",
    "bearerToken": "env:COSMO_BEARER_TOKEN"
  }
}

Or only:

export COSMO_BEARER_TOKEN='…'   # preferred over committing bearerToken
# optional override:
# export COSMO_BACKEND_URL='https://cosmo-alpaca-enterprise.westeurope.cloudapp.azure.com/api/alpaca/release'

How to get a Bearer (VS Code 1.27): cosmo-alpaca.debugMode is not in the Settings UI. Add "cosmo-alpaca.debugMode": true to User settings.json, then Command Palette → Get GitHub API token or Get Azure DevOps API token (token copies to clipboard). Set COSMO_BEARER_TOKEN from that value.

Verify without echoing the secret: cosmo_whoami_config returns resolved backendUrl, whether a bearer is configured (+ length), and defaultBackendUrl / defaultPublicHost — never the token value.

Container tools (cosmo_*)

Tool

Cosmo API

What it is for

cosmo_list_containers

POST /Container/Container/filter

List/filter containers for the tenant

cosmo_get_container

GET /Container/Container/{id}

Status + derived REST/DEV URLs for devConnection

cosmo_create_container

`POST /Container/Container[/gitHub

/azureDevOps

cosmo_update_container

PATCH /Container/Container/{id}

Start

cosmo_delete_container

DELETE /Container/Container/{id}

Tear down ephemeral containers

cosmo_deploy_app

POST /Container/Exec/{id}/deployApp

Install from **NuGet

cosmo_get_app_info

GET /Container/Exec/{id}/appinfo

Installed app info blob from the container

cosmo_ssh_info

GET /Container/Ssh/{id}

SSH endpoint/credentials for NavApp fallback (read-only)

cosmo_ensure_ssh

GET /Container/Ssh/{id} + PATCH /Container/Container/{id}

Makes SSH usable: enables it (or starts a stopped container with sshEnabled=true), polls until ip + key + TCP are there; allowRestart=true also permits Stop → Start. Returns the steps taken, never the key

cosmo_restart_nst

POST /Container/Exec/{id}/restartServerInstance

Restart NST after stubborn publish/uninstall issues

cosmo_whoami_config

(local)

Confirm backend + token configured (no secret echo)

Public container host (for {id}rest / {id}dev) remains https://cosmo-alpaca-enterprise.westeurope.cloudapp.azure.com — that is not the Alpaca API backendUrl.

Cosmo vs bc_dev_* boundary

Concern

Use

Container CRUD, Start/Stop, SSH info, NST restart

cosmo_*

Install app from NuGet / Azure DevOps feed

cosmo_deploy_app

Publish local .app, GitHub Actions / ADO / HTTPS artifact

bc_dev_publish_app / bc_dev_publish_artifact

Uninstall / unpublish

bc_dev_uninstall_app / bc_dev_unpublish_app first; SSH Uninstall-NavApp / Unpublish-NavApp via cosmo_ssh_info if needed

AL unit tests

bc_dev_run_tests (SSH when ip+key present via scp/-File; in-container Client Services / PsTestFunctions, not host BcContainerHelper; blocked with recreate hint otherwise). Cosmo has no Exec test-runner API.

Ephemeral Cosmo loop (policy)

  1. cosmo_create_container (CreateBcContainer / CreateGitHubBcContainer / Azure DevOps / standalone)

  2. cosmo_get_container → wire derived …/{id}rest + …/{id}dev into devConnection (CRONUS IS)

  3. bc_dev_publish_* / tests / bc_dev_uninstall_app (Automation API)

  4. cosmo_delete_container

Do not treat standing personal bc28-is or machine ORI1058 as the long-term test host (avoid polluting personal containers). bc28-is-grok is optional interim only. Cosmo lifecycle tools live in this same MCP server alongside bc_dev_*.

Status & continuation

Scaffold + dual auth + tenant access guard + discovery tools are in place and compile/run. Next: migrate the ~40+ BC tools from the legacy server (api/mcp/tools/*) into src/tools/, then deploy to dev via Azure DevOps.

Further notes live in the docs/ folder of the source repository on Azure DevOps (BC-PTE-CloudEvents → Cloud Events MCP). They are not part of this published package:

  • docs/PROJECT-STATUS.md — full state, decisions, tool-migration inventory, open questions, resume checklist.

  • docs/RESUME-PROMPT.md — ready-to-paste prompt to continue the work later.

  • docs/local-dev.md — how to start the server locally (Basic auth, on-prem/SaaS).

  • docs/devops-setup.md — cross-tenant deploy setup.

Local install

Run with Docker

The included Dockerfile builds a production image with PM2 for automatic restarts. It sets MCP_HOST=0.0.0.0 so published ports work; the image is still intended for private hosts only (not public Caddy). Configuration is stored in a /data volume inside the container and managed through the web dashboard.

Step 1: Build the image

docker build -t origo-bc-mcp https://github.com/businesscentralal/origo-bc-mcp.git

Step 2: Run the container

Mount a local folder for persistent config storage:

docker run -d --name origo-bc-mcp --restart unless-stopped -p 3000:3000 -v "E:\Docker Storage\origo-bc-mcp:/data" -e MCP_ENCRYPTION_KEY=<64-hex-chars> -e MCP_ADMIN_USER=admin -e MCP_ADMIN_PASSWORD=<your-password> -e OLLAMA_PROXY_TARGET=http://<ollama-host>:11434 origo-bc-mcp
docker run -d --name origo-bc-mcp --restart unless-stopped -p 3000:3000 -v /path/to/origo-bc-mcp-data:/data -e MCP_ENCRYPTION_KEY=<64-hex-chars> -e MCP_ADMIN_USER=admin -e MCP_ADMIN_PASSWORD=<your-password> -e OLLAMA_PROXY_TARGET=http://<ollama-host>:11434 origo-bc-mcp

MCP_ENCRYPTION_KEY encrypts connection secrets (passwords, client secrets) at rest in the volume. Generate one with: openssl rand -hex 32

MCP_ADMIN_USER / MCP_ADMIN_PASSWORD secure the dashboard on first boot. Without these, the dashboard is open until you configure Basic Auth in the setup UI.

Step 3: Configure via the dashboard

Open http://localhost:3000/dashboard/setup in your browser.

On first launch (no config exists), the dashboard is open. Add your first connection and enable Basic Auth — subsequent visits will require login.

The setup page lets you:

  • Add SaaS (Entra) or On-Premises BC connections

  • Validate connections (test button confirms access and lists companies)

  • Configure Basic Auth credentials (used for both MCP access and dashboard login)

  • Remove connections

Step 4: Connect your MCP client

Point your MCP client (VS Code Copilot, Claude Desktop, Open WebUI, etc.) at:

http://localhost:3000/mcp

With Basic Auth header using the credentials you configured in the dashboard.

Dashboard login

The dashboard is protected by the same Basic Auth credentials configured in Setup. If you haven't configured Basic Auth yet, the dashboard is open (to allow first-time setup).

Environment variables

Variable

Default

Description

MCP_DATA_DIR

/data

Directory for local.settings.json (mounted volume)

MCP_ADMIN_USER

—

Bootstrap admin username (sets Basic Auth on first start if no config exists)

MCP_ADMIN_PASSWORD

—

Bootstrap admin password (pair with MCP_ADMIN_USER)

MCP_ENCRYPTION_KEY

—

64 hex characters for AES-256-GCM encryption of secrets at rest

MCP_PUBLIC_URL

http://localhost:3000

Public URL for the server

PORT

3000

Listen port

MCP_DEBUG

—

Set to 1 to enable debug logging (also toggleable from dashboard)

OLLAMA_PROXY_TARGET

http://192.168.16.241:11434

Ollama server URL for the /ollama proxy endpoint

Generating MCP_ENCRYPTION_KEY

The key must be exactly 64 hex characters (32 bytes). Generate one with any of these:

# OpenSSL (Linux/macOS/Git Bash)
openssl rand -hex 32

# Node.js (any platform)
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

# PowerShell (Windows)
-join ((1..32) | ForEach-Object { '{0:x2}' -f (Get-Random -Max 256) })

Store the key securely — if you lose it, secrets encrypted with it cannot be recovered.

Docker Compose example

services:
  mcp:
    build: https://github.com/businesscentralal/origo-bc-mcp.git
    ports:
      - "3000:3000"
    volumes:
      - ./mcp-data:/data
    environment:
      - MCP_ENCRYPTION_KEY=${MCP_ENCRYPTION_KEY}
      - MCP_ADMIN_USER=${MCP_ADMIN_USER:-admin}
      - MCP_ADMIN_PASSWORD=${MCP_ADMIN_PASSWORD}
    restart: unless-stopped

Health check

curl http://localhost:3000/healthz

Install from tarball

The server is also published to the Azure Artifacts feed BC-PTE-CloudEvents for local dev/test on Windows and macOS. Installing from GitHub (Part A.2) is the simpler route and is what these instructions assume.

If you install from the feed or from a downloaded tarball instead, the setup guides live in the docs/ folder of the source repository on Azure DevOps (docs/setup-from-tarball.md, docs/setup-macos.md, docs/setup-windows.md). Everything from Part B onwards is identical either way.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    Give AI assistants direct access to Microsoft Dynamics 365 Business Central via native WebSocket protocol.
    14
    46 npm
    41
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    Model Context Protocol (MCP) server for Microsoft Dynamics 365 Business Central. Provides AI assistants with direct access to Business Central data through properly formatted API v2.0 calls.
    6
    22 npm
    8
    MIT