bc-rig
# bc-rig
`bc-rig` is a Linux-hosted [Model Context Protocol](https://modelcontextprotocol.io/) server for
driving two real Microsoft Dynamics 365 Business Central client surfaces:
- Business Central web through Playwright and a persistent, human-authenticated Chromium session.
- MobileNAV through an Android emulator, `adb`, and `uiautomator`.
It exposes both drivers through one stdio MCP server and can record exploratory runs, screenshots,
and honest handover reports. The package does not contain credentials, customer data, MobileNAV,
or an Android system image.
## Install
Requirements:
- Linux and Node.js 22 or newer.
- Playwright Chromium: `npx playwright install chromium`.
- For mobile tools: KVM access, Android command-line tools, and an x86_64 Android system image.
- A valid Business Central account and MobileNAV licence/configuration for the environments you test.
Install the tagged GitHub release globally:
```bash
npm install --global github:Brad-Fullwood/bc-rig#v3.0.0
bc-rig init
bc-rig doctor
```
`bc-rig init` creates a commented config from `.env.example`. By default the config is
`~/.config/bc-rig/.env`; runtime data is kept under `~/.local/share/bc-rig`. Both locations can be
overridden—see [Configuration](#configuration).
To use the package without a global install, replace `bc-rig` with:
```bash
npx -y github:Brad-Fullwood/bc-rig#v3.0.0
```
## MCP configuration
The repository's `.mcp.json` is portable and contains no checkout-specific `cwd`:
```json
{
"mcpServers": {
"bc-rig": {
"command": "npx",
"args": ["-y", "github:Brad-Fullwood/bc-rig#v3.0.0"]
}
}
}
```
The CLI starts the MCP server when no subcommand is given. A global installation can instead use
`"command": "bc-rig-mcp"`.
The old checkout-relative form—`npx tsx src/mcp/server.ts` plus an absolute `cwd`—is deliberately
not used. Renaming or moving a source folder therefore cannot break the published MCP command.
## First run
Every browser is initialized from the AL project's
[`.vscode/launch.json`](https://learn.microsoft.com/dynamics365/business-central/dev-itpro/developer/devenv-json-launch-file);
there is no global tenant, environment, profile, or CDP setting. From an MCP client, start a session with:
```json
{
"operation": "start",
"launchJson": "/absolute/path/to/project/.vscode/launch.json",
"configuration": "Microsoft cloud sandbox"
}
```
`configuration` is optional and selects the first AL launch entry when omitted. Comments and trailing
commas are supported. Tenant and environment come from that entry; omitted fields use Business
Central's normal cloud defaults. A headed window opens for interactive sign-in and MFA.
The equivalent operator CLI is:
```bash
bc-rig browser start --launch /absolute/path/to/project/.vscode/launch.json \
--configuration "Microsoft cloud sandbox"
bc-rig browser list
bc-rig bc-check --session <session-id>
```
Every start creates a new random session ID, persistent profile, headed Chromium process, and
OS-assigned CDP port. Two agents—even against the same launch configuration—therefore cannot share
or redirect each other's tabs. Each MCP process binds its browser tools to the session it starts or
explicitly selects.
For MobileNAV:
```bash
bc-rig avd setup
bc-rig avd start
bc-rig mobile-install
bc-rig mobile-connect /path/to/environment.mnlc
```
The APK is downloaded from the vendor at setup time and checksum-verified. It is not redistributed
by this project. Microsoft/MobileNAV authentication may still require interactive user action.
## Efficient agent workflow
The MCP surface is built around project session → observe → referenced action:
1. Call `browser_session` with `operation=start` and the current project's `launch.json`.
2. Call `browser_observe` or `mobile_observe` in compact mode.
3. Act on the returned stable ref, such as `@b7a12f` or `@m20c9e`.
4. Read the changed state returned by `browser_act` / `mobile_act`; do not immediately observe again.
5. Use `query` to find page data that compact mode intentionally omitted.
6. When polling, pass the previous `rev` as `since`; unchanged state costs one short line.
7. Use full mode only for diagnosis. It is capped and explicitly marked as costly.
Refs are derived from role, accessible name, and occurrence in the existing ARIA/UI dump. Creating
them requires no extra Playwright/ADB lookup calls. Browser actions accept an exact accessible name
when a ref is unavailable, but the MCP surface does not expose raw CSS/Playwright selectors.
## MCP tools
| Tool | Purpose |
|---|---|
| `doctor` | Report host, Android, Playwright, and project-session readiness. |
| `browser_session` | Start/use/status/list/stop isolated browsers from AL launch configurations. |
| `browser_observe` | Return compact referenced state, focused query matches, or a capped full snapshot. |
| `browser_act` | Navigate/click/fill/press and return semantic changes or resulting compact state. |
| `browser_screenshot` | Save visual evidence only when text state is insufficient. |
| `mobile_observe` | Return compact referenced native state, optionally filtered or full. |
| `mobile_act` | Launch/stop/connect/tap/type/back/reset/wait and return resulting compact state. |
| `mobile_screenshot` | Save emulator visual evidence. |
| `run` | Start/status/annotate/finish an exploratory evidence manifest. |
Connections are lazy: browser tools do not boot Android, and mobile tools do not require a browser.
A mobile lease prevents two MCP processes from interleaving taps on the same emulator. Browser
sessions are horizontally isolated and support concurrent agents.
Compared with v1, the advertised interface remains compact despite adding browser concurrency:
9 tools / 5,563 JSON bytes versus 18 tools / 7,663 bytes. Page responses shrink further because
compact mode omits grid/text noise, actions return diffs, and unchanged polling returns only the
revision marker.
### Browser lifecycle change in v3
The singleton `BC_BASE_URL`, `CDP_URL`, and `BC_PROFILE_DIR` model was removed. Browser tools now
reject calls until `browser_session start` or `browser_session use` binds that MCP process to an
explicit project session. There is no implicit shared-browser fallback.
## Reports
Start and finish a run through MCP, then generate its report:
```bash
bc-rig handover --run latest
```
Manifests and screenshots live in `~/.local/share/bc-rig/artifacts` and generated reports in
`~/.local/share/bc-rig/handover` by default. The generator calls out read-only runs, missing client
surfaces, absent failure probes, and missing screenshots instead of overstating coverage.
## Configuration
Configuration precedence is:
1. Existing process environment variables.
2. `BC_RIG_CONFIG_FILE`.
3. `.env` in the process launch directory.
4. `~/.config/bc-rig/.env` (or `$XDG_CONFIG_HOME/bc-rig/.env`).
Important overrides:
| Variable | Default |
|---|---|
| `BC_RIG_DATA_DIR` | `$XDG_DATA_HOME/bc-rig` or `~/.local/share/bc-rig` |
| `BC_RIG_ARTIFACTS_DIR` | `<data>/artifacts` |
| `BC_RIG_HANDOVER_DIR` | `<data>/handover` |
| `ANDROID_HOME` / `ANDROID_SDK_ROOT` | `~/Android/Sdk` |
| `ANDROID_USER_HOME` | `~/.android` |
| `ANDROID_AVD_HOME` | `<ANDROID_USER_HOME>/avd` |
| `ANDROID_AVD_NAME` | `bcrig` |
Browser session records live under `<data>/browser-sessions`; isolated profiles live under
`<data>/browser-profiles/<session-id>`. Chromium chooses each CDP port dynamically.
Run `bc-rig paths` to see the resolved locations.
### Resource isolation
Browsers and emulators are the largest things bc-rig starts, and by default they would be charged
to whichever cgroup started them. When an agent drives bc-rig from a terminal, that is the
terminal's own cgroup: the browser's memory is accounted to the terminal, and anything that kills
the browser can take the terminal and the agent session with it. On top of that, every process in
a systemd user session inherits the same `oom_score_adj` (200), so under memory pressure the kernel
scores a 136 MB terminal and a 339 MB browser tab within a few percent of each other.
bc-rig therefore starts each browser and emulator in its own transient systemd scope
(`bc-rig-browser-<session-id>.scope`, `bc-rig-emulator-<avd>.scope`) and raises the process tree's
`oom_score_adj`, so the kernel reclaims the browser rather than the terminal. Hosts without systemd
fall back to spawning in the caller's cgroup.
| Variable | Default | Effect |
|---|---|---|
| `BC_RIG_SCOPE_CHILDREN` | on | `off` spawns in the caller's cgroup instead of a scope |
| `BC_RIG_OOM_SCORE_ADJ` | `900` | `oom_score_adj` for the tree; `off` disables the hint |
| `BC_RIG_BROWSER_MEMORY_HIGH` | unset | `MemoryHigh` on the session's scope, e.g. `4G` |
| `BC_RIG_BROWSER_MEMORY_MAX` | unset | `MemoryMax`; a breach OOM-kills inside the scope only |
| `BC_RIG_MAX_BROWSER_SESSIONS` | `3` | Concurrent live sessions; `off` removes the cap |
Sessions are never implicitly reused and nothing reclaims one an agent forgot to stop, so
`BC_RIG_MAX_BROWSER_SESSIONS` is what stops them accumulating. Inspect a running session's cgroup
with `systemctl --user status 'bc-rig-browser-*'`.
## Development
```bash
git clone https://github.com/Brad-Fullwood/bc-rig.git
cd bc-rig
npm ci
cp .env.example .env
npm run check
npm run mcp
```
`npm run check` compiles TypeScript, runs unit tests, performs a real MCP initialize/list-tools
handshake, and inspects the publish tarball. See [docs/architecture.md](docs/architecture.md) for the
design and [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidance.
## Security and privacy
Run artifacts can contain page text, company names, record data, screenshots, and target URLs.
Connection files can identify tenants and users. They are excluded from git, but you are responsible
for protecting the runtime data directory and reviewing every change before publishing. See
[SECURITY.md](SECURITY.md).
## Licence
[MIT](LICENSE). MobileNAV, Business Central, Android, Chromium, and Playwright retain their respective
licences and trademarks.
TDQS
Scored across 9 tools
Each tool has a clear, distinct purpose within its category (browser vs mobile vs utility). Browser tools cover session management, observation, action, and screenshot; mobile tools cover observation, action, and screenshot; doctor and run are unique utilities. No two tools appear to do the same thing.
Most tools follow a consistent [category]_[action] pattern (browser_*, mobile_*), which is clear and predictable. The doctor and run tools deviate slightly as they lack a category prefix, but they are still verb-named and not ambiguous. Overall, naming is mostly consistent with minor deviations.
With 9 tools, the server is well-scoped for its purpose: browser automation, mobile automation, and basic utilities. Each tool earns its place, and the number is neither too few nor too many for the apparent breadth of functionality.
The tool surface covers core browser and mobile interactions (observe, act, screenshot) and includes a readiness check (doctor) and manifest management (run). However, mobile session management is absent (no mobile_session tool), while browser has browser_session. This is a notable gap that could hinder mobile workflows.