npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@fractal-mcp/mcp-ui-hooks

v2.2.5

Published

React hooks for MCP-UI cross-iframe messaging

Readme

@fractal-mcp/mcp-ui-hooks

React hooks for MCP UI compatible cross-iframe messaging. Makes it dead simple to build embeddable UIs.

Note: This package is not directly compatible with OpenAI Apps SDK. For OpenAI Apps, use @fractal-mcp/oai-hooks instead.

Installation

npm install @fractal-mcp/mcp-ui-hooks

Quick Start

import { useUIMessenger } from '@fractal-mcp/mcp-ui-hooks';

function App() {
  const { ready, renderData, emitIntent } = useUIMessenger();

  if (!ready) return <div>Loading...</div>;

  return (
    <div>
      <h1>Theme: {renderData?.theme}</h1>
      <button onClick={() => emitIntent({ 
        intent: 'button-clicked',
        params: { timestamp: Date.now() }
      })}>
        Click Me
      </button>
    </div>
  );
}

That's it! The hook handles initialization, render data, and cleanup automatically.

API

useUIMessenger(options?)

Returns an object with messaging functions and state.

const {
  ready,              // boolean - messenger is initialized
  renderData,         // Record<string, unknown> - data from host
  emitIntent,         // Send intent (fire-and-forget)
  emitNotify,         // Send notification
  emitPrompt,         // Ask host to run prompt
  emitTool,           // Ask host to call tool
  emitLink,           // Ask host to navigate
  requestIntent,      // Send intent and wait for response
  requestNotify,      // Send notification and wait for response
  requestPrompt,      // Send prompt and wait for response
  requestTool,        // Send tool call and wait for response ()
  requestLink,        // Send link and wait for response
  requestData,        // Request custom data from host
} = useUIMessenger({ forceWaitForRenderData: true });

Options

{
  forceWaitForRenderData?: boolean;  // Wait for render data even without URL param
}

Usage Examples

Send Intent (Fire-and-Forget)

function TaskCreator() {
  const { emitIntent } = useUIMessenger();

  const createTask = () => {
    emitIntent({ 
      intent: 'create-task',
      params: { 
        title: 'Buy groceries',
        priority: 'high'
      }
    });
  };

  return <button onClick={createTask}>Create Task</button>;
}

Request with Response (Basic)

function WeatherWidget() {
  const { requestTool } = useUIMessenger();
  const [weather, setWeather] = useState(null);

  const getWeather = async () => {
    const req = await requestTool({ 
      toolName: 'get-weather',
      params: { city: 'Tokyo' }
    });
    
    const result = await req.response();
    setWeather(result);
  };

  return (
    <div>
      <button onClick={getWeather}>Get Weather</button>
      {weather && <div>{JSON.stringify(weather)}</div>}
    </div>
  );
}

Request with Response (Advanced: Tracking State)

Track the request lifecycle to show loading states:

function WeatherWidget() {
  const { requestTool } = useUIMessenger();
  const [status, setStatus] = useState('idle');
  const [weather, setWeather] = useState(null);

  const getWeather = async () => {
    setStatus('sending');
    
    const req = await requestTool({ 
      toolName: 'get-weather',
      params: { city: 'Tokyo' }
    });

    // Wait for acknowledgment from host
    req.received().then(() => {
      setStatus('processing'); // Host acknowledged, now processing
    });

    // Wait for the final response
    try {
      const result = await req.response();
      setStatus('success');
      setWeather(result);
    } catch (error) {
      setStatus('error');
    }
  };

  return (
    <div>
      <button onClick={getWeather} disabled={status !== 'idle' && status !== 'success' && status !== 'error'}>
        Get Weather
      </button>
      {status === 'sending' && <p>Sending request...</p>}
      {status === 'processing' && <p>Host is processing...</p>}
      {status === 'success' && <p>Weather: {JSON.stringify(weather)}</p>}
      {status === 'error' && <p>Failed to get weather</p>}
    </div>
  );
}

The RpcRequest object provides:

  • received(): Promise that resolves when host sends ui-message-received (acknowledgment)
  • response(): Promise that resolves when host sends ui-message-response (final result)

See the mcp-ui-messenger automatic behaviors documentation for more details.

Notify Host of State Changes

function ShoppingCart() {
  const { emitNotify } = useUIMessenger();
  const [items, setItems] = useState([]);

  const addItem = (item) => {
    setItems([...items, item]);
    emitNotify({ message: 'cart-updated' });
  };

  return <div>{/* cart UI */}</div>;
}

Request Custom Data

function PaymentMethods() {
  const { requestData } = useUIMessenger();
  const [methods, setMethods] = useState([]);

  useEffect(() => {
    requestData({ 
      requestType: 'get-payment-methods',
      params: { userId: '123' }
    }).then(req => req.response())
      .then(setMethods);
  }, []);

  return <ul>{methods.map(m => <li key={m.id}>{m.name}</li>)}</ul>;
}

Use Render Data

function ThemedApp() {
  const { renderData } = useUIMessenger();

  const theme = renderData?.theme || 'light';
  const locale = renderData?.locale || 'en';

  return (
    <div className={`theme-${theme}`} lang={locale}>
      {/* your app */}
    </div>
  );
}

Ask Host to Run a Prompt

function AIAssistant() {
  const { requestPrompt } = useUIMessenger();

  const askAI = async () => {
    const req = await requestPrompt({ 
      prompt: 'What is the weather in Tokyo?' 
    });
    const response = await req.response();
    console.log('AI response:', response);
  };

  return <button onClick={askAI}>Ask AI</button>;
}

Alias: useFractal

For convenience, useFractal is exported as an alias:

import { useFractal } from '@fractal-mcp/mcp-ui-hooks';

function App() {
  const { ready, emitIntent } = useFractal();
  // ... same as useUIMessenger
}

Message Types

Fire-and-Forget (emit)

  • emitIntent({ intent, params }) - User expressed an intent
  • emitNotify({ message }) - Notify host of state change
  • emitPrompt({ prompt }) - Ask host to run a prompt
  • emitTool({ toolName, params }) - Ask host to call a tool
  • emitLink({ url }) - Ask host to navigate

Request-Response

  • requestIntent() - Send intent and get response
  • requestNotify() - Send notification and get response
  • requestPrompt() - Send prompt and get response
  • requestTool() - Send tool call and get response
  • requestLink() - Send link request and get response
  • requestData({ requestType, params }) - Request custom data

TypeScript Types

All payload types are re-exported from @fractal-mcp/mcp-ui-messenger:

interface IntentPayload {
  intent: string;
  params?: Record<string, unknown>;
}

interface NotifyPayload {
  message: string;
}

interface PromptPayload {
  prompt: string;
}

interface ToolPayload {
  toolName: string;
  params?: Record<string, unknown>;
}

interface LinkPayload {
  url: string;
}

interface RequestDataPayload {
  requestType: string;
  params?: Record<string, unknown>;
}

How It Works

  1. Hook calls initUIMessenger() on mount
  2. Automatically handles iframe lifecycle (ready event, render data)
  3. Provides ready state and render data via hook return
  4. All messaging functions wait for initialization before sending

Automatic Message Handling

The underlying messenger automatically handles MCP-UI protocol details for you:

  • Size changes: Automatically reported to the host via ui-size-change events
  • Request tracking: request* methods automatically handle ui-message-received and ui-message-response events using message IDs

For more details, see the mcp-ui-messenger automatic behaviors documentation.

Advanced: Init Before React

If you need render data before React mounts, use the base package:

import { initUIMessenger } from '@fractal-mcp/mcp-ui-hooks';

const messenger = await initUIMessenger({ rootElId: 'root' });
const renderData = messenger.getRenderData();

root.render(<App messenger={messenger} renderData={renderData} />);

Then in your components, just call useUIMessenger() and it will use the existing instance.

Protocol

Implements the MCP-UI Embeddable UI Protocol.

License

See repository root for license information.