IceOnlys Blog

All Around Microsoft Dynamics Business Central

BusinessCraft: Can AI Rebuild Business Central in Rust?

After seeing SolidCraft and PhotoCraft, I kept asking myself the same question:

If AI can rebuild those products using a clean-room approach, could it do the same for Business Central?

Not a fork. Not a decompiled copy. A real clean-room reimplementation: BusinessCraft.

So I tried it.

The setup

The setup was almost embarrassingly simple:

  • A new, empty repository
  • Rust and Vue.js as the tech stack
  • Cursor, instructed to approach it the same way as the PhotoCraft repo
  • A fresh BC29 container (bc29de), and the instruction: "You can use this."

The first goal was not the whole product. It was the part everyone sees first: the web client.

The journey begins

What happened next surprised me.

The agent did not start by writing Vue components. It started by doing what a good reverse engineer would do: observe.

It deployed a proxy inside the container and asked me to record a few scenarios in the browser with Playwright:

  • Navigate to the customer list and open a customer card
  • Create a new customer, then delete it
  • Use a lookup and a filter
  • Create and post a sales order

That was my part. A few clicks, a few recordings.

Then a cloud job ran for about an hour and rebuilt the Project Madeira experience from scratch in Vue.js and Rust.

Here is a short walkthrough of the web client v0.1:

Clean room, not copy & paste

The rules are written down in the repository, and the agent has to follow them:

  1. Observe, then specify. Raw captures (HAR files, WebSocket frames, SQL schema dumps) stay in a local re-artifacts/ folder that is git-ignored and never committed.
  2. Write findings in your own words. Every observation becomes a numbered finding such as F-PROTO-002 or F-DB-001. Findings describe interface facts, for example message names, field names on the wire, or table naming rules. They never contain code, AL source, captions, or data.
  3. Implement from findings, not from captures. The code references the finding ID in the commit message.
  4. No Microsoft code or assets. No decompiled service tier, no JavaScript, CSS, icons, or fonts from the web client, and no .app or symbol files in the repo.
  5. Invented data only. No CRONUS, not even as a template.

One piece could not be observed from the outside: how the web server protects the password it sends to the service tier. That part was reverse engineered in a separate session, and only the resulting specification, written in plain language, was fed into the project. No key material ends up in the repository.

After two days, the findings document has about 30 entries covering the protocol, navigation, forms, the database schema, and authentication.

What the agent found out

Some of the findings are interesting even if you never plan to build a BC clone.

The browser does not talk to the NST. The browser opens one WebSocket to the web server at /BC/csh and sends JSON-RPC 2.0 messages (OpenSession, Invoke). The web server runs the logical client, which manages forms, the control tree, and change tracking. It then talks to the NST over a second WebSocket at client/ws/connect on the client services port (7046).

browser ──WebSocket /csh, JSON-RPC──▶ web server (logical client)
web server ──WebSocket client/ws/connect, JSON-RPC──▶ NST
web server ──HTTP client/metadata/...──▶ NST

Answers are gzipped handler lists. Responses from the web server do not contain plain JSON. They contain a compressedResult field: base64-encoded gzip data holding an array of handlers such as LogicalClientChangeHandler or LogicalClientEventRaisingHandler. Most of the data arrives later as push notifications.

The NST API is record-based, not form-based. The interface between the web server and the NST has about 90 methods, including OpenForm, GetPage, ValidateField, InsertRecord, ModifyRecord, and InvokeApplicationMethod. Forms, control paths, and the PendingUpdates counter are web server concepts. The NST only deals with records and triggers.

Confirm is a callback. When AL code calls Confirm or StrMenu, the NST sends a JSON-RPC request back to the web server while the original call is still pending. The web server shows a dialog, waits for the user, and resumes the call with EndClientCall.

If you read my post about Confirm keeping transactions open, this is that mechanism at the protocol level.

Record data is binary. Rows are sent as a binary NavDataSet wrapped in JSON as {compressed, count, buffers}.

Validation errors keep your input. When a field fails validation, BC does not revert the value. It keeps the entered text, marks the field and all parent controls with ValidationResults, and disables most other controls until you fix the problem.

BusinessCraft behaves the same way. The invalid country code stays in the field, and the error dialog appears:

BusinessCraft customer card with a validation error

The architecture

BusinessCraft mirrors the BC split so each part can be replaced independently, or combined with the original BC components:

Business Central BusinessCraft
Web client web/client (Vue 3, TypeScript, Pinia)
Web server craft-web (Rust, axum)
Service tier (NST) craft-server + craft-al (planned)
SQL Server database craft-db-* (memory today, BC-compatible SQL naming)

It is a single Cargo workspace with 14 crates. The protocol is defined once in Rust, and ts-rs generates the TypeScript types for the client, so client and server cannot drift apart without a compile error.

Today the project has roughly 17,000 lines of Rust and 5,000 lines of Vue/TypeScript in 33 commits.

Three backends for one web client

The key design decision is a ClientBackend trait in the web server. The Vue client does not know which backend it is talking to. Right now there are three:

                                  ┌─▶ mock backend ──▶ JSON fixtures
Vue web client ──▶ craft-web ─────┼─▶ bc backend   ──▶ original BC web server (/BC/csh)
                                  └─▶ nst backend  ──▶ BC29 service tier (port 7046)

1. Mock backend (craft-mock) This backend renders pages from JSON metadata and an invented demo company. It handles validation, table relations, FlowFields, number series, delayed insert, AutoSplitKey, confirm dialogs, lookups, and drill-downs declaratively. It was enough to build and test the client without BC.

The customer list with search, a filter on the city column, and the FactBox:

BusinessCraft customer list with search and filter pane

A sales order with editable lines and calculated amounts:

BusinessCraft sales order with lines

2. BC backend (craft-bc-client) This backend signs in to the original BC web server, gets the CSRF token, opens the /csh WebSocket, and speaks the original web client protocol. It unpacks the gzipped handler lists, maintains its own copy of the control tree, and maps the result to BusinessCraft forms.

In practice, the Vue client is acting as the original BC web client.

3. NST backend (craft-nst-client) This is the one that impressed me most. It replaces the BC web server and talks directly to the BC29 service tier: JSON-RPC over client/ws/connect, page metadata over HTTP (pagewithdependencies), and the binary NavDataSet for record data. It also handles the NST callbacks while a request is pending.

Sign-in, opening a company, the role center, list pages, cards, filtering, and search already work.

In other words, the BC service tier is now serving a web client that Microsoft did not write.

The bill

Based on token usage, getting this far cost about €50 in Opus 5.5 API usage.

For me, it was a few hours of time: setting up the container, recording scenarios, reviewing, and steering.

Let's be honest about the limitations:

  • Only a few page types are supported so far: role center, list, card, document, FactBoxes, and dialogs
  • Many features are still missing
  • Editing, lookups, and dialogs do not yet work through the NST backend
  • It is a prototype, not a product

Still, for a few hours and €50, this is much further along than I expected.

Next week: the NST

Now comes the hard part.

The web client was mostly a protocol and UI problem. The service tier is the core of Business Central: AL runtime, triggers, events and subscribers, the record API, FlowFields, Confirm with suspension, sessions, and permissions, all on top of a database schema that should stay data compatible with a real BC database.

The plan is to write the AL front end in Rust, compile tables and pages into BusinessCraft's metadata model, and run the code in a Rust-based runtime instead of .NET.

AL on Rust instead of .NET.

Will that work? I honestly don't know yet. It will be a tough one.

I'll keep you updated. :)


BusinessCraft is not affiliated with or endorsed by Microsoft. "Microsoft", "Dynamics 365" and "Business Central" are trademarks of Microsoft Corporation.