Microsoft ships an MCP server inside every Business Central environment from version 26 onward. It listens on a Microsoft-hosted endpoint, validates your OAuth token, and exposes your configuration as a set of tools an AI client can call. That part is done for you.
What is not done for you is the last mile. The Business Central MCP server speaks HTTP — a streamable variant with Server-Sent Events. Claude Desktop, VS Code and Cursor run on your machine and speak stdio to their MCP servers. Something has to sit in between and translate, on your laptop, holding your tokens.
That something is bc-mcp-proxy. It is a fork of Microsoft’s own sample, it is MIT-licensed, and it lives at 360solutionsbe/bc-mcp-proxy.
Why it is public
Read that description again: a small program that runs on your machine, holds a token with access to your financial data, and forwards questions from a language model to your ERP. Customers, vendors, invoices, ledger entries.
That is precisely the category of software nobody should be asked to take on trust. If we shipped it as a closed binary, the honest answer to “what does it do with my data?” would be “trust us” — and there is no version of that answer we would accept from a supplier ourselves.
So the licence is not a marketing decision. It is the only arrangement under which we would be comfortable asking someone to install it.
What the fork actually adds
Microsoft’s sample is a sample. It demonstrates the protocol clearly, which is what a sample is for. Every addition below exists because something broke in real use.
Reconnect on transient upstream errors. The original gave up on the first httpx.ReadTimeout. In practice the connection to a Microsoft-hosted endpoint drops for entirely ordinary reasons, and when it did, the MCP client went with it. The fork retries with exponential backoff — 1s, 2s, 4s, 8s, 16s, five attempts by default — and, importantly, keeps the local stdio pipe to your client open while it does. From the client’s side the hiccup is invisible. A wrinkle worth knowing if you write your own: anyio wraps these in an ExceptionGroup, so matching on the bare exception type is not enough.
Pre-emptive token refresh. Access tokens expire, and Business Central’s response to a stale one is Authentication_InvalidCredentials — an error that reads like a permissions problem and sends you off auditing your app registration. The fork tracks each token’s expiry locally and asks MSAL for a fresh one once the remaining validity drops below a skew window, five minutes by default, rather than waiting to be rejected.
Surfacing errors that arrive disguised as successes. Some Business Central MCP responses come back with isError: false while the content is plainly an error — “Semantic search is not enabled”, or the authentication failure above. The client sees a successful call containing a sentence, and the model dutifully relays it as if it were data. The fork pattern-matches those and re-flags them as real MCP errors. This is the change we would most want in upstream.
Cold-start mitigation. The first tools/list against a cold BC environment can take more than thirty seconds. Claude Desktop’s MCP request timeout is hardcoded and shorter than that, so the very first thing a new user did was watch the integration fail. The fork answers from a three-tier cache — on-disk, then an in-memory pre-warm, then upstream — so the first call returns immediately and the slow path happens out of sight.
Startup validation of the base URL. BC_BASE_URL is checked before it reaches the network layer: the scheme must be https and the host must be under businesscentral.dynamics.com. There is an override for local mock testing, and it is off unless you set it.
Pinned transitive dependencies. Explicit security floors for h11, cryptography, pyjwt, starlette, urllib3 and others, closing sixteen CVE paths that Snyk flagged in the dependency trees of mcp, msal and httpx. None of those are our code. They are still our problem, because they arrive on your machine inside our bundle.
Ninety-five tests cover the parts most likely to rot quietly: error classification, backoff progression, the refresh-skew boundary, the masked-error patterns, endpoint detection, and the URL validation.
What we deliberately didn’t do
The list above is the easy half of the article. This is the half worth writing.
We didn’t call it production-ready. The README says, in as many words, that production fitness is your organisation’s call. It is MIT-licensed, actively maintained, tested and dependency-monitored — and it is a proxy holding credentials to your finance system. Those two facts do not add up to a blanket assurance, and issuing one would cost us nothing and cost you everything if it turned out to be wrong.
We didn’t hide the hard part. The genuinely difficult step is not installing the proxy; it is creating an Azure App Registration with the right permissions. Ten minutes for someone IT-comfortable, a wall for everyone else. It would be easy to write a quick-start that quietly skips past it and let people discover the wall on their own time. The README names it as the hard part in the first section.
We didn’t quietly dismiss the security scanner. Snyk reports four low-severity path-traversal findings and one medium SSRF finding against this repository. We reviewed them, concluded they are false positives, and — instead of just clicking Ignore — wrote down the reasoning for each one in SECURITY.md, with the file and line, so the next reviewer can disagree with the argument rather than re-derive it. A dismissed finding with no rationale is indistinguishable from one nobody looked at.
We didn’t stay quiet about the uncomfortable bit. The proxy sends your Business Central data to an AI provider. Which provider, and on which plan, determines whether that data can be retained or used for training. That is not our software’s behaviour, it is the AI vendor’s, and we could reasonably have left it out of our README. We put it in NOTICE.md instead, with a recommendation: for production use against a live tenant, a plan governed by commercial terms with a DPA available — which for anyone established in the EU is the difference between a defensible arrangement and an awkward conversation.
We didn’t ship an Intel macOS build. Apple Silicon has been the default since 2020. Intel Mac users can build from source; the script is in the repo. Shipping and testing a platform bundle for a shrinking audience is a maintenance cost that buys very little.
What we would do again
Forking rather than rewriting. The upstream sample is good code and the CLI surface is unchanged — every flag and environment variable from Microsoft’s version still works, which means anyone who started there can switch without relearning anything, and switch back just as easily.
And writing the reasoning down as we went. SECURITY.md and NOTICE.md took an afternoon between them. Both have already answered questions we would otherwise be answering by email, one customer at a time.