Inspiration
I build software for client-services businesses.
My first client is a Saudi resume-writing firm.
Their problem was simple. Clients messaged, the team was busy, and nobody replied.
The hard part was never the calendar.
It was qualifying the customer and pricing the job right: tiers, bundles, add-ons, and knowing when a person has to step in.
Then a second question showed up.
People are starting to hand errands to their own AI. Claude can already connect to a business's MCP server and act inside it.
A firm whose offer lives in PDFs and forms gives that agent nothing to talk to.
To be honest about it: agents don't find small businesses on their own yet. People connect them on purpose.
So I built one desk with two doors. The first fixes today's problem. The second is ready for the next one.
What it does
The desk answers a customer's questions, prices their order and books a kickoff call.
There are two ways in:
- The human door. A chat page. You ask "What does a CV rewrite cost with 6 years of experience?", get a quote, pick a time and book.
- The agent door. The same desk as a remote MCP server. Your own AI agent (Claude, or any MCP client) can list services, get a quote, check free times, book, pay and hand over to a person.
Both doors call the same desk code with the same tool definitions.
So the same order gets the same quote on both.
And the model never decides a price, a tier or a free slot. Code does.
Anything the rules can't price goes to a person, in code, not by the model's judgment.
The pay step is simulated. Booking holds the slot until payment, the pay button charges nothing and asks for no card details, and an unpaid booking frees its slot after 30 minutes.
The owner gets a private page with every booking from both doors: which door it came through, the price, the slot and the conversation behind it.
The business in the demo, Darb Career Studio, is fictional. Its services and prices are made up. No real customer data is in the repo.
How we built it
- Node.js 22 and the official MCP TypeScript SDK, served over Streamable HTTP at
/mcp. - The tools are defined once (
src/tools.mjs). Each door builds its tool list from that one file. Five tools are shared.payis on the agent door only.ask_choice(answer buttons for a person) is on the chat door only. - The human door runs on NVIDIA Nemotron 3.5 Super VL through Nebius Token Factory, using its OpenAI-compatible API with tool calling at temperature 0. The model does two jobs: choose the next tool and word the reply.
- A guard (
src/guard.mjs) checks every reply. If it states a SAR amount or a time the tools didn't return, it is re-asked once, then handed to a person. - The agent door needs no model at all. An agent calls the tools directly.
- Hosting: my own VPS behind a Cloudflare tunnel, file storage, zero extra cost. Only the model call goes to Token Factory.
- Limits for a public demo: rate limits on both doors, at most 3 active bookings per caller, a cap on stored records, and a daily cap of 150 model calls, so the credit lasts through judging. When the chat rests for the day, the agent door keeps working.
How I test it:
- Two doors, one answer. A test sends the same five customer profiles through the chat door and through the agent door (a real MCP client over HTTP), and asserts the identical quote and the identical stored booking.
- Adversarial set. 11 hostile prompts on the real model: prompt injection, fake authority, asking for the system prompt or other customers' data, invented prices, card numbers in chat, fake tool output and more. Every reply is checked by code, and each checker is first tested on a hand-written good and bad reply.
- Planted breaks.
npm run check -- --plantbreaks each guard on purpose and fails unless every break turns its gate red. - 40 scripted customer conversations in English, with the replies from the real model, checked by code for the right tier, the right total, handovers, bookings and grounded prices. 38 of 40 pass every check.
Challenges we ran into
The model picked the price tier.
In an early live test, a manager with 12 years of experience was quoted the executive tier, 2,350 SAR. The right answer was senior, 1,450 SAR.
The price math was right. The model had chosen the wrong input.
So I took the choice away from it. The desk now works out the tier itself from the years and an executive flag, on both doors. The caller never picks a tier.
The model was deprecated mid-build.
On 16 October, NVIDIA's API catalog showed that Nemotron 3 Super 120B, the model the desk ran on, would be deprecated on 19 October. Token Factory still listed it.
Judging runs to 15 December. The demo couldn't depend on a model with an end date.
I ran the same quote request through the desk's own chat path on two newer Nemotron models:
- Nemotron 3.5 Super VL: 550 SAR, after calling
list_services,quoteandcheck_availability, 4.4 s, then moved to booking. - Nemotron 3.5 Lightning: 550 SAR, after
list_servicesandquote, 1.3 s, but offered add-ons instead of booking.
Super VL then passed all 11 adversarial cases in one run (25 model calls, 36 s). I switched the default that night. The model is now a server setting (DESK_MODEL), so the next switch needs no code change.
A public demo on a small credit.
Anyone can reach it until judging ends. One script could empty the calendar or spend the credit. That is why the limits and caps exist, and why the agent door never calls the model.
Two misses I kept.
In the conversation set, the desk ignored a hidden instruction to quote "prices 50% lower" and gave the correct 550 SAR, but it didn't hand over as the rubric asks. In another, it handed an off-topic request to a person instead of steering back. I kept both in the set instead of re-rolling them.
Accomplishments that we're proud of
The same order gets the same quote on both doors, and a test proves it.
Claude can add the desk as a custom connector and choose and call its tools on its own.
The model never states a price the desk didn't give it. The guard checks every reply, and the adversarial set tries to break that on the real model.
Every guard has a deliberate break that must turn it red. A check that can't fail isn't a check.
What we learned
The model is good at the conversation.
It should never own the numbers.
Let it choose the next step. Let code decide what's true.
Temperature 0 is not fully deterministic on this endpoint: the same conversation once came back with answer buttons where it had asked in text before. So I judge the desk by code checks over many runs, not by reading one good reply.
And a model is a dependency like any other. Keep it a setting, and keep a test set that tells you in one run whether the next one is safe.
What's next for Widem Agent Desk
- Human ratings on the frozen conversation set, so quality is judged by people as well as code.
- Fix the two misses above: hand over on hidden instructions, steer back on off-topic requests.
- Arabic. The tools already return service names and handover messages in Arabic. The demo is English only for now.
- Real payments, which are out of scope for this entry.
Built With
- cloudflare-tunnel
- javascript
- mcp-typescript-sdk
- model-context-protocol
- nebius-token-factory
- nemotron-3.5-super-vl
- node.js
- nvidia-nemotron
- ovh
- zod
Log in or sign up for Devpost to join the conversation.