ToolJet was the first internal tool builder to go AI-native, but ToolJet MCP takes that idea somewhere much more interesting. Instead of stopping at “describe an app and generate one,” agents can now work directly inside ToolJet, creating and refining the same components, queries, data and workflows a human builder would use. The end result is still a proper ToolJet app, not a code dump that looks impressive on day one and turns into a maintenance problem six months later. That opens up a much bigger surface area: agents that can design, build, test and iterate on real internal tools, while everything remains inspectable and maintainable inside the platform.
So rather than test it on another toy dashboard, we gave it a workflow where things could actually go wrong.
Link to the ToolJet MCP GitHub repo: https://github.com/ToolJet/tooljet-mcp
Here's a sneak peek of the output:
Check out the public preview of the app: Shiproom
And here is how much it cost:
We Asked ToolJet MCP To Solve A Real Problem
Every team that ships software has some version of the same Thursday afternoon. The launch is two days away, QA says they are done, Legal may or may not have signed off, and someone remembers a blocker sitting in a Slack thread that nobody has opened since Tuesday.
The spreadsheet, naturally, is green, but nobody can confidently answer whether the launch is actually ready.
Shiproom is our attempt to fix that. It is a two-page internal app where teams can track launch checks, blockers and approvals, see a live readiness score, and eventually record the decision to ship or delay.
We started with one prompt that covered the whole application: what each page should do, how the data should behave, the rules around blockers and approvals, and the visual direction. We also asked for populated, loading, empty and error states up front, because otherwise those are exactly the screens that tend to get forgotten.
From there, the flow was straightforward: generate the design, build the underlying tables and logic, assemble the app, then check the result against the original design.
First, It Designed the App
The design direction was intentionally restrained. Shiproom uses cool greys, compact rows, a limited blue accent and status colours only where they are useful. Internal tools need hierarchy, enough density to be useful and just enough visual emphasis to make the important things obvious.
The first page is the launch list. It shows upcoming and recent launches, but the next launch gets most of the attention with a countdown, readiness score and the biggest blocker called out at the top.
The second page is the actual war room. It is divided into four sections: critical checks, blockers, approvals and the final ship decision. The idea was to keep the whole workflow visible without turning it into a miniature project management suite.
The design also covered the states that usually disappear from demo apps. We asked for populated, empty, loading and error states in the same prompt, so those were treated as part of the product rather than cleanup work after the happy path was finished.
The empty state still explains what the screen is for instead of simply displaying nothing. The loading state uses skeleton rows rather than pretending stale data is current, while the error state keeps the last successful values visible, marks them as stale and disables the signals that should no longer be trusted.
That last detail matters more than the skeletons. If the application cannot refresh its data, it probably should not confidently tell someone that a launch is ready to ship.
Then It Built the Data Model
The database did not exist beforehand either.
The app ended up with three tables. launches contains the basic launch information, launch_checks contains the things that need to happen before shipping, and launch_events records what actually happens along the way.
The interesting part is what is missing. There is no permanent status field that gets rewritten every time something changes, and there is no stored readiness percentage or is_blocked flag either. The current state is derived from the event history instead.
Completing a check creates an event. Recording an approval creates another. Opening or resolving a blocker does the same, and the final SHIP or DELAY decision is written into that same history.
For a launch application, that felt like the right model. A ship decision is exactly the sort of thing somebody may need to explain six weeks later, and constantly overwriting a status field throws away most of that context.
There was also a practical reason for using an append-only model. In our own testing, insert-style writes had behaved more predictably than some update and delete paths, so this architecture also kept us away from a class of problems we already knew could become awkward.
It is always convenient when the cleaner design is also the one least likely to ruin your afternoon.
From Design to a Working ToolJet App
Once the design and data model were in place, the agent built the actual application.
The final result contained:
| Metric | Result |
|---|---|
| Pages | 2 |
| Components | 94 |
| Queries | 12 |
| Repair cycles | 2 |
| Final validation | ✓ 0 errors |
Ninety-four components sounds slightly absurd until you open almost any internal application and remember how much of a UI is containers inside other containers.
What interested us more was what those 94 components became. They were still ToolJet components, sitting inside a normal ToolJet application with normal queries and bindings.
The agent did not generate a React project next to ToolJet and leave us with another repository to own afterwards. A human can open the application, inspect a query, change a component, alter a binding or continue building visually through the same platform.
That is probably my favourite part of the MCP approach. AI did the assembly without changing what the application fundamentally is.
This becomes much more interesting once you stop thinking about the first generated app and start thinking about the hundredth.
The Readiness Logic Stayed Simple
Shiproom calculates readiness from three things: required checks, approvals and blockers.
Required checks carry most of the score, approvals contribute another part, and unresolved critical blockers affect both the readiness calculation and the launch state.
There is no complicated model behind it, and there should not be. A readiness score does not need to discover a new branch of mathematics; it just needs everyone involved in the launch to be looking at the same definition of ready.
The checks are grouped into Product, Engineering and Go-to-Market, with ownership and required/optional status visible directly in the war room. As those checks change, the readiness score follows automatically.
A Critical Blocker Means Blocked
One rule we did not want the application to soften was the meaning of a critical blocker.
If an unresolved critical blocker exists, the launch becomes Blocked. The ship control remains unavailable until that blocker is resolved, the required checks are complete and the necessary approvals are in.
The final SHIP or DELAY decision can only be recorded by the launch owner. When that happens, the application stores the person, timestamp, decision and optional note as part of the permanent event history.
The modal also tells the user exactly why a ship decision is unavailable. One critical blocker unresolved, one required check still open or Legal approval still missing is much more useful than a disabled button with no explanation.
It is a small interface detail, but “you cannot ship” works considerably better when followed by “and here is why”.
We Added AI in One Place
Shiproom has one generative AI feature: a button called Give me the launch brief.
It produces a short summary that somebody can paste into Slack or another channel, but the model is not given the raw tables and asked to work out whether the launch is healthy.
The application calculates those facts itself first, including readiness, missing approvals, unfinished checks, blockers and current status. The model receives that prepared state and turns it into readable English.
The interface also shows what the generated summary was based on.
This is generally how we prefer AI features inside operational applications to behave. Normal application logic decides what is true, while the model deals with the part it is actually useful for: communicating that information clearly.
Then We Tried to Find Things That Were Wrong
The finished application passed static validation with zero errors, but we still checked it in the browser.
That turned out to be important.
A visual verification pass compared the running application against the original design and found that the loading state was wrong. Instead of showing the designed skeleton state, the app was effectively displaying a frozen table.
The application was structurally valid, but the finished screen was still wrong.
A repair pass corrected the issue, and another check confirmed the fix.
This is one of the clearer lessons from these builds. Static validation is good at structural problems, while opening the application and actually using it catches a different class of issues.
You probably want both.
One Thing Broke Along the Way
The build was not spotless, which made it much more useful.
Seven Queries at Once Was Too Ambitious
At one point the build tried to create seven ToolJet Database queries together.
Three persisted and four hit a Postgres deadlock.
Creating the remaining four individually worked immediately.
This is easy enough to repair in one build, but partial success is awkward for automation because the agent has to work out exactly what survived before it can safely retry anything.
For operations like this, slower and predictable beats fast and occasionally mysterious.
Small Query Changes Were Better Handled by Low-Code
We also ran into cases where changing a tiny part of a large query meant retransmitting the entire query body.
It worked. But since ToolJet is a low-code platform suitable for business users, I'd prefer changing certain things manually in seconds instead of relying on MCP all the time.
Then the Browser Found the Real Bug
This was the most useful failure in the entire build.
A blocker has an owner, and the application was accidentally deriving that owner from the person responsible for the latest blocker event.
At first glance, that sounds reasonable. In practice, it meant Maya could open a critical blocker, Dev could add a one-line status update, and the blocker would quietly become Dev's.
Nothing crashed. The layout looked fine. Validation still returned zero errors.
The browser QA pass caught it because it performed the actual workflow and then read the result back.
The fix itself was tiny. Ownership needed to come from the original blocker_open event rather than whichever blocker event happened most recently.
This is exactly the sort of bug that makes a green validator slightly less comforting. The application structure was correct, but the application behaviour was not.
What We Took Away From It
The surprising part was not that MCP could create 94 components and twelve queries. We expected it to be good at assembly.
What changed was how little of that assembly needed to involve us.
The harder decisions remained exactly where they were before. Someone still had to decide whether a critical blocker should merely produce a warning or completely prevent shipping, how readiness should be calculated, who should be allowed to record the final decision and whether the AI brief should work from raw data or facts the application had already derived.
Someone also still had to notice that posting a blocker update had quietly reassigned its owner.
Those are application-design problems rather than component-building problems, and MCP does not make them disappear.
What it removes is a large amount of the mechanical work around them.
That is also why we are more interested in AI working through ToolJet than in simply generating another codebase. Low-code became useful because every internal application did not need to become its own small software project, and AI should push that abstraction further rather than undo it.
With MCP, the agent can design the application, create its tables, build its queries, wire the interactions and check the finished result, while what gets left behind is still a ToolJet app that somebody else can open and understand.
There are still rough edges. We hit query deadlocks, noisy linting, inefficient updates, a naming collision and one genuine logic bug that static validation missed.
That is probably a more useful result than a flawless demo.
The distance between describing an internal workflow and seeing a working version of it inside the workspace is getting very small.
You still have to know what you want the application to do.
Thankfully, MCP has not automated that part yet.











Top comments (0)