@365businessdev/albuild-mcp
v0.4.27696
Published
ALbuild MCP server - exposes Business Central container/runtime-validation tools (provision, deploy, test) to LLM agents over stdio and streamable HTTP.
Maintainers
Readme
ALbuild MCP server
An MCP server that exposes ALbuild's Business Central container / runtime-validation capabilities to LLM agents (OpenClaw 365 local agents, Claude Code CLI/VSCode on any OS). It closes the gap between "it compiles" (the AL MCP's job) and "it actually runs and the tests pass against a specific BC version".
Built on the businessdev.ALbuild PowerShell module — every tool is a thin, typed wrapper over an
existing cmdlet.
Why
A fast inner loop: provision or reuse a warm BC container and iterate deploy → test in well
under two minutes, versus 15+ minutes for a full CI pipeline. CI remains the authoritative gate; this
is the development loop before it. Container ops are Windows + Docker only — so the remote HTTP
deployment is the only way a Mac developer/agent gets real BC runtime validation.
Transports
One binary, two transports (chosen at start):
| Transport | Start | Use |
|---|---|---|
| stdio | node dist/index.js (default) | local Claude Code on a Windows box with Docker |
| Streamable HTTP | node dist/index.js --http | shared Windows Server; OpenClaw agents + Mac Claude Code over LAN/Tailscale |
Auth is opt-in: with no ALBUILD_MCP_TOKENS configured the HTTP endpoint is open and
accepts every request, scoped to the default agent; set tokens to require a per-agent bearer token.
It binds to 127.0.0.1 by default (LAN/Tailscale-only — never expose it publicly without mTLS + an
IP allowlist; on an open, non-loopback host, anyone who can reach the port has full access).
New here? Follow the bundled
INSTALL.md(included in this package) — a step-by-step guide for the local (stdio) and remote (HTTP) setups, including the AI-client config snippets.
Build & run
npm install
npm run build
albuild-mcp --check # preflight: verifies PowerShell + businessdev.ALbuild module + Docker
# local stdio
node dist/index.js
# shared HTTP host
ALBUILD_MCP_TOKENS='{"<token-agent1>":"agent1","<token-agent2>":"agent2"}' node dist/index.js --httpOn start the server runs the same preflight as a hard gate and refuses to start if PowerShell or the
module is missing (bypass with ALBUILD_MCP_SKIP_PREFLIGHT=1). Drop the bundled templates/ai/CLAUDE.md
(included in this package) into your repo so the assistant follows the right workflow.
Tests
npm run build
node dist/test/smoke.js # fast: protocol + read-only tools + governance gate (no container)
npm run live-test # slow: creates a real BC 28.2/de container, inspects it, removes itConfiguration (environment)
| Variable | Default | Purpose |
|---|---|---|
| ALBUILD_MODULE_PATH | repo path else installed module | The PowerShell module to import. |
| ALBUILD_POWERSHELL | pwsh (falls back to powershell) | PowerShell executable. |
| ALBUILD_DOCKER | docker | Docker executable. |
| ALBUILD_MAX_WARM_CONTAINERS | 2 | Pool size cap (LRU eviction beyond it). RAM-bound. |
| ALBUILD_CONTAINER_MEMORY | 8G | Memory per container; ceiling for a tool's memoryLimit request. |
| ALBUILD_DEFAULT_ARTIFACT_TYPE | Sandbox | Fallback artifact type. |
| ALBUILD_ALLOW_SELF_CLEANUP | true | Agent may remove containers it created without approval. |
| ALBUILD_POOL_STATE | %LOCALAPPDATA%/albuild-mcp/pool.json | Pool registry file. |
| ALBUILD_MCP_HTTP_HOST | 127.0.0.1 | HTTP bind host. |
| ALBUILD_MCP_PORT | 5020 | HTTP port (OpenClaw reserves 5010–5099). |
| ALBUILD_MCP_TOKENS | (none) | JSON { "<token>": "<agentId>" } for HTTP bearer auth. Unset ⇒ open: every request is accepted as the default agent. |
| ALBUILD_MCP_AGENT | claude-code | Agent identity for the stdio transport (scopes the toolset). |
| ALBUILD_MCP_AGENT_TOOLSETS | (none) | Inline JSON { "<agent>": ["tool", …] } mapping agents to allowed tools. |
| ALBUILD_MCP_AGENTS_FILE | (none) | Path to a JSON file with the same shape (see examples/agent-toolsets.example.json). |
Tools
Read-only are synchronous; mutating/long tools return a jobId (poll get-job). A direct MCP client
already sees this async shape — start the tool, then poll get-job (and cancel-job to abort). The
albuild CLI blocks by default (it polls for you); pass albuild … --no-wait to get the same
start-and-poll-yourself flow from the CLI (see the CLI README's "Async / non-blocking" section).
| Tool | Tier | Notes |
|---|---|---|
| list-containers | read-only | docker state + pool metadata |
| inspect-container | read-only | + installed apps + reachable web-client URL/protocol/credentials |
| find-artifact | read-only | resolve a BC artifact URL |
| get-build-order | read-only | multi-project dependency order |
| resolve-dependencies-plan | read-only | dry-run dependency resolution |
| ensure-container | mutating (job) | warm-reuse or provision; optional dependency install; optional http + reachability:'transparent' for a LAN-reachable HTTP container; optional language (e.g. de-DE) for the UI language |
| restart-container | mutating (job) | |
| remove-container | destructive | self-created → runs; shared → approval_required |
| reconcile-dependencies | read-only | manifest vs container vs feeds → satisfied / feed-available / missing |
| publish-app | mutating (job) | upload a built .app (base64) and publish it — the remote file transport |
| deploy-and-test | mutating (job) | appFolder (local) or apps:[{name,base64}] (remote) → publish clean → run tests |
| run-tests | mutating (job) | re-run tests without redeploy |
| get-job / cancel-job | read-only | async job control |
LAN-reachable HTTP container (for a browser agent)
By default a container is on Docker's NAT and serves the Web Client over the image's self-signed HTTPS —
reachable only on the Docker host. To let an external browser agent (e.g. OpenClaw's Playwright on
another host) open the Web Client and screenshot pages, ensure-container takes two opt-in inputs:
http: true— serve the Web Client over plain HTTP (image envuseSSL=N); no self-signed-cert problem, NavUserPassword still works.reachability: "transparent"— attach to a transparent Docker network so the container gets its own LAN IP via DHCP (reachable from other hosts; no port publishing/firewall). On a Hyper-V VM host, enableMACAddressSpoofingon the vNIC.
ensure-container and inspect-container then return the IP-based webClientUrl (e.g.
http://<lan-ip>/BC/?tenant=default), protocol, ipAddress and the NavUserPassword credentials —
everything the browser agent needs. The URL is re-resolved live on warm reuse (a transparent DHCP IP can
change). The IP is read from docker inspect, with a docker exec fallback that reads the DHCP-assigned
IP from inside the container — Docker's NetworkSettings IP is empty for a transparent network on Windows,
so this fallback is what keeps ipAddress/webClientUrl populated on a transparent container.
Dev/Sandbox + trusted LAN only. HTTP + NavUserPassword sends credentials in clear text. The default (not exposed, HTTPS) is unchanged, and
remove-containerstays approval-gated.
UI language
ensure-container takes an optional language (a culture name, e.g. de-DE; default en-US). A
non-default language sets the instance's DefaultLanguage and pins SupportedLanguages to it, so the
web client shows that language even when the browser sends a different Accept-Language (a headless
Chromium usually sends en-US). Setting it restarts the service tier (the keys are not dynamically
updatable), so it is skipped for the en-US default.
The language module must be installed on the instance — use a matching country artifact (e.g.
country: "de" ships de-DE; w1 does not). If it is not installed the container is still created and
usable (it stays en-US with a warning; language comes back null) rather than being orphaned.
ensure-container/inspect-container return the applied language and supportedLanguages. language
is part of the warm-reuse key, so a de-DE request never reuses an en-US container.
Pool, warm-reuse & cleanup
Every container ensure-container provisions is registered in the server's pool and reported as
managedByMcp: true by list-containers. Warm-reuse matches on country + artifactType + bcVersion
protocol+reachability+language; a second identicalensure-containercall returns the same container (reused: true,path: "fast"). The pool entry is written as soon as the container is created — before the best-effort URL resolution — so a reachability hiccup can never leave a healthy container unmanaged. An agent mayremove-containera container it created without approval (allowSelfCleanup); a failed provisioning removes its partial container automatically. Secrets never appear in error messages or logs (docker runcommand lines are redacted).
Remote app/dependency flow
When the agent is not on the MCP host (e.g. OpenClaw/Claude Code on a Mac → Windows MCP host), a filesystem path is meaningless on the host, so:
reconcile-dependencies— send theapp.jsondependencies and the merged (app + workspace)albuild.jsonfeeds (the host can't read your repo). Private feeds carry their API key infeeds[].token— the host doesn't have these credentials, so the agent supplies them (kept out of logs, passed to PowerShell via a child env var). Get back what's satisfied (in the container, incl. Microsoft apps), availableFromFeed, and missing.publish-app/deploy-and-test(upload form) — send the missing.apps (and the app + test app) as base64; the server stages them on the host and publishes. Tests run bytestExtensionId.
Per-agent tool scoping
Each agent (by stdio ALBUILD_MCP_AGENT or HTTP token) sees only its allowed tools — a correctness
requirement for small local models. The mapping is configurable, not baked in: set
ALBUILD_MCP_AGENT_TOOLSETS (inline JSON) or ALBUILD_MCP_AGENTS_FILE (a JSON file) to
{ "<agent>": ["tool", …] }, where ["*"] means all tools and a "*" agent key is the catch-all.
- Default (no config): permissive — every agent gets every tool (
{ "*": ["*"] }), so the package works out of the box. - With a config that has no
"*"key: deny-by-default — an unlisted agent gets no tools.
See examples/agent-toolsets.example.json for a ready roster
(e.g. a coding agent, an ops agent, read-only architecture/DevOps agents, and a full claude-code).
Governance
- Mutating tools run as logged async jobs.
remove-container(and other destructive/external actions) on shared/pre-existing resources return{ status: "approval_required" }for the OpenClaw Escalation Router instead of acting.- The host's own secrets (its PAT, signing cert) live on the host as the service identity. Per-feed API
keys, which the host does not have, are sent by the agent (
reconcile-dependenciesfeeds[].token), kept out of logs and passed to PowerShell via a child env var (never inline). Relies on the authenticated, LAN/Tailscale-only channel.
