Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,21 @@
# 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.
# 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`.
# PUBLIC_KAPA_INTEGRATION_ID=f31c5644-dd65-4f49-9762-bf580062afa8
# Kapa CUSTOM_FRONTEND integration ID for the custom "Ask AI" React chat
# (KapaChatLauncher / @kapaai/react-sdk). If unset, the custom chat launcher
# is omitted. Get yours at: https://app.kapa.ai (Integrations → custom frontend).
# Do not reuse this value for WebMCP — that needs a Website Widget integration.
PUBLIC_KAPA_INTEGRATION_ID=

# Kapa Website Widget integration ID for WebMCP only. Emitted as
# data-website-id on the official widget script with data-webmcp-enabled=true
# so in-browser agents can call search_warp_knowledge_sources. If unset, the
# WebMCP script is omitted. The default floating Ask button stays hidden
# (data-button-hide); the custom sidebar Ask control remains the human entry
# point. Get yours at: https://app.kapa.ai (Integrations → Website Widget).
# Live Docs Bot example: "Docs Widget" (type WIDGET).
PUBLIC_KAPA_WEBSITE_WIDGET_INTEGRATION_ID=

# Optional Kapa project ID used to build internal conversation links in
# "Create ticket" email drafts:
# https://app.kapa.ai/<PROJECT_ID>/conversations/<THREAD_ID>
Expand Down
7 changes: 7 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export default defineConfig({
access: 'public',
optional: true,
}),
// Website Widget integration ID for WebMCP only (not the custom
// React chat frontend). Required by Kapa for data-webmcp-enabled.
PUBLIC_KAPA_WEBSITE_WIDGET_INTEGRATION_ID: envField.string({
context: 'client',
access: 'public',
optional: true,
}),
PUBLIC_KAPA_PROJECT_ID: envField.string({
context: 'client',
access: 'public',
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
3 changes: 3 additions & 0 deletions src/components/CustomHead.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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: `<ClientRouter />` (Astro View Transitions) is intentionally NOT
Expand Down Expand Up @@ -189,6 +190,8 @@ const websiteJsonLd = JSON.stringify({
/>
<RudderStackAnalytics />
<ArticleImageLightbox />
{/* WebMCP page tool for in-browser agents; custom Ask chat stays separate. */}
<KapaWebMcp />

<Analytics />
<SpeedInsights />
Expand Down
35 changes: 35 additions & 0 deletions src/components/KapaWebMcp.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
/**
* Loads the Kapa Website Widget script solely so WebMCP can register the
* read-only `search_warp_knowledge_sources` page tool for in-browser agents.
*
* Uses the Website Widget integration (`PUBLIC_KAPA_WEBSITE_WIDGET_INTEGRATION_ID`),
* not the custom React frontend ID (`PUBLIC_KAPA_INTEGRATION_ID`). WebMCP only
* works with Website Widget integrations.
*
* 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_WEBSITE_WIDGET_INTEGRATION_ID } from 'astro:env/client';
import { getKapaWebMcpScriptAttributes } from '../lib/kapa-webmcp.js';

const attrs = getKapaWebMcpScriptAttributes(PUBLIC_KAPA_WEBSITE_WIDGET_INTEGRATION_ID);
---

{
attrs ? (
<script
is:inline
async
src={attrs.src}
data-website-id={attrs['data-website-id']}
data-project-name={attrs['data-project-name']}
data-webmcp-enabled={attrs['data-webmcp-enabled']}
data-button-hide={attrs['data-button-hide']}
/>
) : null
}
52 changes: 52 additions & 0 deletions src/lib/kapa-webmcp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Kapa Website Widget attributes for WebMCP.
*
* Docs human chat uses a custom React SDK surface (`KapaChatLauncher`) backed
* by the CUSTOM_FRONTEND integration (`PUBLIC_KAPA_INTEGRATION_ID`). WebMCP
* requires a separate Website Widget integration ID
* (`PUBLIC_KAPA_WEBSITE_WIDGET_INTEGRATION_ID`) on the official widget script
* with `data-webmcp-enabled="true"`:
* https://docs.kapa.ai/integrations/website-widget/features/webmcp
*
* Do not pass the custom-frontend ID here — Kapa only enables WebMCP on
* Website Widget integrations.
*
* `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} websiteWidgetIntegrationId
* Website Widget integration ID (`PUBLIC_KAPA_WEBSITE_WIDGET_INTEGRATION_ID`).
* Not the custom React frontend integration ID.
* @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(websiteWidgetIntegrationId) {
const websiteId =
typeof websiteWidgetIntegrationId === 'string' ? websiteWidgetIntegrationId.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',
};
}
35 changes: 35 additions & 0 deletions src/lib/kapa-webmcp.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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 Website Widget 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', () => {
// Fixture only — production uses PUBLIC_KAPA_WEBSITE_WIDGET_INTEGRATION_ID.
const websiteWidgetIntegrationId = '00000000-0000-4000-8000-000000000001';
const attrs = getKapaWebMcpScriptAttributes(` ${websiteWidgetIntegrationId} `);

assert.deepEqual(attrs, {
src: KAPA_WIDGET_SCRIPT_SRC,
async: true,
'data-website-id': websiteWidgetIntegrationId,
'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);
// Must not accidentally lean on the custom-frontend env var name/value.
assert.notEqual(websiteWidgetIntegrationId, process.env.PUBLIC_KAPA_INTEGRATION_ID);
});
Loading