diff --git a/.env.example b/.env.example
index ed587847d..1deeaa234 100644
--- a/.env.example
+++ b/.env.example
@@ -4,10 +4,13 @@
# Values below are PUBLIC (they're shipped to the browser by Astro's
# `envField`, and live behind rate limiting / allow-lists on the vendor side).
-# Kapa Website Widget integration ID for the "Ask AI" button in the sidebar.
-# If unset, docs falls back to the shared widget integration ID used in preview/prod.
+# Kapa Website Widget integration ID for the custom "Ask AI" chat and WebMCP.
+# If unset, both the custom chat launcher and the WebMCP widget script are omitted.
# Get yours at: https://app.kapa.ai/admin (Integrations → Widget → Copy integration ID).
-# The same value is passed to the widget as `data-website-id`.
+# The same value is passed to the widget as `data-website-id` (with
+# data-webmcp-enabled=true so in-browser agents can call
+# search_warp_knowledge_sources). The default floating Ask button stays hidden;
+# the custom sidebar Ask control remains the human entry point.
# PUBLIC_KAPA_INTEGRATION_ID=f31c5644-dd65-4f49-9762-bf580062afa8
PUBLIC_KAPA_INTEGRATION_ID=
diff --git a/package.json b/package.json
index f664e07e5..d56eb55f6 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"seo:audit": "node scripts/seo-audit.mjs",
"test:heading-tocs": "node scripts/validate-heading-tocs.mjs",
"test:heading-vars": "node --test src/plugins/heading-vars-core.test.mjs",
+ "test:kapa-webmcp": "node --test src/lib/kapa-webmcp.test.mjs",
"test:homepage-json-ld": "node scripts/validate-homepage-json-ld.mjs",
"postinstall": "patch-package"
},
diff --git a/src/components/CustomHead.astro b/src/components/CustomHead.astro
index fdee99342..59e284070 100644
--- a/src/components/CustomHead.astro
+++ b/src/components/CustomHead.astro
@@ -4,6 +4,7 @@ import Analytics from '@vercel/analytics/astro';
import SpeedInsights from '@vercel/speed-insights/astro';
import RudderStackAnalytics from './RudderStackAnalytics.astro';
import ArticleImageLightbox from './ArticleImageLightbox.astro';
+import KapaWebMcp from './KapaWebMcp.astro';
import { DOCS_ORIGIN } from '../lib/site.js';
// Note: `` (Astro View Transitions) is intentionally NOT
@@ -189,6 +190,8 @@ const websiteJsonLd = JSON.stringify({
/>
+{/* WebMCP page tool for in-browser agents; custom Ask chat stays separate. */}
+
diff --git a/src/components/KapaWebMcp.astro b/src/components/KapaWebMcp.astro
new file mode 100644
index 000000000..a4c8726de
--- /dev/null
+++ b/src/components/KapaWebMcp.astro
@@ -0,0 +1,31 @@
+---
+/**
+ * Loads the Kapa Website Widget script solely so WebMCP can register the
+ * read-only `search_warp_knowledge_sources` page tool for in-browser agents.
+ *
+ * Human chat stays on the custom React SDK surface (`KapaChatLauncher`).
+ * This script hides Kapa's default floating button and does not replace or
+ * duplicate the custom Ask launcher.
+ *
+ * @see https://docs.kapa.ai/integrations/website-widget/features/webmcp
+ * @see https://www.kapa.ai/blog/kapa-webmcp
+ */
+import { PUBLIC_KAPA_INTEGRATION_ID } from 'astro:env/client';
+import { getKapaWebMcpScriptAttributes } from '../lib/kapa-webmcp.js';
+
+const attrs = getKapaWebMcpScriptAttributes(PUBLIC_KAPA_INTEGRATION_ID);
+---
+
+{
+ attrs ? (
+
+ ) : null
+}
diff --git a/src/lib/kapa-webmcp.js b/src/lib/kapa-webmcp.js
new file mode 100644
index 000000000..13012252b
--- /dev/null
+++ b/src/lib/kapa-webmcp.js
@@ -0,0 +1,44 @@
+/**
+ * Kapa Website Widget attributes for WebMCP.
+ *
+ * Docs use a custom React SDK chat surface (`KapaChatLauncher`), not Kapa's
+ * default floating Ask AI button. WebMCP still requires the website widget
+ * script with `data-webmcp-enabled="true"`:
+ * https://docs.kapa.ai/integrations/website-widget/features/webmcp
+ *
+ * `data-button-hide` keeps the default widget launcher off the page so the
+ * existing custom Ask control remains the only human chat entry point.
+ * Browsers without WebMCP ignore the enable flag; chat behavior is unchanged.
+ */
+
+export const KAPA_WIDGET_SCRIPT_SRC = 'https://widget.kapa.ai/kapa-widget.bundle.js';
+
+/** Becomes `search_warp_knowledge_sources` via Kapa's snake_case tool naming. */
+export const KAPA_WEBMCP_PROJECT_NAME = 'Warp';
+
+/**
+ * @param {string | undefined | null} integrationId
+ * @returns {null | {
+ * src: string,
+ * async: true,
+ * 'data-website-id': string,
+ * 'data-project-name': string,
+ * 'data-webmcp-enabled': 'true',
+ * 'data-button-hide': 'true',
+ * }}
+ */
+export function getKapaWebMcpScriptAttributes(integrationId) {
+ const websiteId = typeof integrationId === 'string' ? integrationId.trim() : '';
+ if (!websiteId) return null;
+
+ return {
+ src: KAPA_WIDGET_SCRIPT_SRC,
+ async: true,
+ 'data-website-id': websiteId,
+ 'data-project-name': KAPA_WEBMCP_PROJECT_NAME,
+ 'data-webmcp-enabled': 'true',
+ // Hide Kapa's default floating button only. The custom sidebar Ask
+ // launcher (and search-footer Ask AI CTA) stay visible.
+ 'data-button-hide': 'true',
+ };
+}
diff --git a/src/lib/kapa-webmcp.test.mjs b/src/lib/kapa-webmcp.test.mjs
new file mode 100644
index 000000000..501d20ed8
--- /dev/null
+++ b/src/lib/kapa-webmcp.test.mjs
@@ -0,0 +1,32 @@
+import assert from 'node:assert/strict';
+import test from 'node:test';
+import {
+ KAPA_WEBMCP_PROJECT_NAME,
+ KAPA_WIDGET_SCRIPT_SRC,
+ getKapaWebMcpScriptAttributes,
+} from './kapa-webmcp.js';
+
+test('returns null when the integration id is missing', () => {
+ assert.equal(getKapaWebMcpScriptAttributes(undefined), null);
+ assert.equal(getKapaWebMcpScriptAttributes(null), null);
+ assert.equal(getKapaWebMcpScriptAttributes(''), null);
+ assert.equal(getKapaWebMcpScriptAttributes(' '), null);
+});
+
+test('enables WebMCP on the official widget script without duplicating chat UI', () => {
+ const integrationId = 'f31c5644-dd65-4f49-9762-bf580062afa8';
+ const attrs = getKapaWebMcpScriptAttributes(` ${integrationId} `);
+
+ assert.deepEqual(attrs, {
+ src: KAPA_WIDGET_SCRIPT_SRC,
+ async: true,
+ 'data-website-id': integrationId,
+ 'data-project-name': KAPA_WEBMCP_PROJECT_NAME,
+ 'data-webmcp-enabled': 'true',
+ // Default floating Ask button stays hidden; custom sidebar Ask remains.
+ 'data-button-hide': 'true',
+ });
+ assert.equal(attrs.src, 'https://widget.kapa.ai/kapa-widget.bundle.js');
+ assert.equal(attrs['data-project-name'], 'Warp');
+ assert.equal(attrs['data-consent-required'], undefined);
+});