Inspiration

Every local business website has the same dead end: a visitor that needs immediate help, and a contact form that answers in "1-2 business days". The visitor wants to talk to someone, right now.

An AI agent browsing that page hits the same wall. It can read the pricing and summarize the docs, but the moment its user says "I just need to talk to a human about this", the best it can do is fill in the form and hope.

We build a phone system for a living, so we already had the missing piece - a live view of who is on shift and who is mid-call, and the ability to ring them. What we had no way to do was hand that to an agent standing on a customer's website. WebMCP is exactly that missing handle.

What it does

One script tag on any website registers two WebMCP tools:

Tool What it does
callingly-check-sales-availability Read-only. Is the business open right now, is a sales rep actually free (not on another call, past their cooldown), when is the next opening, and are call requests being accepted at all.
callingly-request-sales-call Asks Callingly to call the visitor on the number the agent passes, and to connect them to the company.

What happens on the second one is the part worth watching. Callingly's AI sales rep calls the visitor within seconds and starts qualifying - and at the same time it dials the company's reps in parallel. The moment a human picks up, the AI hands the caller over: "Ok great, let me get you on the line with one of our specialists." No hold music, no callback queue, and no dead air while the humans are being rung.

So the agent can negotiate the timing of a human conversation: "They're free now, want me to get someone on the line?" / "Nobody until 9am Tuesday - shall I have them call you then?"

That is not a lookup and it is not a booking link. It is a live check against who is on shift, who is mid-call, and what the team's calling hours are, turned into an offer the agent can actually make good on. A contact form cannot have that conversation. A scheduling widget offers slots without knowing whether anyone is at their desk. A chatbot can promise a callback with no authority to cause one.

Concretely: a visitor asks an agent about a burst pipe at 8:47pm. Seconds later their phone rings, an AI rep is asking what's wrong, and by the time they've finished describing it a plumber is on the line - instead of a form submission someone reads on Monday.

This is not a mock. The dialer, the routing rules, the availability logic and the reps are Callingly's production system, the same one paying customers use. The demo places real phone calls.

How we built it

webmcp.js is a dependency-free ES5 IIFE, 516 lines, no build step. It:

  • registers both tools through document.modelContext.registerTool(tool, { signal }), falling back to navigator.modelContext for older origin-trial builds;
  • polls for the model context rather than assuming one at load;
  • unregisters through an AbortController, since WebMCP has no unregisterTool;
  • returns MCP content results and uses isError for refusals, so the agent gets a sentence it can act on rather than an exception;
  • ships annotations: { readOnlyHint: true } on the availability tool and an inputSchema on both - required by ChatGPT's implementation;
  • exposes the same functions on window.callingly, so ordinary page code, or an extension that doesn't speak WebMCP yet, can call them.

Server-side is two public endpoints keyed by a webhook integration's slug: GET /embed/v1/{key}/availability and POST /embed/v1/{key}/calls. Accepted calls go into the same queued pipeline every webhook lead goes through - routing rules, de-duplication, then dialling - so the endpoint answers 202 rather than pretending to know which rep will pick up.

Challenges we ran into

Registration is not fire-and-forget. Agent browsers install document.modelContext at wildly different points. A context can arrive long after load, be replaced after you have already registered against it, or accept a tool and then reject it after your retry window has closed.

Our own debug console lied to us - twice. It reported "no WebMCP in this browser" for every failure, including causes it had never checked. Then, once it did report honestly, it turned out ChatGPT hands out a model context whose registerTool cannot be replaced: our instrumentation's assignment failed silently, nothing reached our wrapper, and the console concluded registration had failed while both tools were in fact live. It now reads the snippet's own state instead of trusting its own instrumentation, and names which of four distinct causes it actually found.

You have to convince ChatGPT to use the WebMCP when its on the site, it doesn't check for it automatically. We assume that in the future this will be the first thing that it checks for.

Accomplishments that we're proud of

The integration really is one line, and the before/after pair in the repo is a one-line diff - nothing else on the plumber's page changes. The contact form still promises 1-2 business days, because the human path is not what this replaces.

It is wired to production, not a demo backend. And the failure paths got as much care as the happy one: outside calling hours the agent gets a real next-opening time and can offer it, rather than a dead end.

What we learned

WebMCP's is a very young technology and working with it is tricky the same way as working with any new tech, its not fully supported, people don't know what it is, even the AI agents themselves have to be coerced into using it.

But it has a ton of potential, particularly in our space. In the future, AI agents will be doing the shopping around for consumers and our customers (the consumer businesses) will need to have AI sales reps of their own to deal with the AI agent shoppers.

What's next

  • More actions. The backend already schedules callbacks for a named time, so "have them call me at 9am Tuesday" is a natural third tool - right now the agent can only ask for a call now.
  • Ship it to customers. The snippet is MIT and the endpoints are live, so any Callingly account can paste it on their own site today.

Built With

Share this project:

Updates

Submission history