Frequently Asked Questions

WebMCP Technical Integration & Best Practices

What is WebMCP and how does it enable AI agents to interact with websites?

WebMCP is a standard proposed by Google that allows AI agents to interact with websites through structured tools, rather than by reverse-engineering user interfaces. It provides both declarative and imperative APIs, enabling websites to expose their capabilities directly to AI agents for faster, more reliable, and precise interactions. [Source]

Why does my AI agent get 'Message sent' instead of the real answer when using WebMCP?

This happens when the WebMCP tool's execute() function returns immediately after sending a message, rather than waiting for the actual response. The correct approach is to return a Promise that resolves only when the real answer is received, ensuring the AI agent gets the intended response. [Source]

What is the correct way to handle asynchronous responses in a WebMCP tool?

The correct way is to have the execute() function return a Promise that resolves with the actual answer, not just an acknowledgment. This ensures the AI agent waits for the full response, even if it takes several seconds. [Source]

How do I implement a WebMCP tool that works with a chat widget inside an iframe?

To handle cross-origin iframes, use window.postMessage to send the question from the parent page to the iframe. The parent page's execute() function should return a Promise that resolves when the iframe posts the answer back, using a unique callId for correlation. [Source]

What are the three rules for building reliable WebMCP tools?

The three rules are: 1) Always return a Promise from execute(); 2) Never reject the Promise—resolve with an error message instead; 3) Always set a timeout to avoid hanging indefinitely if a response never arrives. [Source]

How does the WebMCP Gateway ensure AI agents receive actual answers from websites?

The WebMCP Gateway uses a headless browser to call the tool's execute() function and waits for the Promise to resolve, ensuring the AI agent receives the real answer, not just a confirmation. [Source]

Can you provide a production-ready example of a WebMCP integration?

Yes, a production-ready script is provided in the blog post. It registers an ask_question tool, handles async responses, and works with both Chrome's native WebMCP and the WebMCP Gateway. See the full code example in the original article. [Source]

Why is a unique callId important in WebMCP async patterns?

A unique callId ensures that concurrent requests are correctly matched to their responses, preventing one agent from receiving another's answer. This is critical for reliability in multi-agent or multi-request scenarios. [Source]

How does WebMCP handle widgets that need to be opened before sending messages?

For widgets that start hidden, the execute() function should check if the widget is open, open it if necessary, and only send the message once the widget is ready. This ensures the question is delivered and the response is received reliably. [Source]

What are common mistakes when implementing WebMCP tools?

Common mistakes include returning immediately from execute() without waiting for the answer, not handling errors gracefully, and failing to set a timeout, which can cause the AI agent to hang indefinitely. [Source]

How does the WebMCP Gateway parse responses from website tools?

The Gateway waits for the Promise from execute() to resolve, then parses the result. If the result is a string, it uses that as the answer; if it's in MCP format, it extracts the text; otherwise, it converts the object to a JSON string. [Source]

What is the long-term vision for the WebMCP Gateway?

The long-term vision is for every website to function as an API, allowing AI agents to gather and compare information across sites efficiently, such as comparing vendor pricing or policies in real time. [Source]

How can I ensure my WebMCP tool doesn't hang indefinitely?

Always set a timeout in your execute() function so that if a response is not received within a reasonable period (e.g., 60 seconds), the Promise resolves with an error message, preventing indefinite hangs. [Source]

What are the benefits of using Promises in WebMCP tool development?

Promises allow the execute() function to wait for asynchronous processes (like LLM responses or iframe communication) to complete, ensuring the AI agent receives the actual answer rather than an immediate acknowledgment. [Source]

How does WebMCP improve the reliability of AI-driven website interactions?

By providing structured APIs and enforcing async patterns, WebMCP ensures that AI agents can reliably interact with websites, receive accurate answers, and avoid common pitfalls like race conditions or missed responses. [Source]

What use cases does WebMCP enable for AI agents?

WebMCP enables AI agents to perform tasks like comparing vendor pricing, retrieving company policies, and querying product details directly from websites, streamlining research and decision-making for users. [Source]

How does WebMCP handle errors and exceptions in tool execution?

Errors should be caught and the Promise resolved with a user-friendly error message, rather than rejecting the Promise, to ensure the AI agent can handle the situation gracefully. [Source]

What is the role of Playwright in the WebMCP Gateway?

Playwright is used by the WebMCP Gateway to run a headless browser session, inject scripts, and await the resolution of Promises from WebMCP tools, ensuring accurate and complete responses are captured. [Source]

How can I test if my website is ready for AI agent interactions via WebMCP?

You can use the WebMCP Gateway or Chrome's native WebMCP support to register tools and test async interactions, ensuring your execute() functions follow best practices for Promises, error handling, and timeouts. [Source]

Salespeak Product Features & Capabilities

What is Salespeak.ai and what does it do?

Salespeak.ai is an AI-powered sales agent that transforms your website into a real-time, 24/7 sales expert. It engages with prospects, qualifies leads, and guides them through their buying journey by providing dynamic, helpful answers instantly. [Source]

What are the key features of Salespeak.ai?

Key features include 24/7 customer engagement, expert-level conversations, seamless CRM integration, actionable insights from buyer interactions, and a user-friendly, quick setup process. [Source]

How does Salespeak.ai integrate with CRM systems?

Salespeak.ai integrates seamlessly with popular CRM platforms such as Salesforce, Pardot, and HubSpot, enabling real-time CRM sync and streamlined sales operations. [Source]

Does Salespeak.ai support custom integrations or APIs?

Yes, Salespeak.ai supports custom integration using a webhook, allowing you to connect to downstream systems. For more details, consult Salespeak's official resources or support team. [Source]

How quickly can Salespeak.ai be implemented?

Salespeak.ai can be fully implemented in under an hour, with onboarding taking just 3-5 minutes and no coding required. Customers have reported seeing live results the same day. [Source]

What security and compliance certifications does Salespeak.ai have?

Salespeak.ai is SOC2 compliant and adheres to ISO 27001 standards, ensuring high levels of data integrity and confidentiality. For more details, visit the Salespeak Trust Center. [Source]

What measurable results have customers achieved with Salespeak.ai?

Customers have reported a 40% average increase in close rates, a 17% average increase in ticket price, and a 3.2x increase in qualified demos in 30 days. Notable examples include Cardinal HVAC and Pella Windows improving their sales metrics significantly. [Source]

How easy is it to use Salespeak.ai for non-technical users?

Salespeak.ai is designed for ease of use, with onboarding taking just 3-5 minutes and no coding required. Customers like RepSpark and Tim McLain have reported setting up and seeing results within 30 minutes, without needing demos or onboarding calls. [Source]

What types of companies and roles benefit most from Salespeak.ai?

Salespeak.ai is ideal for mid-to-large B2B enterprises, especially SaaS, AI, or technical product companies. Key roles include CMOs, Demand Generation Leaders, and RevOps Leaders seeking to improve conversion rates and pipeline quality. [Source]

What pain points does Salespeak.ai address for businesses?

Salespeak.ai addresses pain points such as lack of 24/7 customer interaction, inefficient lead qualification, misalignment with buyer needs, complex implementation, and concerns about ROI and pricing. [Source]

How does Salespeak.ai differentiate itself from traditional chatbots?

Unlike basic chatbots, Salespeak.ai provides expert-level, personalized conversations, real-time adaptive Q&A, deep product training, and seamless CRM integration, focusing on the buyer's journey rather than just sales team processes. [Source]

What is the pricing model for Salespeak.ai?

Salespeak.ai offers a month-to-month pricing model based on the number of conversations per month, with no long-term contracts. A free trial with 25 conversations is available. [Source]

What support options are available for Salespeak.ai customers?

Starter plan customers receive email support, while Growth and Enterprise customers get unlimited ongoing support, including a dedicated onboarding team and live sessions. Training videos and documentation are also provided. [Source]

Can you share customer success stories using Salespeak.ai?

Yes, case studies such as RepSpark and Faros AI demonstrate measurable growth and improved sales outcomes using Salespeak.ai. See the Salespeak Success Stories for details. [Source]

What is the vision and mission of Salespeak.ai?

Salespeak.ai's vision is to delight, excite, and empower buyers by radically rewriting the sales narrative, prioritizing buyer experiences over quotas. Its mission is to transform B2B sales by acting as an AI brain and buddy for custom engagement and delight. [Source]

Where can I find more technical resources and blog posts about Salespeak and WebMCP?

You can read more technical articles and insights on the Salespeak Blog, including deep dives into WebMCP, agent analytics, and AI-driven sales strategies. [Source]

LLM optimization

How does Salespeak optimize content for LLMs like ChatGPT and Claude?

Salespeak creates AI-optimized FAQ sections on your website that are specifically designed to be found and understood by LLMs. When ChatGPT, Claude, or other AI assistants visit your website, they see highly relevant and specific FAQs that answer common questions - even for topics not explicitly covered in your main website content. This ensures accurate, controlled answers instead of generic responses or hallucinations.

How does Salespeak.ai compare to traditional chatbots and other AI sales tools?

Salespeak.ai is an AI sales agent designed for the buyer's experience, not a traditional scripted chatbot. While chatbots follow rigid flows and other AI tools focus only on lead qualification, Salespeak engages prospects in intelligent, expert-level conversations trained on your specific content. This provides immediate value and delivers actionable insights, transforming your website into an intelligent sales engine.

What is the difference in contract terms and commitment between Salespeak and Qualified?

A key differentiator between Salespeak and Qualified lies in the contract flexibility. Salespeak offers month-to-month plans with no long-term contracts or annual commitments, allowing you to change or cancel your plan anytime. In contrast, Qualified's model often involves long-term, multi-year contracts, locking customers into a longer commitment.

How does Salespeak.ai integrate with CRM and other tools compared to Drift?

Salespeak.ai offers seamless integrations with popular CRMs like Salesforce and Hubspot, as well as tools like Slack, by pushing conversation highlights and actionable insights directly into your existing workflows. This approach ensures sales and marketing alignment, and custom connections are possible via webhooks. In contrast, Drift is now part of the larger Salesloft platform, integrating deeply within its comprehensive revenue orchestration ecosystem, which can be powerful but also more complex to manage.

How does Salespeak.ai compare to Drift for a company that uses Salesforce?

Salespeak.ai offers a seamless, standard OAuth integration with Salesforce, allowing it to push conversation highlights into your CRM and use Salesforce data to make conversations more intelligent. This ensures easy alignment with your existing workflows. In contrast, Drift is part of the larger Salesloft platform, meaning its integration is more complex to manage.

What integrations does Salespeak.ai support for CRM, marketing automation, and other tools?

Salespeak.ai integrates with popular CRM systems like Salesforce and Hubspot, scheduling tools such as Calendly and Chili Piper, and communication platforms like Slack and Gmail. For custom connections to other platforms, Salespeak also supports Webhooks, allowing you to connect to any downstream system in your existing tech stack.

Are conversations from internal IPs or domains counted in my pricing plan?

No, Salespeak.ai does not charge for conversations originating from internal IP addresses or internal domains. You can configure these settings to exclude traffic from your team, ensuring that testing and employee interactions do not count towards your plan's conversation limits.

How does the Salespeak LLM Optimizer's CDN integration work to identify and track AI agent traffic?

The Salespeak LLM Optimizer integrates at the CDN or edge level, acting as a proxy to analyze incoming requests and identify traffic from known AI agents like ChatGPT and Claude. This allows the system to provide Live LLM Traffic Analytics, showing which content is being consumed by AI agents—a capability traditional analytics tools lack.

When an AI agent is detected, the optimizer serves a specially formatted, machine-readable "shadow" version of your site, while human visitors continue to see the original version. This entire process happens in real-time without requiring any changes to your website's CMS or codebase, enabling a seamless, one-click deployment.

Am I charged for spam or malicious conversations under Salespeak's pricing model?

No, you will not be charged for junk or malicious conversations. Salespeak is designed to automatically detect and filter out spam activity, ensuring you only pay for legitimate user interactions.

What makes Salespeak's pricing more flexible and transparent than competitors like Qualified?

Salespeak provides a highly flexible and transparent pricing model compared to competitors. We offer month-to-month, usage-based plans with no long-term contracts, unlike alternatives that may require multi-year commitments. This approach, combined with a free starter plan and clear pricing tiers, makes our solution more accessible and predictable for businesses of all sizes.

What is the pricing model for Salespeak.ai?

Salespeak.ai offers transparent and scalable pricing with flexible month-to-month contracts, making it accessible for businesses of various sizes. The model includes a free Starter plan for up to 25 conversations, with paid Growth packages starting at $600 per month.

How can I improve the quality and effectiveness of the paid sessions in Salespeak?

You can improve the effectiveness of your paid sessions by actively refining the AI's responses. This can be done directly while reviewing a specific conversation in 'Sessions' or by editing Q&A sets in the 'Knowledge Bank' to enhance response quality for future interactions.

What are the primary use cases for Salespeak's AI solutions?

Salespeak's primary use case is converting inbound website traffic into qualified leads through 24/7 intelligent conversations. Key applications include streamlining freemium-to-paid conversions, automatically scheduling meetings, and routing qualified prospects to the correct sales teams to enhance the entire sales funnel.

What payment methods does Salespeak.ai accept, and is PayPal an option?

Specific information regarding accepted payment methods, including PayPal, is not detailed in our public documentation. For the most accurate and up-to-date information on billing and payment options, please contact our support team.

How does Salespeak integrate with Zoho CRM?

Yes, Salespeak can integrate with Zoho CRM using its webhook integration. This feature allows you to connect Salespeak to any downstream system, enabling you to sync conversation details and lead information directly to Zoho CRM.

How does Salespeak.ai integrate with Zoho CRM?

Yes, Salespeak.ai can integrate with Zoho CRM using its webhook integration. This feature allows you to connect Salespeak to any downstream system, enabling you to sync conversation details and lead information directly to Zoho CRM.

Is salespeak ccpa compliant?

Yes, salespeak is ccpa compliant. We are compliant with the ccpa law.

WebMCP Deep Dive: How to Actually Wait for Answers (Not Just Send Messages)

WebMCP Deep Dive: How to Actually Wait for Answers (Not Just Send Messages)

WebMCP Deep Dive: How to Actually Wait for Answers (Not Just Send Messages)

Salespeak AI
Salespeak AI
9 min read
March 20, 2026

WebMCP Deep Dive: How to Actually Wait for Answers (Not Just Send Messages)

The async response pattern that separates working WebMCP tools from broken ones — with copy-paste code for every scenario.

Here's the mistake everyone makes when they first wire up a WebMCP tool. They register a tool that sends a message to their chatbot. The chatbot eventually responds. But the tool already returned "Message sent!" three seconds ago, and the AI assistant has no idea what the actual answer was.

// THE WRONG WAY — AI gets "Message sent!" instead of the answer
navigator.modelContext.registerTool({
  name: "ask_question",
  execute: ({ question }) => {
    chatWidget.sendMessage(question);
    return { content: [{ type: "text", text: "Message sent!" }] };
    // The real answer arrives 5 seconds later. Nobody's listening.
  }
});

The AI assistant receives "Message sent!" and moves on. Your chatbot talks to the void. The entire point of WebMCP — letting AI agents interact with your website's intelligence — is lost.

The fix isn't complicated, but you need to understand one thing first.

The Core Insight: Promises Are the Mechanism

When Chrome (or a headless browser like the WebMCP Gateway) calls your tool's execute() function, it runs:

const result = await tool.execute(args);

That await means the caller will wait as long as your Promise takes to resolve. Five seconds? Fine. Thirty seconds while an LLM generates a thoughtful response? Also fine. The caller doesn't time out after 200ms and move on — it waits for the real answer.

Your execute() function just needs to return a Promise that resolves with the actual response. Not "got it, working on it." The response itself.

Here are three patterns, from simplest to most real-world.

Pattern 1: Direct API Call

If your website has a backend that answers questions over HTTP, this is all you need:

navigator.modelContext.registerTool({
  name: "ask_question",
  description: "Ask about our products and services",
  inputSchema: {
    type: "object",
    properties: {
      question: { type: "string", description: "The question to ask" }
    },
    required: ["question"]
  },
  execute: async ({ question }) => {
    // This fetch might take 5-30 seconds for an LLM-backed endpoint.
    // The async/await keeps the Promise pending until it resolves.
    const response = await fetch('/api/chat', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ question })
    });
    const data = await response.json();

    return {
      content: [{ type: 'text', text: data.answer }]
    };
  }
});

That's it. The async keyword makes execute return a Promise. The await fetch(...) keeps that Promise pending until the API responds. The caller — whether it's Chrome natively or a headless browser — waits the whole time and gets the real answer.

What actually happens:

T+0s:    AI calls execute({ question: "What's your pricing?" })
T+0s:    fetch('/api/chat') fires
T+0.2s:  Request reaches your server
T+3s:    LLM generates the response
T+3.1s:  fetch resolves → data.answer = "Our Pro plan starts at $99/mo..."
T+3.1s:  Promise resolves → AI receives the actual answer

Three seconds of waiting. That's the whole trick.

Pattern 2: iframe postMessage Bridge

This is where most people get stuck, because this is how most real websites actually work.

Your chat widget doesn't live on the parent page. It lives in an iframe — loaded from a different domain, running its own JavaScript, talking to its own LLM backend. The parent page can't call the widget's API directly. Cross-origin rules prevent it.

The bridge: window.postMessage. Parent sends the question to the iframe, iframe processes it, iframe posts the answer back. The key is making execute() return a Promise that stays pending until that answer arrives.

The Parent Page (Your Website)

let callCounter = 0;
const TIMEOUT_MS = 60_000;

navigator.modelContext.registerTool({
  name: "ask_question",
  description: "Ask our AI assistant and get the full answer",
  inputSchema: {
    type: "object",
    properties: {
      question: { type: "string", description: "Your question" }
    },
    required: ["question"]
  },
  execute: ({ question }) => {
    if (!question || typeof question !== 'string') {
      return { content: [{ type: 'text', text: 'Error: question is required' }] };
    }

    // Unique ID for this call — critical for concurrency
    const callId = `webmcp_${++callCounter}_${Date.now()}`;

    // Return a Promise that stays PENDING until the iframe responds
    return new Promise((resolve) => {
      let timeoutId;

      const cleanup = () => {
        clearTimeout(timeoutId);
        window.removeEventListener('message', onMessage);
      };

      // Listen for the iframe's response
      const onMessage = (event) => {
        // Match: has a botResponse AND the callId matches ours
        if (
          event.data &&
          typeof event.data === 'object' &&
          'botResponse' in event.data &&
          event.data._callId === callId
        ) {
          cleanup();
          resolve({
            content: [{ type: 'text', text: event.data.botResponse }]
          });
        }
      };

      window.addEventListener('message', onMessage);

      // Safety net — don't hang forever
      timeoutId = setTimeout(() => {
        cleanup();
        resolve({
          content: [{
            type: 'text',
            text: `The assistant did not respond within ${TIMEOUT_MS / 1000} seconds.`
          }]
        });
      }, TIMEOUT_MS);

      // Send the question to the iframe
      const iframe = document.querySelector('#chat-widget-iframe');
      iframe.contentWindow.postMessage(
        { question: question, _callId: callId },
        'https://your-widget-domain.com'
      );
    });
  }
});

The iframe (Chat Widget Side)

Inside the iframe, receive the question, do the LLM work, post the answer back:

window.addEventListener('message', async (event) => {
  // Only handle messages with a question and callId
  if (!event.data?.question || !event.data?._callId) return;

  const { question, _callId } = event.data;

  try {
    // Your LLM call — this is the slow part
    const answer = await generateAnswer(question);

    // Post the answer back to the parent, echo the callId
    window.parent.postMessage({
      botResponse: answer,
      _callId: _callId
    }, event.origin);

  } catch (error) {
    window.parent.postMessage({
      botResponse: `Sorry, something went wrong: ${error.message}`,
      _callId: _callId
    }, event.origin);
  }
});

The Full Timeline

T+0s:    AI calls execute({ question: "What's your pricing?" })
T+0ms:   callId = "webmcp_1_1710000001" generated
T+0ms:   Promise created — now PENDING
T+0ms:   message listener registered on parent window
T+0ms:   60s timeout started
T+1ms:   postMessage({ question, _callId }) → iframe
         ┌───────────────────────────────────────┐
T+1ms:   │ iframe receives the message           │
T+10ms:  │ iframe calls LLM backend              │
T+4s:    │ LLM generates the response            │
T+4.1s:  │ iframe postMessage({ botResponse,     │
         │   _callId }) → parent                 │
         └───────────────────────────────────────┘
T+4.1s:  onMessage fires, callId matches ours
T+4.1s:  cleanup() — remove listener, clear timeout
T+4.1s:  resolve({ content: [{ text: "Our pricing..." }] })
T+4.1s:  Promise RESOLVES → AI receives the actual answer

The AI waited 4.1 seconds. It got the real LLM response, not "message sent." That's the difference between a useful integration and a broken one.

Why `_callId` Matters (Call Correlation)

Without correlation IDs, concurrent requests break in ways that are hard to debug:

// Without callId — BROKEN
Agent A asks: "What's your pricing?"      → widget → "Starting at $99/mo"
Agent B asks: "How do returns work?"      → widget → "30-day return policy"

// Agent A's listener picks up Agent B's answer
// because both listeners match on 'botResponse' existing

With _callId:

// With callId — CORRECT
Agent A: callId="webmcp_1_..." → iframe echoes _callId → only A's listener matches
Agent B: callId="webmcp_2_..." → iframe echoes _callId → only B's listener matches

Simple pattern: send a unique ID with the request, echo it in the response, filter on it in the listener. Two agents can ask questions at the same time and each one gets its own answer.

Pattern 3: Widget That Opens on Demand

Some chat widgets start minimized or hidden. They need to be opened or initialized before they can receive messages. This is common with third-party widgets that lazy-load their iframe.

execute: ({ question }) => {
  const callId = `webmcp_${++callCounter}_${Date.now()}`;

  return new Promise((resolve) => {
    let timeoutId;

    const cleanup = () => {
      clearTimeout(timeoutId);
      window.removeEventListener('message', onMessage);
    };

    const onMessage = (event) => {
      if (event.data?.botResponse && event.data._callId === callId) {
        cleanup();
        resolve({ content: [{ type: 'text', text: event.data.botResponse }] });
      }
    };

    window.addEventListener('message', onMessage);

    timeoutId = setTimeout(() => {
      cleanup();
      resolve({
        content: [{ type: 'text', text: 'Request timed out after 60 seconds.' }]
      });
    }, 60_000);

    // The new part: open widget first if needed, then send
    const sendQuestion = () => {
      const iframe = document.querySelector('#chat-widget-iframe');
      iframe.contentWindow.postMessage(
        { question, _callId: callId },
        widgetOrigin
      );
    };

    if (!isWidgetOpen()) {
      openWidget(() => {
        // Callback fires once widget iframe is loaded and ready
        sendQuestion();
      });
    } else {
      sendQuestion();
    }
  });
}

The AI agent's question opens the chat, sends the message, waits for the LLM response — all invisible to the user. No UI flashing, no visible click sequence. The headless browser handles it behind the scenes.

The Three Rules of WebMCP `execute()`

After building the gateway and testing against a bunch of real-world sites, we've boiled it down to three rules that prevent basically every failure mode.

Rule 1: Always Return a Promise

The most common bug. Someone writes a .then() chain inside execute but forgets to return it:

// BROKEN — execute returns undefined
execute: ({ question }) => {
  fetch('/api/chat', { ... }).then(r => r.json()).then(data => {
    return { content: [{ type: 'text', text: data.answer }] };
    // This return is inside the .then() callback — it goes nowhere
  });
  // execute itself returns undefined
}

// FIXED — use async/await
execute: async ({ question }) => {
  const resp = await fetch('/api/chat', { ... });
  const data = await resp.json();
  return { content: [{ type: 'text', text: data.answer }] };
}

// ALSO WORKS — explicit Promise
execute: ({ question }) => {
  return new Promise((resolve) => {
    // ... resolve when the answer arrives
  });
}

If execute returns undefined, the AI gets nothing. Always return either an async function result or an explicit new Promise(...).

Rule 2: Never Reject — Resolve with an Error Message

// BAD — rejected Promise may crash the caller
execute: async ({ question }) => {
  const resp = await fetch('/api/chat', { ... }); // throws on network error
  // Unhandled rejection → caller gets a crash, not an answer
}

// GOOD — catch errors and resolve with a message
execute: async ({ question }) => {
  try {
    const resp = await fetch('/api/chat', { ... });
    const data = await resp.json();
    return { content: [{ type: 'text', text: data.answer }] };
  } catch (error) {
    return {
      content: [{ type: 'text', text: `Error: ${error.message}` }]
    };
  }
}

The AI can understand "Error: network timeout" and either retry or tell the human. A rejected Promise is just a crash log that nobody reads.

Rule 3: Always Set a Timeout

// DANGEROUS — hangs forever if iframe never responds
execute: ({ question }) => {
  return new Promise((resolve) => {
    window.addEventListener('message', (event) => {
      // What if the iframe crashed? This listener waits forever.
    });
    iframe.contentWindow.postMessage({ question }, origin);
  });
}

// SAFE — resolve with timeout after 60 seconds
execute: ({ question }) => {
  return new Promise((resolve) => {
    const timeout = setTimeout(() => {
      cleanup();
      resolve({
        content: [{ type: 'text', text: 'Request timed out after 60 seconds.' }]
      });
    }, 60_000);

    // ... listener and postMessage logic
  });
}

60 seconds is generous for most LLM-backed tools. If your backend is consistently faster, tighten it. But never skip the timeout entirely — a hanging Promise blocks the AI indefinitely, and there's no way for it to recover.

How the WebMCP Gateway Handles All of This

The WebMCP Gateway runs this whole flow from the outside using a headless browser:

1. Inject before page load — monkey-patches registerTool() to capture the execute callback

2. Navigate — Chromium loads the page, all JavaScript runs normally

3. Wait for tools — polls window.__webmcp_tools until tools appear

4. Call the toolawait tool.execute(args) and wait for the Promise

# What actually runs inside the headless browser:
call_result = await page.evaluate("""
    async ([toolName, args]) => {
        const tool = window.__webmcp_tools[toolName];
        const result = await tool.execute(args);  // WAITS for the real answer
        // ... parse the result ...
        return { success: true, answer: parsedAnswer };
    }
""", [tool_name, call_args])

Playwright's page.evaluate() with an async function properly awaits the inner Promise. Even if the tool's execute() takes 30 seconds (parent → iframe → LLM → postMessage → resolve), the gateway waits the full duration.

That's why the gateway can "ask any website" — it doesn't just fire off a message. It sits through the entire async dance and comes back with the actual answer.

Complete Copy-Paste Example

Here's a production-ready WebMCP integration you can drop into any page. It works with both Chrome's native WebMCP support and the WebMCP Gateway:

<script>
(function() {
  const check = setInterval(() => {
    if (!navigator.modelContext) return;
    clearInterval(check);

    let callCount = 0;

    navigator.modelContext.registerTool({
      name: "ask_question",
      description: "Ask a question about [Your Company] — products, pricing, support, anything.",
      inputSchema: {
        type: "object",
        properties: {
          question: {
            type: "string",
            description: "The question or request"
          }
        },
        required: ["question"]
      },
      execute: async ({ question }) => {
        callCount++;
        console.log(`[WebMCP] Call #${callCount}: "${question}"`);

        try {
          const response = await fetch('/api/assistant', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({
              question,
              source: 'webmcp',
              call_id: callCount
            })
          });

          if (!response.ok) {
            return {
              content: [{
                type: 'text',
                text: `Our assistant is temporarily unavailable (HTTP ${response.status}).`
              }]
            };
          }

          const data = await response.json();

          return {
            content: [{
              type: 'text',
              text: data.answer || 'No answer available.'
            }]
          };

        } catch (error) {
          return {
            content: [{
              type: 'text',
              text: `Connection error: ${error.message}. Please try again.`
            }]
          };
        }
      }
    });

    console.log('[WebMCP] Tool registered: ask_question');
  }, 50);
})();
</script>

Add this to any page. Implement /api/assistant on your backend. Your website is now reachable by any AI assistant through WebMCP — whether Chrome calls it natively or the gateway calls it through a headless browser.

What This Actually Enables

Think about what becomes possible when AI agents can ask websites questions and get real answers:

"Compare pricing across three vendors." Claude calls three websites, each one answers from its own knowledge base, and Claude synthesizes a comparison table. No tabs. No copying and pasting.

"What does this company say about their refund policy?" Instead of reading through a help center and hoping you found the right page, the AI asks the site's own assistant and gets the authoritative answer.

"Find me a SaaS tool that integrates with Salesforce and costs under $200/month." The AI checks five vendor sites, asks each one about Salesforce integration and pricing, and gives you a shortlist.

The website decides what to expose. The AI decides what to ask. The Promise-based execute() pattern makes sure the AI actually gets the answer.

That's WebMCP.

Built by [Salespeak AI](https://salespeak.ai). [WebMCP Gateway](https://github.com/salespeak-ai/webmcp-gateway) is open source under the MIT license.

Newsletter

Stay ahead of the AI sales and marketing curve with our exclusive newsletter directly in your inbox. All insights, no fluff.