Enabling WebMCP Tools on my SvelteKit Migration Reference
Stay on top of this story
Follow the names and topics behind it.
Add this story's key topics to your watchlist so LyscoNews can highlight related developments and future matches.
Create a free account to sync your watchlist, saved stories, and alerts across devices.
Quick Summary
An AI agent asks: "What's the Svelte 5 equivalent of useEffect?" Today it has two options — scrape the page HTML and parse it, or fetch api/data.json and filter 100+ items in context. Both work ok, but they both waste tokens. WebMCP gives our AI agent a third option: call get_svelte_equivalent({ framework: 'react', concept: 'Side Effects' }) and get a structured answer directly. The data is the same. The interface is purpose-built for agents. WebMCP is a W3C Community Group Draft that adds navigator.modelContext to browsers. It lets websites register MCP tools — the same protocol that powers Claude's tool use, Cursor's context, and other agent frameworks — directly in a webpage. An AI agent browsing the site can discover and call those tools without scraping. Chrome Canary (146+) has WebMCP behind a flag, and Microsoft Edge appears to support it out of the box — no flags or settings needed. For other browsers, @mcp-b/global from the MCP-B project fills in, until native support ships. Don't confuse this with Anthropic's MCP server protocol — WebMCP is a client-side browser API that happens to speak the same message format. My site svelte.cogley.jp is a migration reference showing how concepts from React, Vue, Angular, and now Svelte 4 map to Svelte 5. There are 120+ structured mappings across four categories: overview, syntax, architecture, and ecosystem. Each mapping has a concept name, source framework code, Svelte equivalent code, notes, doc links, and bilingual support (English/Japanese, well, because I can). The data already has machine interfaces: a JSON API at /api/data.json, content negotiation via Accept: text/markdown, and an llms.txt describing the site for crawlers. WebMCP adds another layer — typed tools that agents can discover and call in-page without fetching or parsing anything. The polyfill weighs ~285KB. Most visitors are humans browsing migration cards, not AI agents calling tools — so shipping it in the initial bundle would be wasteful. Dynamic import: onMount(async () => { theme.init(); language.init(data.lang);
// Force polyfill mode — Chrome Canary's native modelContext is // incomplete (missing listTools/registerTool), which crashes the // native adapter. Disable auto-init, wipe, then reinitialize. const win = window as unknown as Record; win.__webModelContextOptions = { autoInitialize: false }; const { cleanupWebModelContext, initializeWebModelContext } = await import('@mcp-b/global'); cleanupWebModelContext(); try { Object.defineProperty(navigator, 'modelContext', { value: undefined, configurable: true, writable: true, }); } catch { /* non-configurable — already cleaned */ } initializeWebModelContext();
const { registerMigrationTools } = await import('$lib/webmcp'); registerMigrationTools(); });
Vite code-splits the polyfill into its own chunk. The __webModelContextOptions flag prevents the module's auto-initialization from crashing on browsers (like Chrome Canary) where the native navigator.modelContext exists but is incomplete — missing listTools() and registerTool(). After wiping the incomplete native API, initializeWebModelContext() installs the full polyfill. On Edge and browsers without a native API, cleanupWebModelContext() is a no-op and the polyfill installs normally. The registerMigrationTools() import is dynamic too, keeping tool definitions out of both the server bundle and the initial page load. Six read-only tools answer questions that AI agents might want to do, like "what's the Svelte equivalent of X?" Full-text search across concepts, code, and notes. Same filter logic as the UI's search box: mc.registerTool({ name: 'search_mappings', description: 'Search migration mappings by keyword...', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search keyword' }, framework: { type: 'string', enum: ['react', 'vue', 'angular', 'svelte4'] }, category: { type: 'string', enum: ['overview', 'syntax', 'architecture', 'ecosystem'] }, lang: { type: 'string', enum: ['en', 'ja'] }, }, required: ['query'], }, annotations: { readOnlyHint: true }, execute: async (args) => { // Filter pool by framework/category, then substring-match on query // Return structured JSON with framework/category context per match }, });
An agent searching for "useState" gets React entries; searching for "routing" gets results across all four frameworks. The optional framework and category filters let agents narrow results when they already know what they're looking for. Direct lookup: give it a framework and concept name, get the best match. Uses exact match first, then substring fallback. If the agent asks for { framework: 'react', concept: 'Reactive State' }, it gets the $state mapping with full code examples and notes. Items where the to field contains "No equivalent" or "⚠️". This is the kind of query that's awkward to express via JSON filtering but natural as a tool call: "What can't I do in Svelte that I can do in React?" get_site_info — overview: frameworks, categories, total count, API URLs. The "hello world" tool. get_changelog — recent updates, bilingual, with configurable limit. compare_frameworks — side-by-side: how React, Vue, Angular, and Svelte 4 each handle the same concept. An agent comparing "Reactive State" gets useState vs ref() vs signal() vs let count = 0 → $state in one call. The entire module is ~180 lines of TypeScript. Shared helpers handle filtering and matching: function applyFilter(pool: MappingItem[], query: string): MappingItem[] { if (!query.trim()) return pool; const q = query.toLowerCase(); return pool.filter( (item) => item.concept.toLowerCase().includes(q) || item.concept_ja?.toLowerCase().includes(q) || item.from?.toLowerCase().includes(q) || item.to?.toLowerCase().includes(q) || item.notes?.toLowerCase().includes(q) || item.notes_ja?.toLowerCase().includes(q) || item.checklist?.some((s) => s.toLowerCase().includes(q)) || item.checklist_ja?.some((s) => s.toLowerCase().includes(q)) ); }
Same logic that drives the $derived block in +page.svelte. The UI and the tools search identically — one filter function, two consumers. Every tool returns MCP's standard content format ({ content: [{ type: 'text', text: JSON.stringify(...) }] }) and carries annotations: { readOnlyHint: true }. Query only, no mutations. Microsoft Edge works out of the box. Chrome Canary needs the WebMCP flag (chrome://flags → "WebMCP for testing"). Alternatively, install the MCP-B extension. Open the console: // List tools navigator.modelContext.listTools() // → Array of 6 tools with names, descriptions, input schemas
// Search for useState await navigator.modelContext.callTool({ name: 'search_mappings', arguments: { query: 'useState' } })
// Direct lookup await navigator.modelContext.callTool({ name: 'get_svelte_equivalent', arguments: { framework: 'react', concept: 'Reactive State' } })
// Compare across frameworks await navigator.modelContext.callTool({ name: 'compare_frameworks', arguments: { concept: 'Reactive State' } })
In browsers without native support, the polyfill loads silently with no visible UI change and no console errors. Here it is in Chrome Canary, but I tested in Edge too and it worked without any special settings:
I added a WebMCP section to llms.txt listing all six tools with their parameters. An agent that discovers the site via llms.txt now knows it can call tools directly — it doesn't have to guess whether the site supports WebMCP or fall back to fetching JSON. 22 Feb 2026 — I added Svelte 4 as a fourth source framework. This one's different from the other three — it's not a cross-framework migration, it's an upgrade guide for developers already on Svelte who need to move from v4 to v5 (runes, snippets, new event syntax, stores → reactive classes). The data includes 25 new mappings: 4 overview items, 12 syntax mappings, 5 architecture patterns, and 4 ecosystem entries. The overview category has a new card type — a ChecklistCard — that renders a step-by-step migration checklist with numbered steps instead of the usual two-column code comparison. The checklist covers everything from npm install svelte@5 through running sv migrate to testing $$props edge cases. A new checklist field on MappingItem holds the step arrays, with checklist_ja for Japanese translations. All four machine-readable interfaces were updated: WebMCP tools — search_mappings and get_svelte_equivalent now accept framework: 'svelte4'; search covers checklist text; localizeItem returns the checklist array llms-full.txt — Svelte 4 section renders checklist steps as numbered lists llms.txt — framework list and tool descriptions updated data.json — includes the full svelte4 data structure automatically An agent can now do things like get_svelte_equivalent({ framework: 'svelte4', concept: 'Props' }) to learn that export let prop → let { prop } = $props(), or search_mappings({ query: 'stores', framework: 'svelte4' }) to find the stores → reactive classes migration path. The registration module is ~200 lines of TypeScript, with six tools. Zero bytes added to the initial page bundle of https://svelte.cogley.jp because everything loads dynamically — the polyfill chunk (429KB) only fires if the browser doesn't have native support yet. Originally published at cogley.jp Rick Cogley is CEO of eSolia Inc., providing bilingual IT outsourcing and infrastructure services in Tokyo, Japan.