> ## Documentation Index
> Fetch the complete documentation index at: https://mcp-b-sync-npm-packages-docs-bf03420.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Live WebMCP Tool Examples

> Interactive examples of WebMCP tools that AI agents can call in real-time. Working demonstrations of calculator, color converter, storage, and DOM query tools.

export const PolyfillSetup = () => {
  const {useState, useEffect} = React;
  const [isLoaded, setIsLoaded] = useState(false);
  useEffect(() => {
    const checkPolyfill = () => {
      if (window.navigator?.modelContext) {
        setIsLoaded(true);
      }
    };
    checkPolyfill();
    window.addEventListener('load', checkPolyfill);
    window.addEventListener('webmcp-loaded', checkPolyfill);
    return () => {
      window.removeEventListener('load', checkPolyfill);
      window.removeEventListener('webmcp-loaded', checkPolyfill);
    };
  }, []);
  const copyScript = () => {
    const scriptTag = '<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>';
    navigator.clipboard.writeText(scriptTag);
  };
  return <div className="not-prose border dark:border-white/10 rounded-xl p-6 space-y-4">
      <h3 className="text-lg font-semibold text-zinc-950 dark:text-white mb-4">
        WebMCP Polyfill Status
      </h3>

      <div className="space-y-3">
        <div className="flex items-center justify-between p-4 rounded-lg bg-zinc-50 dark:bg-zinc-900">
          <div className="flex items-center gap-3">
            <div className={`w-3 h-3 rounded-full ${isLoaded ? 'bg-green-500' : 'bg-zinc-400'}`} />
            <div>
              <p className="text-sm font-medium text-zinc-900 dark:text-zinc-100">
                navigator.modelContext API
              </p>
              <p className="text-xs text-zinc-600 dark:text-zinc-400">
                {isLoaded ? 'Loaded and ready' : 'Not detected'}
              </p>
            </div>
          </div>
          {isLoaded && <span className="text-xs px-2 py-1 rounded bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400">
              Active
            </span>}
        </div>
      </div>

      {!isLoaded && <div className="p-4 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800">
          <p className="text-sm font-semibold text-amber-900 dark:text-amber-200 mb-2">
            Polyfill Not Detected
          </p>
          <p className="text-sm text-amber-800 dark:text-amber-300 mb-3">
            Add the WebMCP polyfill to enable the API. Add this script tag to your HTML:
          </p>
          <div className="relative">
            <pre className="text-xs bg-white dark:bg-zinc-950 p-3 rounded border border-amber-300 dark:border-amber-700 overflow-x-auto">
              <code className="text-amber-900 dark:text-amber-100">
                {`<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>`}
              </code>
            </pre>
            <button onClick={copyScript} className="absolute top-2 right-2 px-2 py-1 text-xs bg-amber-200 dark:bg-amber-800 hover:bg-amber-300 dark:hover:bg-amber-700 text-amber-900 dark:text-amber-100 rounded transition-colors">
              Copy
            </button>
          </div>
        </div>}

      <div className="p-4 rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800">
        <p className="text-sm font-semibold text-blue-900 dark:text-blue-200 mb-2">
          Installation Options
        </p>
        <div className="space-y-2 text-sm text-blue-800 dark:text-blue-300">
          <div>
            <strong>Via CDN (Easiest):</strong>
            <pre className="text-xs mt-1 bg-white dark:bg-blue-950 p-2 rounded border border-blue-200 dark:border-blue-700 overflow-x-auto">
              <code>{`<script src="https://unpkg.com/@mcp-b/global@latest/dist/index.iife.js"></script>`}</code>
            </pre>
          </div>
          <div>
            <strong>Via NPM:</strong>
            <pre className="text-xs mt-1 bg-white dark:bg-blue-950 p-2 rounded border border-blue-200 dark:border-blue-700 overflow-x-auto">
              <code>{`npm install @mcp-b/global
import '@mcp-b/global';`}</code>
            </pre>
          </div>
        </div>
      </div>
    </div>;
};

export const DOMQueryTool = () => {
  const [selector, setSelector] = useState('h1');
  const [queryResult, setQueryResult] = useState(null);
  const [isRegistered, setIsRegistered] = useState(false);
  const [toolCalls, setToolCalls] = useState([]);
  const [executionPhase, setExecutionPhase] = useState(null);
  const [lastQuery, setLastQuery] = useState(null);
  const containerRef = useRef(null);
  const highlightOverlaysRef = useRef([]);
  const showPageEffect = () => {
    const overlay = document.createElement('div');
    overlay.id = 'webmcp-page-effect';
    overlay.style.cssText = `
      position: fixed;
      inset: 0;
      background: linear-gradient(180deg,
        rgba(139, 92, 246, 0.1) 0%,
        rgba(139, 92, 246, 0.02) 50%,
        rgba(139, 92, 246, 0.1) 100%);
      opacity: 0;
      pointer-events: none;
      z-index: 9998;
      transition: opacity 0.3s ease;
    `;
    const scanLine = document.createElement('div');
    scanLine.style.cssText = `
      position: absolute;
      left: 0;
      right: 0;
      height: 4px;
      background: linear-gradient(90deg, transparent, #8b5cf6, transparent);
      box-shadow: 0 0 20px #8b5cf6;
      animation: scanDown 1.5s ease-in-out infinite;
    `;
    overlay.appendChild(scanLine);
    const style = document.createElement('style');
    style.id = 'webmcp-scan-style';
    style.textContent = `
      @keyframes scanDown {
        0% { top: 0; opacity: 0; }
        10% { opacity: 1; }
        90% { opacity: 1; }
        100% { top: 100%; opacity: 0; }
      }
    `;
    document.head.appendChild(style);
    document.body.appendChild(overlay);
    requestAnimationFrame(() => {
      overlay.style.opacity = '1';
    });
    return overlay;
  };
  const hidePageEffect = () => {
    const overlay = document.getElementById('webmcp-page-effect');
    const style = document.getElementById('webmcp-scan-style');
    if (overlay) {
      overlay.style.opacity = '0';
      setTimeout(() => overlay.remove(), 300);
    }
    if (style) {
      setTimeout(() => style.remove(), 300);
    }
  };
  const startExecution = async onExecute => {
    setExecutionPhase('executing');
    showPageEffect();
    if (containerRef.current) {
      containerRef.current.scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      });
    }
    await new Promise(resolve => setTimeout(resolve, 1000));
    const result = await onExecute();
    setExecutionPhase('complete');
    hidePageEffect();
    await new Promise(resolve => setTimeout(resolve, 2500));
    setExecutionPhase(null);
    return result;
  };
  const highlightPageElements = selector => {
    clearHighlights();
    try {
      const elements = document.querySelectorAll(selector);
      const overlays = [];
      elements.forEach((el, idx) => {
        if (idx >= 5) return;
        const rect = el.getBoundingClientRect();
        const overlay = document.createElement('div');
        overlay.className = 'webmcp-highlight-overlay';
        overlay.style.cssText = `
          position: fixed;
          top: ${rect.top}px;
          left: ${rect.left}px;
          width: ${rect.width}px;
          height: ${rect.height}px;
          border: 3px solid #8b5cf6;
          background: rgba(139, 92, 246, 0.15);
          border-radius: 4px;
          pointer-events: none;
          z-index: 10000;
          animation: pulseHighlight 1.5s ease-in-out infinite;
          box-shadow: 0 0 20px rgba(139, 92, 246, 0.4);
        `;
        const label = document.createElement('div');
        label.style.cssText = `
          position: absolute;
          top: -24px;
          left: 0;
          background: #8b5cf6;
          color: white;
          padding: 2px 8px;
          font-size: 11px;
          font-weight: 600;
          border-radius: 4px;
          font-family: monospace;
        `;
        label.textContent = `${el.tagName.toLowerCase()}${idx > 0 ? ` [${idx + 1}]` : ''}`;
        overlay.appendChild(label);
        document.body.appendChild(overlay);
        overlays.push(overlay);
      });
      highlightOverlaysRef.current = overlays;
      if (!document.querySelector('#webmcp-highlight-styles')) {
        const style = document.createElement('style');
        style.id = 'webmcp-highlight-styles';
        style.textContent = `
          @keyframes pulseHighlight {
            0%, 100% { opacity: 1; transform: scale(1); }
            50% { opacity: 0.7; transform: scale(1.02); }
          }
          @keyframes scanLine {
            0% { top: 0; }
            100% { top: 100%; }
          }
        `;
        document.head.appendChild(style);
      }
      return elements.length;
    } catch (e) {
      console.error('Error highlighting elements:', e);
      return 0;
    }
  };
  const clearHighlights = () => {
    highlightOverlaysRef.current.forEach(overlay => {
      if (overlay.parentNode) {
        overlay.parentNode.removeChild(overlay);
      }
    });
    highlightOverlaysRef.current = [];
  };
  useEffect(() => {
    return () => clearHighlights();
  }, []);
  useEffect(() => {
    const registerTool = async () => {
      if (typeof window === 'undefined' || !window.navigator?.modelContext) {
        return;
      }
      try {
        await window.navigator.modelContext.registerTool({
          name: 'dom_query',
          description: 'Queries the page DOM using CSS selectors and returns element information',
          inputSchema: {
            type: 'object',
            properties: {
              selector: {
                type: 'string',
                description: 'CSS selector to query (e.g., "h1", ".nav-logo", "#content")'
              },
              action: {
                type: 'string',
                enum: ['count', 'text', 'attributes', 'all'],
                description: 'What information to return about matched elements',
                default: 'all'
              }
            },
            required: ['selector']
          },
          async execute(args) {
            try {
              const {selector, action = 'all'} = args || ({});
              if (selector === undefined || selector === null) {
                return {
                  content: [{
                    type: 'text',
                    text: 'Missing required parameter: selector'
                  }],
                  isError: true
                };
              }
              if (typeof selector !== 'string') {
                return {
                  content: [{
                    type: 'text',
                    text: `Invalid parameter type: selector must be a string, got ${typeof selector}`
                  }],
                  isError: true
                };
              }
              if (action && !['count', 'text', 'attributes', 'all'].includes(action)) {
                return {
                  content: [{
                    type: 'text',
                    text: `Invalid parameter value: action must be one of 'count', 'text', 'attributes', 'all', got '${action}'`
                  }],
                  isError: true
                };
              }
              return startExecution(async () => {
                try {
                  clearHighlights();
                  setToolCalls(prev => [...prev, {
                    time: new Date().toISOString(),
                    selector,
                    action,
                    status: 'processing'
                  }]);
                  const elements = document.querySelectorAll(selector);
                  highlightPageElements(selector);
                  const elementData = Array.from(elements).slice(0, 5).map(el => ({
                    tag: el.tagName.toLowerCase(),
                    text: el.textContent?.substring(0, 100) || '',
                    classes: Array.from(el.classList),
                    id: el.id || null
                  }));
                  setLastQuery({
                    selector,
                    count: elements.length,
                    elements: elementData
                  });
                  if (elements.length === 0) {
                    setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                      ...call,
                      count: 0,
                      status: 'success'
                    } : call));
                    setTimeout(() => clearHighlights(), 2000);
                    return {
                      content: [{
                        type: 'text',
                        text: `No elements found matching selector "${selector}"`
                      }]
                    };
                  }
                  let result = '';
                  if (action === 'count' || action === 'all') {
                    result += `Found ${elements.length} element(s)\n\n`;
                  }
                  if (action === 'text' || action === 'all') {
                    result += 'Text content:\n';
                    elementData.forEach((el, idx) => {
                      result += `  ${idx + 1}. ${el.text.substring(0, 80)}${el.text.length > 80 ? '...' : ''}\n`;
                    });
                    result += '\n';
                  }
                  if (action === 'attributes' || action === 'all') {
                    result += 'Elements:\n';
                    elementData.forEach((el, idx) => {
                      result += `  ${idx + 1}. <${el.tag}${el.id ? ` id="${el.id}"` : ''}${el.classes.length ? ` class="${el.classes.join(' ')}"` : ''}>\n`;
                    });
                  }
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    count: elements.length,
                    elements: elementData,
                    status: 'success'
                  } : call));
                  setTimeout(() => clearHighlights(), 2000);
                  return {
                    content: [{
                      type: 'text',
                      text: result.trim()
                    }]
                  };
                } catch (error) {
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    error: error.message,
                    status: 'error'
                  } : call));
                  clearHighlights();
                  return {
                    content: [{
                      type: 'text',
                      text: `Error querying DOM: ${error.message}`
                    }],
                    isError: true
                  };
                }
              });
            } catch (error) {
              return {
                content: [{
                  type: 'text',
                  text: `Error: ${error.message}`
                }],
                isError: true
              };
            }
          }
        });
        setIsRegistered(true);
      } catch (error) {
        console.error('Failed to register DOM query tool:', error);
      }
    };
    registerTool();
    window.addEventListener('webmcp-loaded', registerTool);
    return () => {
      window.removeEventListener('webmcp-loaded', registerTool);
      clearHighlights();
      if (window.navigator?.modelContext?.unregisterTool) {
        window.navigator.modelContext.unregisterTool('dom_query');
      }
    };
  }, []);
  const handleQuery = () => {
    try {
      const elements = document.querySelectorAll(selector);
      const elementData = Array.from(elements).slice(0, 10).map(el => ({
        tag: el.tagName.toLowerCase(),
        text: el.textContent?.substring(0, 100) || '',
        classes: Array.from(el.classList),
        id: el.id || null
      }));
      setQueryResult({
        count: elements.length,
        elements: elementData
      });
    } catch (error) {
      setQueryResult({
        error: error.message
      });
    }
  };
  const isActive = executionPhase !== null;
  return <div ref={containerRef} className={`not-prose border rounded-xl p-6 space-y-4 transition-all duration-300 relative ${isActive ? 'border-[#1F5EFF] shadow-lg shadow-[#1F5EFF]/10 ring-2 ring-[#1F5EFF]/20' : 'border-zinc-200 dark:border-white/10'}`}>
      {}
      {isActive && <div className="absolute top-0 left-0 right-0 h-1 bg-zinc-100 dark:bg-zinc-800 rounded-t-xl overflow-hidden">
          <div className={`h-full bg-[#1F5EFF] transition-all duration-500 ${executionPhase === 'executing' ? 'w-2/3 animate-pulse' : 'w-full'}`} />
        </div>}

      <div className="flex items-center justify-between">
        <div className="flex items-center gap-3">
          <h3 className="text-lg font-semibold text-zinc-950 dark:text-white">DOM Query Tool</h3>
          {isActive && <span className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-md bg-[#1F5EFF]/10 text-[#1F5EFF] dark:bg-[#1F5EFF]/20 dark:text-[#4B7BFF]">
              {executionPhase === 'executing' && <>
                  <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
                    <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
                    <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                  </svg>
                  Scanning...
                </>}
              {executionPhase === 'complete' && <>
                  <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                    <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                  </svg>
                  Complete
                </>}
            </span>}
        </div>
        {isRegistered && !isActive && <span className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-800">
            <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
            Ready
          </span>}
      </div>

      {}
      <div className="flex flex-wrap items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400">
        <span className="px-2 py-0.5 rounded bg-zinc-100 dark:bg-zinc-800 font-mono">
          querySelectorAll
        </span>
        <span className="px-2 py-0.5 rounded bg-zinc-100 dark:bg-zinc-800 font-mono">
          structured data
        </span>
        <span>Page introspection + Rich responses</span>
      </div>

      <div className="space-y-3">
        <div>
          <label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-2">
            CSS Selector
          </label>
          <div className="flex gap-2">
            <input type="text" value={selector} onChange={e => setSelector(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleQuery()} placeholder="h1, .nav-logo, #content" className="flex-1 px-4 py-2 rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 font-mono text-sm focus:ring-2 focus:ring-[#1F5EFF] focus:border-transparent transition-shadow" />
            <button onClick={handleQuery} className="px-6 py-2 bg-[#1F5EFF] hover:bg-[#1449CC] text-white font-medium rounded-lg transition-colors text-sm">
              Query
            </button>
          </div>
        </div>

        {}
        {(executionPhase === 'executing' || executionPhase === 'complete') && lastQuery && <div className="p-4 rounded-lg bg-[#1F5EFF]/5 dark:bg-[#1F5EFF]/10 border border-[#1F5EFF]/20">
            <div className="flex items-center justify-between mb-3">
              <p className="text-xs font-medium text-[#1F5EFF] dark:text-[#4B7BFF] uppercase tracking-wide">
                AI Result
              </p>
              <span className="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
                {lastQuery.count} element{lastQuery.count !== 1 ? 's' : ''} found
              </span>
            </div>
            {lastQuery.elements.length > 0 && <div className="space-y-1.5">
                {lastQuery.elements.slice(0, 3).map((el, idx) => <div key={idx} className="flex items-center gap-2 p-2 rounded bg-white/50 dark:bg-zinc-800/50">
                    <span className="w-5 h-5 rounded bg-[#1F5EFF]/20 text-[#1F5EFF] flex items-center justify-center text-xs font-medium">
                      {idx + 1}
                    </span>
                    <code className="text-xs text-zinc-700 dark:text-zinc-300 truncate">
                      &lt;{el.tag}
                      {el.id && ` #${el.id}`}
                      {el.classes.length > 0 && ` .${el.classes.slice(0, 2).join('.')}`}&gt;
                    </code>
                  </div>)}
                {lastQuery.elements.length > 3 && <p className="text-xs text-zinc-500 text-center pt-1">
                    +{lastQuery.elements.length - 3} more elements
                  </p>}
              </div>}
          </div>}

        {queryResult && !isActive && <div className="p-4 rounded-lg bg-zinc-100 dark:bg-zinc-800">
            {queryResult.error ? <p className="text-sm text-red-600 dark:text-red-400">Error: {queryResult.error}</p> : <div className="space-y-3">
                <p className="text-sm font-semibold text-zinc-900 dark:text-zinc-100">
                  Found {queryResult.count} element(s)
                </p>
                {queryResult.elements.length > 0 && <div className="space-y-1.5">
                    {queryResult.elements.slice(0, 5).map((el, idx) => <div key={idx} className="p-2 rounded bg-white dark:bg-zinc-900 border border-zinc-200 dark:border-zinc-700">
                        <code className="text-xs text-zinc-700 dark:text-zinc-300">
                          &lt;{el.tag}
                          {el.id && <span className="text-[#1F5EFF]"> #{el.id}</span>}
                          {el.classes.length > 0 && <span className="text-emerald-600 dark:text-emerald-400">
                              {' '}
                              .{el.classes.slice(0, 2).join('.')}
                            </span>}
                          &gt;
                        </code>
                        {el.text && <p className="text-xs text-zinc-500 truncate mt-1">
                            {el.text.substring(0, 60)}
                            {el.text.length > 60 ? '...' : ''}
                          </p>}
                      </div>)}
                    {queryResult.count > 5 && <p className="text-xs text-zinc-500 text-center">
                        Showing first 5 of {queryResult.count} elements
                      </p>}
                  </div>}
              </div>}
          </div>}
      </div>

      {toolCalls.length > 0 && <div className="mt-6 pt-4 border-t border-zinc-200 dark:border-zinc-800">
          <h4 className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 mb-3 uppercase tracking-wide">
            Recent Calls
          </h4>
          <div className="space-y-2 max-h-40 overflow-y-auto">
            {toolCalls.slice(-3).reverse().map((call, idx) => <div key={idx} className={`p-3 rounded-lg text-sm transition-all duration-200 ${call.status === 'processing' ? 'bg-[#1F5EFF]/5 dark:bg-[#1F5EFF]/10 border border-[#1F5EFF]/20' : call.status === 'success' ? 'bg-zinc-50 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700' : 'bg-red-50 dark:bg-red-900/10 border border-red-200 dark:border-red-800'}`}>
                  <div className="flex items-center justify-between">
                    <code className="text-zinc-700 dark:text-zinc-300 font-mono text-sm">
                      {call.selector}
                    </code>
                    <div className="flex items-center gap-2">
                      {call.count !== undefined && <span className="text-xs text-zinc-500 font-medium">
                          {call.count} found
                        </span>}
                      {call.status === 'processing' && <svg className="w-3.5 h-3.5 animate-spin text-[#1F5EFF]" fill="none" viewBox="0 0 24 24">
                          <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
                          <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                        </svg>}
                      {call.status === 'success' && <svg className="w-3.5 h-3.5 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                          <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                        </svg>}
                      {call.status === 'error' && <svg className="w-3.5 h-3.5 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                          <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
                        </svg>}
                    </div>
                  </div>
                  {call.error && <p className="text-xs text-red-600 dark:text-red-400 mt-1">{call.error}</p>}
                </div>)}
          </div>
        </div>}
    </div>;
};

export const StorageTool = () => {
  const [key, setKey] = useState('');
  const [value, setValue] = useState('');
  const [storedItems, setStoredItems] = useState({});
  const [isRegistered, setIsRegistered] = useState(false);
  const [toolCalls, setToolCalls] = useState([]);
  const [executionPhase, setExecutionPhase] = useState(null);
  const [activeOperation, setActiveOperation] = useState(null);
  const [lastAction, setLastAction] = useState(null);
  const containerRef = useRef(null);
  const showPageEffect = operation => {
    const overlay = document.createElement('div');
    overlay.id = 'webmcp-page-effect';
    const colors = {
      set: '#10b981',
      get: '#3b82f6',
      list: '#8b5cf6'
    };
    const color = colors[operation] || '#1F5EFF';
    overlay.style.cssText = `
      position: fixed;
      inset: 0;
      background: linear-gradient(135deg, ${color}22 0%, ${color}11 50%, ${color}22 100%);
      opacity: 0;
      pointer-events: none;
      z-index: 9999;
      transition: opacity 0.3s ease;
    `;
    document.body.appendChild(overlay);
    requestAnimationFrame(() => {
      overlay.style.opacity = '1';
    });
    return overlay;
  };
  const hidePageEffect = () => {
    const overlay = document.getElementById('webmcp-page-effect');
    if (overlay) {
      overlay.style.opacity = '0';
      setTimeout(() => overlay.remove(), 300);
    }
  };
  const startExecution = async (operation, onExecute) => {
    setExecutionPhase('executing');
    setActiveOperation(operation);
    showPageEffect(operation);
    if (containerRef.current) {
      containerRef.current.scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      });
    }
    await new Promise(resolve => setTimeout(resolve, 1000));
    const result = await onExecute();
    setExecutionPhase('complete');
    hidePageEffect();
    await new Promise(resolve => setTimeout(resolve, 2000));
    setExecutionPhase(null);
    setActiveOperation(null);
    return result;
  };
  const refreshStorage = () => {
    const items = {};
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      if (key && key.startsWith('webmcp_demo_')) {
        items[key] = localStorage.getItem(key);
      }
    }
    setStoredItems(items);
  };
  useEffect(() => {
    refreshStorage();
  }, []);
  useEffect(() => {
    const registerTool = async () => {
      if (typeof window === 'undefined' || !window.navigator?.modelContext) {
        return;
      }
      try {
        await window.navigator.modelContext.registerTool({
          name: 'storage_set',
          description: 'Stores a key-value pair in browser localStorage',
          inputSchema: {
            type: 'object',
            properties: {
              key: {
                type: 'string',
                description: 'Storage key (will be prefixed with webmcp_demo_)'
              },
              value: {
                type: 'string',
                description: 'Value to store'
              }
            },
            required: ['key', 'value']
          },
          async execute(args) {
            try {
              const {key, value} = args || ({});
              if (key === undefined || key === null) {
                return {
                  content: [{
                    type: 'text',
                    text: 'Missing required parameter: key'
                  }],
                  isError: true
                };
              }
              if (typeof key !== 'string') {
                return {
                  content: [{
                    type: 'text',
                    text: `Invalid parameter type: key must be a string, got ${typeof key}`
                  }],
                  isError: true
                };
              }
              if (value === undefined || value === null) {
                return {
                  content: [{
                    type: 'text',
                    text: 'Missing required parameter: value'
                  }],
                  isError: true
                };
              }
              if (typeof value !== 'string') {
                return {
                  content: [{
                    type: 'text',
                    text: `Invalid parameter type: value must be a string, got ${typeof value}`
                  }],
                  isError: true
                };
              }
              return startExecution('set', async () => {
                try {
                  const prefixedKey = `webmcp_demo_${key}`;
                  setToolCalls(prev => [...prev, {
                    time: new Date().toISOString(),
                    operation: 'set',
                    key: prefixedKey,
                    value,
                    status: 'processing'
                  }]);
                  localStorage.setItem(prefixedKey, value);
                  refreshStorage();
                  setLastAction({
                    type: 'set',
                    key,
                    value
                  });
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    status: 'success'
                  } : call));
                  return {
                    content: [{
                      type: 'text',
                      text: `Stored "${value}" under key "${key}"`
                    }]
                  };
                } catch (error) {
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    error: error.message,
                    status: 'error'
                  } : call));
                  return {
                    content: [{
                      type: 'text',
                      text: `Error storing data: ${error.message}`
                    }],
                    isError: true
                  };
                }
              });
            } catch (error) {
              return {
                content: [{
                  type: 'text',
                  text: `Error: ${error.message}`
                }],
                isError: true
              };
            }
          }
        });
        await window.navigator.modelContext.registerTool({
          name: 'storage_get',
          description: 'Retrieves a value from browser localStorage',
          inputSchema: {
            type: 'object',
            properties: {
              key: {
                type: 'string',
                description: 'Storage key to retrieve'
              }
            },
            required: ['key']
          },
          async execute(args) {
            try {
              const {key} = args || ({});
              if (key === undefined || key === null) {
                return {
                  content: [{
                    type: 'text',
                    text: 'Missing required parameter: key'
                  }],
                  isError: true
                };
              }
              if (typeof key !== 'string') {
                return {
                  content: [{
                    type: 'text',
                    text: `Invalid parameter type: key must be a string, got ${typeof key}`
                  }],
                  isError: true
                };
              }
              return startExecution('get', async () => {
                try {
                  const prefixedKey = `webmcp_demo_${key}`;
                  setToolCalls(prev => [...prev, {
                    time: new Date().toISOString(),
                    operation: 'get',
                    key: prefixedKey,
                    status: 'processing'
                  }]);
                  const value = localStorage.getItem(prefixedKey);
                  setLastAction({
                    type: 'get',
                    key,
                    value
                  });
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    value,
                    status: 'success'
                  } : call));
                  if (value === null) {
                    return {
                      content: [{
                        type: 'text',
                        text: `No value found for key "${key}"`
                      }]
                    };
                  }
                  return {
                    content: [{
                      type: 'text',
                      text: `Value for "${key}": ${value}`
                    }]
                  };
                } catch (error) {
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    error: error.message,
                    status: 'error'
                  } : call));
                  return {
                    content: [{
                      type: 'text',
                      text: `Error retrieving data: ${error.message}`
                    }],
                    isError: true
                  };
                }
              });
            } catch (error) {
              return {
                content: [{
                  type: 'text',
                  text: `Error: ${error.message}`
                }],
                isError: true
              };
            }
          }
        });
        await window.navigator.modelContext.registerTool({
          name: 'storage_list',
          description: 'Lists all stored keys and values',
          inputSchema: {
            type: 'object',
            properties: {}
          },
          async execute() {
            try {
              return startExecution('list', async () => {
                try {
                  setToolCalls(prev => [...prev, {
                    time: new Date().toISOString(),
                    operation: 'list',
                    status: 'processing'
                  }]);
                  const items = {};
                  for (let i = 0; i < localStorage.length; i++) {
                    const key = localStorage.key(i);
                    if (key && key.startsWith('webmcp_demo_')) {
                      const cleanKey = key.replace('webmcp_demo_', '');
                      items[cleanKey] = localStorage.getItem(key);
                    }
                  }
                  setLastAction({
                    type: 'list',
                    count: Object.keys(items).length
                  });
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    count: Object.keys(items).length,
                    status: 'success'
                  } : call));
                  const itemsList = Object.entries(items).map(([k, v]) => `  • ${k}: ${v}`).join('\n');
                  return {
                    content: [{
                      type: 'text',
                      text: `Stored items (${Object.keys(items).length}):\n${itemsList || '  (none)'}`
                    }]
                  };
                } catch (error) {
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    error: error.message,
                    status: 'error'
                  } : call));
                  return {
                    content: [{
                      type: 'text',
                      text: `Error listing data: ${error.message}`
                    }],
                    isError: true
                  };
                }
              });
            } catch (error) {
              return {
                content: [{
                  type: 'text',
                  text: `Error: ${error.message}`
                }],
                isError: true
              };
            }
          }
        });
        setIsRegistered(true);
      } catch (error) {
        console.error('Failed to register storage tools:', error);
      }
    };
    registerTool();
    window.addEventListener('webmcp-loaded', registerTool);
    return () => {
      window.removeEventListener('webmcp-loaded', registerTool);
      if (window.navigator?.modelContext?.unregisterTool) {
        window.navigator.modelContext.unregisterTool('storage_set');
        window.navigator.modelContext.unregisterTool('storage_get');
        window.navigator.modelContext.unregisterTool('storage_list');
      }
    };
  }, []);
  const handleStore = () => {
    if (!key || !value) return;
    const prefixedKey = `webmcp_demo_${key}`;
    localStorage.setItem(prefixedKey, value);
    refreshStorage();
    setKey('');
    setValue('');
  };
  const handleDelete = key => {
    localStorage.removeItem(key);
    refreshStorage();
  };
  const getOperationLabel = () => {
    switch (activeOperation) {
      case 'set':
        return 'Storing...';
      case 'get':
        return 'Reading...';
      case 'list':
        return 'Listing...';
      default:
        return 'Processing...';
    }
  };
  const isActive = executionPhase !== null;
  return <div ref={containerRef} className={`not-prose border rounded-xl p-6 space-y-4 transition-all duration-300 relative ${isActive ? 'border-[#1F5EFF] shadow-lg shadow-[#1F5EFF]/10 ring-2 ring-[#1F5EFF]/20' : 'border-zinc-200 dark:border-white/10'}`}>
      {}
      {isActive && <div className="absolute top-0 left-0 right-0 h-1 bg-zinc-100 dark:bg-zinc-800 rounded-t-xl overflow-hidden">
          <div className={`h-full bg-[#1F5EFF] transition-all duration-500 ${executionPhase === 'executing' ? 'w-2/3 animate-pulse' : 'w-full'}`} />
        </div>}

      <div className="flex items-center justify-between">
        <div className="flex items-center gap-3">
          <h3 className="text-lg font-semibold text-zinc-950 dark:text-white">
            Storage Management Tool
          </h3>
          {isActive && <span className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-md bg-[#1F5EFF]/10 text-[#1F5EFF] dark:bg-[#1F5EFF]/20 dark:text-[#4B7BFF]">
              {executionPhase === 'executing' && <>
                  <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
                    <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
                    <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                  </svg>
                  {getOperationLabel()}
                </>}
              {executionPhase === 'complete' && <>
                  <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                    <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                  </svg>
                  Complete
                </>}
            </span>}
        </div>
        {isRegistered && !isActive && <span className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-800">
            <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />3 Tools Ready
          </span>}
      </div>

      {}
      <div className="flex flex-wrap items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400">
        <span className="px-2 py-0.5 rounded bg-zinc-100 dark:bg-zinc-800 font-mono">
          Promise.all()
        </span>
        <span className="px-2 py-0.5 rounded bg-zinc-100 dark:bg-zinc-800 font-mono">
          localStorage
        </span>
        <span>Multiple tools + Browser API integration</span>
      </div>

      <div className="space-y-3">
        <div className="grid grid-cols-2 gap-2">
          <input type="text" value={key} onChange={e => setKey(e.target.value)} placeholder="Key" className="px-3 py-2 rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 text-sm focus:ring-2 focus:ring-[#1F5EFF] focus:border-transparent transition-shadow" />
          <input type="text" value={value} onChange={e => setValue(e.target.value)} placeholder="Value" className="px-3 py-2 rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 text-sm focus:ring-2 focus:ring-[#1F5EFF] focus:border-transparent transition-shadow" />
        </div>

        <button onClick={handleStore} disabled={!key || !value} className="w-full px-4 py-2 bg-[#1F5EFF] hover:bg-[#1449CC] disabled:bg-zinc-400 disabled:cursor-not-allowed text-white font-medium rounded-lg transition-colors text-sm">
          Store Value
        </button>

        {}
        {(executionPhase === 'executing' || executionPhase === 'complete') && lastAction && <div className="p-4 rounded-lg bg-[#1F5EFF]/5 dark:bg-[#1F5EFF]/10 border border-[#1F5EFF]/20">
            <p className="text-xs font-medium text-[#1F5EFF] dark:text-[#4B7BFF] mb-2 uppercase tracking-wide">
              AI{' '}
              {lastAction.type === 'set' ? 'Stored' : lastAction.type === 'get' ? 'Retrieved' : 'Listed'}
            </p>
            {lastAction.type === 'set' && <div className="font-mono text-sm text-zinc-900 dark:text-zinc-100">
                <span className="text-zinc-500">{lastAction.key}</span> ={' '}
                <span className="font-semibold">{lastAction.value}</span>
              </div>}
            {lastAction.type === 'get' && <div className="font-mono text-sm text-zinc-900 dark:text-zinc-100">
                <span className="text-zinc-500">{lastAction.key}</span> ={' '}
                <span className="font-semibold">{lastAction.value || '(not found)'}</span>
              </div>}
            {lastAction.type === 'list' && <div className="font-mono text-sm text-zinc-900 dark:text-zinc-100">
                <span className="font-semibold">{lastAction.count}</span> items in storage
              </div>}
          </div>}
      </div>

      {Object.keys(storedItems).length > 0 && <div className="mt-4">
          <h4 className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 mb-2 uppercase tracking-wide flex items-center gap-2">
            <svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
              <path strokeLinecap="round" strokeLinejoin="round" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4" />
            </svg>
            Stored Items ({Object.keys(storedItems).length})
          </h4>
          <div className="space-y-1.5">
            {Object.entries(storedItems).map(([k, v]) => <div key={k} className="flex items-center justify-between p-2.5 rounded-lg bg-zinc-50 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700">
                <div className="flex-1 min-w-0">
                  <p className="text-sm font-medium text-zinc-700 dark:text-zinc-300 truncate font-mono">
                    {k.replace('webmcp_demo_', '')}
                  </p>
                  <p className="text-xs text-zinc-500 truncate">{v}</p>
                </div>
                <button onClick={() => handleDelete(k)} className="ml-2 px-2 py-1 text-xs text-zinc-500 hover:text-red-600 dark:hover:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 rounded transition-colors">
                  ×
                </button>
              </div>)}
          </div>
        </div>}

      {Object.keys(storedItems).length === 0 && <div className="mt-4 p-4 rounded-lg bg-zinc-50 dark:bg-zinc-900 border border-dashed border-zinc-300 dark:border-zinc-700 text-center">
          <p className="text-sm text-zinc-500">No items stored yet</p>
          <p className="text-xs text-zinc-400 mt-1">Ask AI to store something!</p>
        </div>}

      {toolCalls.length > 0 && <div className="mt-6 pt-4 border-t border-zinc-200 dark:border-zinc-800">
          <h4 className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 mb-3 uppercase tracking-wide">
            Recent Calls
          </h4>
          <div className="space-y-2 max-h-40 overflow-y-auto">
            {toolCalls.slice(-3).reverse().map((call, idx) => <div key={idx} className={`p-3 rounded-lg text-sm transition-all duration-200 ${call.status === 'processing' ? 'bg-[#1F5EFF]/5 dark:bg-[#1F5EFF]/10 border border-[#1F5EFF]/20' : call.status === 'success' ? 'bg-zinc-50 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700' : 'bg-red-50 dark:bg-red-900/10 border border-red-200 dark:border-red-800'}`}>
                  <div className="flex items-center justify-between">
                    <span className="font-mono text-sm text-zinc-700 dark:text-zinc-300">
                      storage_{call.operation}
                    </span>
                    <div className="flex items-center gap-2">
                      {call.status === 'processing' && <svg className="w-3.5 h-3.5 animate-spin text-[#1F5EFF]" fill="none" viewBox="0 0 24 24">
                          <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
                          <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                        </svg>}
                      {call.status === 'success' && <svg className="w-3.5 h-3.5 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                          <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                        </svg>}
                      {call.status === 'error' && <svg className="w-3.5 h-3.5 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                          <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
                        </svg>}
                    </div>
                  </div>
                  {call.key && <p className="text-xs text-zinc-500 mt-1 font-mono">
                      {call.key.replace('webmcp_demo_', '')}
                      {call.value !== undefined ? ` = ${call.value}` : ''}
                    </p>}
                  {call.count !== undefined && <p className="text-xs text-zinc-500 mt-1">{call.count} items</p>}
                  {call.error && <p className="text-xs text-red-600 dark:text-red-400 mt-1">{call.error}</p>}
                </div>)}
          </div>
        </div>}
    </div>;
};

export const ColorConverterTool = () => {
  const [hexInput, setHexInput] = useState('#3b82f6');
  const [rgbOutput, setRgbOutput] = useState('');
  const [hslOutput, setHslOutput] = useState('');
  const [isRegistered, setIsRegistered] = useState(false);
  const [toolCalls, setToolCalls] = useState([]);
  const [executionPhase, setExecutionPhase] = useState(null);
  const [lastConversion, setLastConversion] = useState(null);
  const containerRef = useRef(null);
  const showPageEffect = color => {
    const overlay = document.createElement('div');
    overlay.id = 'webmcp-page-effect';
    overlay.style.cssText = `
      position: fixed;
      inset: 0;
      background: ${color};
      opacity: 0;
      pointer-events: none;
      z-index: 9999;
      transition: opacity 0.4s ease;
    `;
    document.body.appendChild(overlay);
    requestAnimationFrame(() => {
      overlay.style.opacity = '0.15';
    });
    return overlay;
  };
  const hidePageEffect = () => {
    const overlay = document.getElementById('webmcp-page-effect');
    if (overlay) {
      overlay.style.opacity = '0';
      setTimeout(() => overlay.remove(), 400);
    }
  };
  const startExecution = async (color, onExecute) => {
    setExecutionPhase('executing');
    const hex = color?.startsWith('#') ? color : `#${color}`;
    showPageEffect(hex);
    if (containerRef.current) {
      containerRef.current.scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      });
    }
    await new Promise(resolve => setTimeout(resolve, 1000));
    const result = await onExecute();
    setExecutionPhase('complete');
    hidePageEffect();
    await new Promise(resolve => setTimeout(resolve, 2000));
    setExecutionPhase(null);
    return result;
  };
  const hexToRgb = hex => {
    const result = (/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i).exec(hex);
    return result ? {
      r: Number.parseInt(result[1], 16),
      g: Number.parseInt(result[2], 16),
      b: Number.parseInt(result[3], 16)
    } : null;
  };
  const rgbToHsl = (r, g, b) => {
    r /= 255;
    g /= 255;
    b /= 255;
    const max = Math.max(r, g, b);
    const min = Math.min(r, g, b);
    let h, s, l = (max + min) / 2;
    if (max === min) {
      h = s = 0;
    } else {
      const d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      switch (max) {
        case r:
          h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
          break;
        case g:
          h = ((b - r) / d + 2) / 6;
          break;
        case b:
          h = ((r - g) / d + 4) / 6;
          break;
      }
    }
    return {
      h: Math.round(h * 360),
      s: Math.round(s * 100),
      l: Math.round(l * 100)
    };
  };
  const generatePalette = hex => {
    const rgb = hexToRgb(hex);
    if (!rgb) return [];
    const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
    const palette = [];
    const hues = [0, 180, 30, -30, 60].map(offset => (hsl.h + offset + 360) % 360);
    hues.forEach(h => {
      const hNorm = h / 360;
      const sNorm = hsl.s / 100;
      const lNorm = hsl.l / 100;
      let r, g, b;
      if (sNorm === 0) {
        r = g = b = lNorm;
      } else {
        const hue2rgb = (p, q, t) => {
          if (t < 0) t += 1;
          if (t > 1) t -= 1;
          if (t < 1 / 6) return p + (q - p) * 6 * t;
          if (t < 1 / 2) return q;
          if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
          return p;
        };
        const q = lNorm < 0.5 ? lNorm * (1 + sNorm) : lNorm + sNorm - lNorm * sNorm;
        const p = 2 * lNorm - q;
        r = hue2rgb(p, q, hNorm + 1 / 3);
        g = hue2rgb(p, q, hNorm);
        b = hue2rgb(p, q, hNorm - 1 / 3);
      }
      const toHex = x => Math.round(x * 255).toString(16).padStart(2, '0');
      palette.push(`#${toHex(r)}${toHex(g)}${toHex(b)}`);
    });
    return palette;
  };
  useEffect(() => {
    const registerTool = async () => {
      if (typeof window === 'undefined' || !window.navigator?.modelContext) {
        return;
      }
      try {
        await window.navigator.modelContext.registerTool({
          name: 'color_converter',
          description: 'Converts HEX colors to RGB and HSL formats. Input must be in HEX format.',
          inputSchema: {
            type: 'object',
            properties: {
              color: {
                type: 'string',
                description: 'Color in HEX format (e.g., "#3b82f6" or "3b82f6"). Must be a valid HEX color code.'
              },
              outputFormat: {
                type: 'string',
                enum: ['rgb', 'hsl', 'all'],
                description: 'Desired output format (rgb, hsl, or all)',
                default: 'all'
              }
            },
            required: ['color']
          },
          async execute(args) {
            try {
              const {color, outputFormat = 'all'} = args || ({});
              if (color === undefined || color === null) {
                return {
                  content: [{
                    type: 'text',
                    text: 'Missing required parameter: color'
                  }],
                  isError: true
                };
              }
              if (typeof color !== 'string') {
                return {
                  content: [{
                    type: 'text',
                    text: `Invalid parameter type: color must be a string, got ${typeof color}`
                  }],
                  isError: true
                };
              }
              if (outputFormat && !['rgb', 'hsl', 'all'].includes(outputFormat)) {
                return {
                  content: [{
                    type: 'text',
                    text: `Invalid parameter value: outputFormat must be one of 'rgb', 'hsl', 'all', got '${outputFormat}'`
                  }],
                  isError: true
                };
              }
              return startExecution(color, async () => {
                try {
                  setToolCalls(prev => [...prev, {
                    time: new Date().toISOString(),
                    color,
                    outputFormat,
                    status: 'processing'
                  }]);
                  const hex = color.startsWith('#') ? color : `#${color}`;
                  const rgb = hexToRgb(hex);
                  if (!rgb) {
                    throw new Error('Invalid HEX color format');
                  }
                  const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
                  const rgbStr = `rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`;
                  const hslStr = `hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`;
                  setLastConversion({
                    hex,
                    rgb: rgbStr,
                    hsl: hslStr
                  });
                  let result;
                  if (outputFormat === 'rgb') {
                    result = rgbStr;
                  } else if (outputFormat === 'hsl') {
                    result = hslStr;
                  } else {
                    result = `RGB: ${rgbStr}\nHSL: ${hslStr}`;
                  }
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    result: {
                      rgb: rgbStr,
                      hsl: hslStr
                    },
                    status: 'success'
                  } : call));
                  return {
                    content: [{
                      type: 'text',
                      text: `Color ${hex} converted:\n${result}`
                    }]
                  };
                } catch (error) {
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    error: error.message,
                    status: 'error'
                  } : call));
                  return {
                    content: [{
                      type: 'text',
                      text: `Error converting color: ${error.message}`
                    }],
                    isError: true
                  };
                }
              });
            } catch (error) {
              return {
                content: [{
                  type: 'text',
                  text: `Error: ${error.message}`
                }],
                isError: true
              };
            }
          }
        });
        setIsRegistered(true);
      } catch (error) {
        console.error('Failed to register color converter tool:', error);
      }
    };
    registerTool();
    window.addEventListener('webmcp-loaded', registerTool);
    return () => {
      window.removeEventListener('webmcp-loaded', registerTool);
      if (window.navigator?.modelContext?.unregisterTool) {
        window.navigator.modelContext.unregisterTool('color_converter');
      }
    };
  }, []);
  const handleConvert = () => {
    try {
      const hex = hexInput.startsWith('#') ? hexInput : `#${hexInput}`;
      const rgb = hexToRgb(hex);
      if (!rgb) {
        setRgbOutput('Invalid HEX color');
        setHslOutput('Invalid HEX color');
        return;
      }
      const hsl = rgbToHsl(rgb.r, rgb.g, rgb.b);
      setRgbOutput(`rgb(${rgb.r}, ${rgb.g}, ${rgb.b})`);
      setHslOutput(`hsl(${hsl.h}, ${hsl.s}%, ${hsl.l}%)`);
    } catch (error) {
      setRgbOutput(`Error: ${error.message}`);
      setHslOutput(`Error: ${error.message}`);
    }
  };
  useEffect(() => {
    handleConvert();
  }, [hexInput]);
  const isActive = executionPhase !== null;
  return <div ref={containerRef} className={`not-prose border rounded-xl p-6 space-y-4 transition-all duration-300 relative ${isActive ? 'border-[#1F5EFF] shadow-lg shadow-[#1F5EFF]/10 ring-2 ring-[#1F5EFF]/20' : 'border-zinc-200 dark:border-white/10'}`}>
      {}
      {isActive && <div className="absolute top-0 left-0 right-0 h-1 bg-zinc-100 dark:bg-zinc-800 rounded-t-xl overflow-hidden">
          <div className={`h-full bg-[#1F5EFF] transition-all duration-500 ${executionPhase === 'executing' ? 'w-2/3 animate-pulse' : 'w-full'}`} />
        </div>}

      <div className="flex items-center justify-between">
        <div className="flex items-center gap-3">
          <h3 className="text-lg font-semibold text-zinc-950 dark:text-white">
            Color Converter Tool
          </h3>
          {isActive && <span className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-md bg-[#1F5EFF]/10 text-[#1F5EFF] dark:bg-[#1F5EFF]/20 dark:text-[#4B7BFF]">
              {executionPhase === 'executing' && <>
                  <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
                    <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
                    <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                  </svg>
                  Converting...
                </>}
              {executionPhase === 'complete' && <>
                  <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                    <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                  </svg>
                  Complete
                </>}
            </span>}
        </div>
        {isRegistered && !isActive && <span className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-800">
            <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
            Ready
          </span>}
      </div>

      {}
      <div className="flex flex-wrap items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400">
        <span className="px-2 py-0.5 rounded bg-zinc-100 dark:bg-zinc-800 font-mono">enum</span>
        <span className="px-2 py-0.5 rounded bg-zinc-100 dark:bg-zinc-800 font-mono">default</span>
        <span className="px-2 py-0.5 rounded bg-zinc-100 dark:bg-zinc-800 font-mono">optional</span>
        <span>Complex schemas with enums and defaults</span>
      </div>

      <div className="space-y-3">
        <div>
          <label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-2">
            HEX Color
          </label>
          <div className="flex gap-2">
            <div className="w-12 h-12 rounded-lg border-2 border-zinc-300 dark:border-zinc-700 flex-shrink-0 transition-all duration-300" style={{
    backgroundColor: hexInput
  }} />
            <input type="text" value={hexInput} onChange={e => setHexInput(e.target.value)} placeholder="#3b82f6" className="flex-1 px-4 py-2 rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 font-mono focus:ring-2 focus:ring-[#1F5EFF] focus:border-transparent transition-shadow" />
          </div>
        </div>

        {}
        {(executionPhase === 'executing' || executionPhase === 'complete') && lastConversion && <div className="p-4 rounded-lg bg-[#1F5EFF]/5 dark:bg-[#1F5EFF]/10 border border-[#1F5EFF]/20">
            <div className="flex items-center gap-3 mb-3">
              <div className="w-10 h-10 rounded-lg border-2 border-[#1F5EFF]/30" style={{
    backgroundColor: lastConversion.hex
  }} />
              <div>
                <p className="text-xs font-medium text-[#1F5EFF] dark:text-[#4B7BFF] uppercase tracking-wide">
                  AI Result
                </p>
                <p className="font-mono text-sm text-zinc-900 dark:text-zinc-100">
                  {lastConversion.hex}
                </p>
              </div>
            </div>
            <div className="grid grid-cols-2 gap-2">
              <div className="p-2 rounded bg-white/50 dark:bg-zinc-800/50">
                <p className="text-xs text-zinc-500 dark:text-zinc-400 mb-0.5">RGB</p>
                <p className="font-mono text-xs text-zinc-900 dark:text-zinc-100">
                  {lastConversion.rgb}
                </p>
              </div>
              <div className="p-2 rounded bg-white/50 dark:bg-zinc-800/50">
                <p className="text-xs text-zinc-500 dark:text-zinc-400 mb-0.5">HSL</p>
                <p className="font-mono text-xs text-zinc-900 dark:text-zinc-100">
                  {lastConversion.hsl}
                </p>
              </div>
            </div>
          </div>}

        {rgbOutput && !isActive && <div className="grid grid-cols-2 gap-2">
            <div className="p-3 rounded-lg bg-zinc-100 dark:bg-zinc-800">
              <p className="text-xs font-medium text-zinc-600 dark:text-zinc-400 mb-1">RGB</p>
              <p className="font-mono text-sm text-zinc-900 dark:text-zinc-100">{rgbOutput}</p>
            </div>
            <div className="p-3 rounded-lg bg-zinc-100 dark:bg-zinc-800">
              <p className="text-xs font-medium text-zinc-600 dark:text-zinc-400 mb-1">HSL</p>
              <p className="font-mono text-sm text-zinc-900 dark:text-zinc-100">{hslOutput}</p>
            </div>
          </div>}
      </div>

      {toolCalls.length > 0 && <div className="mt-6 pt-4 border-t border-zinc-200 dark:border-zinc-800">
          <h4 className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 mb-3 uppercase tracking-wide">
            Recent Calls
          </h4>
          <div className="space-y-2 max-h-40 overflow-y-auto">
            {toolCalls.slice(-3).reverse().map((call, idx) => <div key={idx} className={`p-3 rounded-lg text-sm transition-all duration-200 ${call.status === 'processing' ? 'bg-[#1F5EFF]/5 dark:bg-[#1F5EFF]/10 border border-[#1F5EFF]/20' : call.status === 'success' ? 'bg-zinc-50 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700' : 'bg-red-50 dark:bg-red-900/10 border border-red-200 dark:border-red-800'}`}>
                  <div className="flex items-center justify-between">
                    <div className="flex items-center gap-2">
                      <div className="w-6 h-6 rounded border border-zinc-300 dark:border-zinc-600" style={{
    backgroundColor: call.color
  }} />
                      <code className="text-zinc-700 dark:text-zinc-300 font-mono text-sm">
                        {call.color}
                      </code>
                    </div>
                    <div className="flex items-center gap-2">
                      {call.status === 'processing' && <svg className="w-3.5 h-3.5 animate-spin text-[#1F5EFF]" fill="none" viewBox="0 0 24 24">
                          <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
                          <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                        </svg>}
                      {call.status === 'success' && <svg className="w-3.5 h-3.5 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                          <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                        </svg>}
                      {call.status === 'error' && <svg className="w-3.5 h-3.5 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                          <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
                        </svg>}
                    </div>
                  </div>
                  {call.error && <p className="text-xs text-red-600 dark:text-red-400 mt-1">{call.error}</p>}
                </div>)}
          </div>
        </div>}
    </div>;
};

export const CalculatorTool = () => {
  const [result, setResult] = useState('');
  const [expression, setExpression] = useState('2 + 2');
  const [isRegistered, setIsRegistered] = useState(false);
  const [toolCalls, setToolCalls] = useState([]);
  const [executionPhase, setExecutionPhase] = useState(null);
  const [lastResult, setLastResult] = useState(null);
  const containerRef = useRef(null);
  const showPageEffect = (color = '#1F5EFF') => {
    const overlay = document.createElement('div');
    overlay.id = 'webmcp-page-effect';
    overlay.style.cssText = `
      position: fixed;
      inset: 0;
      background: ${color};
      opacity: 0;
      pointer-events: none;
      z-index: 9999;
      transition: opacity 0.3s ease;
    `;
    document.body.appendChild(overlay);
    requestAnimationFrame(() => {
      overlay.style.opacity = '0.08';
    });
    return overlay;
  };
  const hidePageEffect = () => {
    const overlay = document.getElementById('webmcp-page-effect');
    if (overlay) {
      overlay.style.opacity = '0';
      setTimeout(() => overlay.remove(), 300);
    }
  };
  const startExecution = async onExecute => {
    setExecutionPhase('executing');
    const overlay = showPageEffect('#1F5EFF');
    if (containerRef.current) {
      containerRef.current.scrollIntoView({
        behavior: 'smooth',
        block: 'center'
      });
    }
    await new Promise(resolve => setTimeout(resolve, 1000));
    const result = await onExecute();
    setExecutionPhase('complete');
    hidePageEffect();
    await new Promise(resolve => setTimeout(resolve, 2000));
    setExecutionPhase(null);
    return result;
  };
  useEffect(() => {
    const registerTool = async () => {
      if (typeof window === 'undefined' || !window.navigator?.modelContext) {
        return;
      }
      try {
        await window.navigator.modelContext.registerTool({
          name: 'calculator',
          description: 'Performs mathematical calculations. Supports basic arithmetic operations (+, -, *, /) and common math functions.',
          inputSchema: {
            type: 'object',
            properties: {
              expression: {
                type: 'string',
                description: 'Mathematical expression to evaluate (e.g., "2 + 2", "sqrt(16)", "pow(2, 3)")'
              }
            },
            required: ['expression']
          },
          async execute(args) {
            try {
              const {expression} = args || ({});
              if (expression === undefined || expression === null) {
                return {
                  content: [{
                    type: 'text',
                    text: 'Missing required parameter: expression'
                  }],
                  isError: true
                };
              }
              if (typeof expression !== 'string') {
                return {
                  content: [{
                    type: 'text',
                    text: `Invalid parameter type: expression must be a string, got ${typeof expression}`
                  }],
                  isError: true
                };
              }
              return startExecution(async () => {
                try {
                  setToolCalls(prev => [...prev, {
                    time: new Date().toISOString(),
                    expression,
                    status: 'processing'
                  }]);
                  const sanitized = expression.replace(/[^0-9+\-*/().,\s]/g, '').replace(/Math\./g, '');
                  const result = Function(`"use strict"; return (${sanitized})`)();
                  setLastResult(result);
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    result,
                    status: 'success'
                  } : call));
                  return {
                    content: [{
                      type: 'text',
                      text: `The result of ${expression} is ${result}`
                    }]
                  };
                } catch (error) {
                  setToolCalls(prev => prev.map((call, idx) => idx === prev.length - 1 ? {
                    ...call,
                    error: error.message,
                    status: 'error'
                  } : call));
                  return {
                    content: [{
                      type: 'text',
                      text: `Error evaluating expression: ${error.message}`
                    }],
                    isError: true
                  };
                }
              });
            } catch (error) {
              return {
                content: [{
                  type: 'text',
                  text: `Error: ${error.message}`
                }],
                isError: true
              };
            }
          }
        });
        setIsRegistered(true);
      } catch (error) {
        console.error('Failed to register calculator tool:', error);
      }
    };
    registerTool();
    window.addEventListener('webmcp-loaded', registerTool);
    return () => {
      window.removeEventListener('webmcp-loaded', registerTool);
      if (window.navigator?.modelContext?.unregisterTool) {
        window.navigator.modelContext.unregisterTool('calculator');
      }
    };
  }, []);
  const handleCalculate = () => {
    try {
      const sanitized = expression.replace(/[^0-9+\-*/().,\s]/g, '').replace(/Math\./g, '');
      const calcResult = Function(`"use strict"; return (${sanitized})`)();
      setResult(calcResult.toString());
    } catch (error) {
      setResult(`Error: ${error.message}`);
    }
  };
  const isActive = executionPhase !== null;
  return <div ref={containerRef} className={`not-prose border rounded-xl p-6 space-y-4 transition-all duration-300 relative ${isActive ? 'border-[#1F5EFF] shadow-lg shadow-[#1F5EFF]/10 ring-2 ring-[#1F5EFF]/20' : 'border-zinc-200 dark:border-white/10'}`}>
      {}
      {isActive && <div className="absolute top-0 left-0 right-0 h-1 bg-zinc-100 dark:bg-zinc-800 rounded-t-xl overflow-hidden">
          <div className={`h-full bg-[#1F5EFF] transition-all duration-500 ${executionPhase === 'executing' ? 'w-2/3 animate-pulse' : 'w-full'}`} />
        </div>}

      <div className="flex items-center justify-between">
        <div className="flex items-center gap-3">
          <h3 className="text-lg font-semibold text-zinc-950 dark:text-white">Calculator Tool</h3>
          {isActive && <span className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium rounded-md bg-[#1F5EFF]/10 text-[#1F5EFF] dark:bg-[#1F5EFF]/20 dark:text-[#4B7BFF]">
              {executionPhase === 'executing' && <>
                  <svg className="w-4 h-4 animate-spin" fill="none" viewBox="0 0 24 24">
                    <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
                    <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                  </svg>
                  Computing...
                </>}
              {executionPhase === 'complete' && <>
                  <svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                    <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                  </svg>
                  Complete
                </>}
            </span>}
        </div>
        {isRegistered && !isActive && <span className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-md bg-emerald-50 dark:bg-emerald-900/20 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-800">
            <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
            Ready
          </span>}
      </div>

      {}
      <div className="flex items-center gap-2 text-xs text-zinc-500 dark:text-zinc-400">
        <span className="px-2 py-0.5 rounded bg-zinc-100 dark:bg-zinc-800 font-mono">
          registerTool()
        </span>
        <span>Basic tool registration with simple input schema</span>
      </div>

      {!isRegistered && !isActive && <div className="p-3 rounded-lg bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800">
          <p className="text-sm text-amber-800 dark:text-amber-200">
            WebMCP not detected. Install the MCP-B extension to enable AI agent integration.
          </p>
        </div>}

      <div className="space-y-3">
        <div>
          <label className="block text-sm font-medium text-zinc-700 dark:text-zinc-300 mb-2">
            Expression
          </label>
          <input type="text" value={expression} onChange={e => setExpression(e.target.value)} onKeyDown={e => e.key === 'Enter' && handleCalculate()} placeholder="Enter expression (e.g., 2 + 2 * 3)" className="w-full px-4 py-2 rounded-lg border border-zinc-300 dark:border-zinc-700 bg-white dark:bg-zinc-900 text-zinc-900 dark:text-zinc-100 focus:ring-2 focus:ring-[#1F5EFF] focus:border-transparent transition-shadow" />
        </div>

        <button onClick={handleCalculate} className="w-full px-4 py-2 bg-[#1F5EFF] hover:bg-[#1449CC] text-white font-medium rounded-lg transition-colors">
          Calculate
        </button>

        {}
        {(executionPhase === 'executing' || executionPhase === 'complete') && lastResult !== null && <div className="p-4 rounded-lg bg-[#1F5EFF]/5 dark:bg-[#1F5EFF]/10 border border-[#1F5EFF]/20">
              <p className="text-xs font-medium text-[#1F5EFF] dark:text-[#4B7BFF] mb-1 uppercase tracking-wide">
                AI Result
              </p>
              <p className="text-2xl font-bold font-mono text-zinc-900 dark:text-zinc-100">
                {lastResult}
              </p>
            </div>}

        {result && !isActive && <div className="p-4 rounded-lg bg-zinc-100 dark:bg-zinc-800">
            <p className="text-sm font-medium text-zinc-600 dark:text-zinc-400 mb-1">Result:</p>
            <p className="text-2xl font-bold text-zinc-900 dark:text-zinc-100">{result}</p>
          </div>}
      </div>

      {toolCalls.length > 0 && <div className="mt-6 pt-4 border-t border-zinc-200 dark:border-zinc-800">
          <h4 className="text-xs font-semibold text-zinc-500 dark:text-zinc-400 mb-3 uppercase tracking-wide">
            Recent Calls
          </h4>
          <div className="space-y-2 max-h-40 overflow-y-auto">
            {toolCalls.slice(-3).reverse().map((call, idx) => <div key={idx} className={`p-3 rounded-lg text-sm transition-all duration-200 ${call.status === 'processing' ? 'bg-[#1F5EFF]/5 dark:bg-[#1F5EFF]/10 border border-[#1F5EFF]/20' : call.status === 'success' ? 'bg-zinc-50 dark:bg-zinc-800/50 border border-zinc-200 dark:border-zinc-700' : 'bg-red-50 dark:bg-red-900/10 border border-red-200 dark:border-red-800'}`}>
                  <div className="flex items-center justify-between">
                    <code className="text-zinc-700 dark:text-zinc-300 font-mono text-sm">
                      {call.expression}
                    </code>
                    <div className="flex items-center gap-2">
                      {call.result !== undefined && <span className="font-mono font-semibold text-zinc-900 dark:text-zinc-100">
                          = {call.result}
                        </span>}
                      {call.status === 'processing' && <svg className="w-3.5 h-3.5 animate-spin text-[#1F5EFF]" fill="none" viewBox="0 0 24 24">
                          <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="3" />
                          <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
                        </svg>}
                      {call.status === 'success' && <svg className="w-3.5 h-3.5 text-emerald-600 dark:text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                          <path strokeLinecap="round" strokeLinejoin="round" d="M5 13l4 4L19 7" />
                        </svg>}
                      {call.status === 'error' && <svg className="w-3.5 h-3.5 text-red-600 dark:text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth="2">
                          <path strokeLinecap="round" strokeLinejoin="round" d="M6 18L18 6M6 6l12 12" />
                        </svg>}
                    </div>
                  </div>
                  {call.error && <p className="text-xs text-red-600 dark:text-red-400 mt-1">{call.error}</p>}
                </div>)}
          </div>
        </div>}
    </div>;
};

This page demonstrates **live WebMCP tools** that register themselves with the browser and can be called by AI agents in real-time. Each tool showcases a different WebMCP capability.

<Note>
  **These tools are live and ready!** The WebMCP polyfill is included with this documentation site. Install the [MCP-B browser extension](https://chromewebstore.google.com/detail/mcp-b-extension/daohopfhkdelnpemnhlekblhnikhdhfa) to enable AI agents (like Claude) to discover and call these tools directly.
</Note>

<CardGroup cols={3}>
  <Card title="Build Your Own" icon="code" href="/best-practices">
    Learn how to create custom tools
  </Card>

  <Card title="Tool Registration" icon="list-check" href="/concepts/tool-registration">
    Understanding tool registration
  </Card>

  <Card title="WebMCP SDK" icon="package" href="/packages/webmcp-ts-sdk">
    SDK reference documentation
  </Card>
</CardGroup>

***

## WebMCP Status

Check if the WebMCP polyfill is loaded and ready:

<PolyfillSetup />

***

## Calculator Tool

**Demonstrates:** Basic tool registration with a simple input schema

This tool shows the fundamentals of `registerTool()` - a single required parameter and straightforward execution. Watch the animated result display when AI computes your expression!

<CalculatorTool />

<Tip>
  **Try it with your AI assistant:** Ask Claude to "use the calculator tool to compute 42 \* 1.5 + 10" and watch the animated result appear!
</Tip>

<CodeGroup>
  ```jsx Calculator Tool Implementation theme={null}
  export const CalculatorTool = () => {
    const [isRegistered, setIsRegistered] = useState(false);
    const [toolCalls, setToolCalls] = useState([]);

    useEffect(() => {
      const registerTool = async () => {
        if (!window.navigator?.modelContext) return;

        await window.navigator.modelContext.registerTool({
          name: 'calculator',
          description: 'Performs mathematical calculations',
          inputSchema: {
            type: 'object',
            properties: {
              expression: {
                type: 'string',
                description: 'Mathematical expression to evaluate',
              },
            },
            required: ['expression'],
          },
          handler: async ({ expression }) => {
            const result = Function(`"use strict"; return (${expression})`)();

            return {
              content: [{
                type: 'text',
                text: `The result of ${expression} is ${result}`,
              }],
            };
          },
        });

        setIsRegistered(true);
      };

      registerTool();
    }, []);

    // ... UI implementation
  };
  ```
</CodeGroup>

***

## Color Converter Tool

**Demonstrates:** Complex schemas with `enum`, `default` values, and optional parameters

This tool showcases advanced input schema features - the `outputFormat` parameter uses an enum for constrained choices and includes a default value. Watch the color splash animation and generated palette when AI converts your color!

<ColorConverterTool />

<Tip>
  **Try it with your AI assistant:** Ask Claude to "convert the color #FF5733 to RGB format" and watch the dramatic color visualization!
</Tip>

<CodeGroup>
  ```jsx Color Converter Tool Implementation theme={null}
  // Converts HEX colors to RGB and HSL formats
  export const ColorConverterTool = () => {
    useEffect(() => {
      const registerTool = async () => {
        if (!window.navigator?.modelContext) return;

        await window.navigator.modelContext.registerTool({
          name: 'color_converter',
          description: 'Converts HEX colors to RGB and HSL formats. Input must be in HEX format.',
          inputSchema: {
            type: 'object',
            properties: {
              color: {
                type: 'string',
                description: 'Color in HEX format (e.g., "#3b82f6" or "3b82f6"). Must be a valid HEX color code.'
              },
              outputFormat: {
                type: 'string',
                enum: ['rgb', 'hsl', 'all'],
                description: 'Desired output format (rgb, hsl, or all)',
                default: 'all'
              }
            },
            required: ['color']
          },
          handler: async ({ color, outputFormat = 'all' }) => {
            // Conversion logic here
          }
        });
      };
      registerTool();
    }, []);
  };
  ```
</CodeGroup>

***

## Storage Management Tool

**Demonstrates:** Multiple tool registration and browser API integration

This component registers **three related tools** (`storage_set`, `storage_get`, `storage_list`) that work together to manage browser localStorage. Watch the data flow animation as AI reads and writes to persistent storage!

<StorageTool />

<Tip>
  **Try it with your AI assistant:** Ask Claude to "store my favorite color as blue, then list all stored items" and watch the data persist!
</Tip>

<CodeGroup>
  ```jsx Storage Tool Implementation theme={null}
  // Manages browser localStorage with set, get, and list operations
  export const StorageTool = () => {
    useEffect(() => {
      const registerTools = async () => {
        if (!window.navigator?.modelContext) return;

        // Registers multiple tools: storage_set, storage_get, storage_list
        await Promise.all([
          window.navigator.modelContext.registerTool({
            name: 'storage_set',
            description: 'Store a key-value pair in localStorage',
            // ... implementation
          }),
          window.navigator.modelContext.registerTool({
            name: 'storage_get',
            description: 'Retrieve a value from localStorage',
            // ... implementation
          }),
          window.navigator.modelContext.registerTool({
            name: 'storage_list',
            description: 'List all items in localStorage',
            // ... implementation
          })
        ]);
      };
      registerTools();
    }, []);
  };
  ```
</CodeGroup>

***

## DOM Query Tool

**Demonstrates:** Page introspection and structured data responses

This tool queries the actual page DOM using CSS selectors and returns structured element information. Watch the scanning animation and see matched elements **highlighted directly on the page** when AI queries!

<DOMQueryTool />

<Tip>
  **Try it with your AI assistant:** Ask Claude "how many h2 headings are on this page?" and watch the elements get highlighted on screen!
</Tip>

<CodeGroup>
  ```jsx DOM Query Tool Implementation theme={null}
  // Queries page elements using CSS selectors
  export const DOMQueryTool = () => {
    useEffect(() => {
      const registerTool = async () => {
        if (!window.navigator?.modelContext) return;

        await window.navigator.modelContext.registerTool({
          name: 'dom_query',
          description: 'Query page elements using CSS selectors',
          inputSchema: {
            type: 'object',
            properties: {
              selector: { type: 'string', description: 'CSS selector (e.g., "h1", ".class", "#id")' }
            },
            required: ['selector']
          },
          handler: async ({ selector }) => {
            const elements = document.querySelectorAll(selector);
            return {
              content: [{
                type: 'text',
                text: `Found ${elements.length} elements matching "${selector}"`
              }]
            };
          }
        });
      };
      registerTool();
    }, []);
  };
  ```
</CodeGroup>
