Overview

The @mcp-b/mcp-react-hooks package provides React hooks for integrating the Model Context Protocol (MCP) into React applications. The primary hook useMcpClient is used extensively in production for connecting to MCP servers, listing tools, and managing connections. The package also provides useMcpServer for creating MCP servers within React components.

Installation

npm install @mcp-b/mcp-react-hooks @mcp-b/transports @modelcontextprotocol/sdk

Providers and Hooks

useMcpClient Hook

The primary hook for connecting to MCP servers. Used in production Chrome extensions and web applications.
import { useMcpClient } from '@mcp-b/mcp-react-hooks';
import { ExtensionClientTransport } from '@mcp-b/transports';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';

// Real-world example from Chrome extension sidepanel
export const transport = new ExtensionClientTransport({
  portName: 'mcp',
  autoReconnect: true,
});

export const client = new Client({
  name: 'Extension Sidepanel',
  version: '1.0.0',
});

// In your component
function McpServer() {
  const {
    client,
    capabilities,
    isLoading,
    error,
    resources,
    tools: mcpTools,
    isConnected,
  } = useMcpClient();

  // Call a tool with proper error handling
  const callTool = async (toolName: string, data: any) => {
    if (!client) return;
    
    try {
      const result = await client.callTool({
        name: toolName,
        arguments: data,
      });
      
      // Handle MCP tool result format
      if (result?.content?.[0]?.text) {
        return JSON.parse(result.content[0].text);
      }
      return result;
    } catch (error) {
      console.error(`Tool ${toolName} failed:`, error);
      throw error;
    }
  };

  if (isLoading) return <Skeleton />;
  if (error) return <Alert variant="destructive">{error.message}</Alert>;
  if (!isConnected) return <div>Disconnected</div>;

  return (
    <div>
      <h3>Connected - {mcpTools.length} tools available</h3>
      {mcpTools.map(tool => (
        <ToolCard key={tool.name} tool={tool} onCall={callTool} />
      ))}
    </div>
  );
}

McpServerProvider & useMcpServer

Create an MCP server that exposes tools to clients.
import { McpServerProvider, useMcpServer } from '@mcp-b/mcp-react-hooks';
import { TabServerTransport } from '@mcp-b/transports';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';

// Create server and transport
const server = new McpServer({
  name: 'my-server',
  version: '1.0.0'
});

const transport = new TabServerTransport({
  allowedOrigins: ['*']
});

// Wrap your app
function App() {
  return (
    <McpServerProvider server={server} transport={transport}>
      <ServerComponent />
    </McpServerProvider>
  );
}

// Use the hook
function ServerComponent() {
  const { 
    server, 
    isConnected, 
    isConnecting, 
    error,
    registerTool,
    elicitInput
  } = useMcpServer();

  useEffect(() => {
    // Register a tool dynamically
    const unregister = registerTool('greet', {
      description: 'Greets a user',
      inputSchema: z.object({
        name: z.string().describe('Name to greet')
      }),
      handler: async ({ name }) => ({
        content: [{ 
          type: 'text', 
          text: `Hello, ${name}!` 
        }]
      })
    });

    // Cleanup on unmount
    return unregister;
  }, [registerTool]);

  const handleElicit = async () => {
    const result = await elicitInput(
      'Enter your name',
      { type: 'string' }
    );
    console.log('User entered:', result);
  };

  return (
    <div>
      <p>Server status: {isConnected ? 'Connected' : 'Disconnected'}</p>
      <button onClick={handleElicit}>Request Input</button>
    </div>
  );
}

McpMemoryProvider

Combines both client and server functionality using an in-memory transport, perfect for self-contained applications.
import { McpMemoryProvider } from '@mcp-b/mcp-react-hooks';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

const server = new McpServer({
  name: 'memory-server',
  version: '1.0.0'
});

function App() {
  return (
    <McpMemoryProvider server={server}>
      <CombinedComponent />
    </McpMemoryProvider>
  );
}

function CombinedComponent() {
  // Can use both hooks
  const { tools } = useMcpClient();
  const { registerTool } = useMcpServer();

  useEffect(() => {
    const unregister = registerTool('echo', {
      description: 'Echoes input',
      inputSchema: z.object({ message: z.string() }),
      handler: async ({ message }) => ({
        content: [{ type: 'text', text: message }]
      })
    });
    return unregister;
  }, [registerTool]);

  return (
    <div>
      <h3>Self-contained MCP app</h3>
      <p>Tools available: {tools.length}</p>
    </div>
  );
}

Hook Return Values

useMcpClient Returns

PropertyTypeDescription
clientClientMCP client instance
toolsMcpTool[]Available tools from server
resourcesResource[]Available resources
isConnectedbooleanConnection status
isLoadingbooleanLoading state
errorError | nullConnection error if any
capabilitiesServerCapabilities | nullServer capabilities
reconnect() => Promise<void>Manual reconnection function

useMcpServer Returns

PropertyTypeDescription
serverMcpServerMCP server instance
isConnectedbooleanConnection status
isConnectingbooleanConnection in progress
errorError | nullConnection error if any
registerToolFunctionRegister a tool dynamically
elicitInputFunctionRequest input from client

Real-World Patterns

Dynamic Tool Registration

function DynamicToolsComponent() {
  const { registerTool } = useMcpServer();
  const [registeredTools, setRegisteredTools] = useState([]);

  const addCalculatorTool = () => {
    const unregister = registerTool('calculate', {
      description: 'Performs basic calculations',
      inputSchema: z.object({
        operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
        a: z.number(),
        b: z.number()
      }),
      handler: async ({ operation, a, b }) => {
        let result;
        switch (operation) {
          case 'add': result = a + b; break;
          case 'subtract': result = a - b; break;
          case 'multiply': result = a * b; break;
          case 'divide': result = a / b; break;
        }
        return {
          content: [{ 
            type: 'text', 
            text: `Result: ${result}` 
          }]
        };
      }
    });

    setRegisteredTools(prev => [...prev, { name: 'calculate', unregister }]);
  };

  return (
    <div>
      <button onClick={addCalculatorTool}>Add Calculator Tool</button>
      {registeredTools.map(tool => (
        <div key={tool.name}>
          {tool.name} 
          <button onClick={() => tool.unregister()}>Remove</button>
        </div>
      ))}
    </div>
  );
}

Error Handling

function RobustMcpComponent() {
  const { client, error, reconnect, isConnected } = useMcpClient();
  const [callError, setCallError] = useState(null);

  const safeCallTool = async (toolName, args) => {
    setCallError(null);
    try {
      const result = await client.callTool({
        name: toolName,
        arguments: args
      });
      return result;
    } catch (err) {
      setCallError(err);
      console.error('Tool call failed:', err);
    }
  };

  if (error) {
    return (
      <div className="error-state">
        <p>Connection error: {error.message}</p>
        <button onClick={reconnect}>Try Reconnecting</button>
      </div>
    );
  }

  return (
    <div>
      <p>Status: {isConnected ? '🟢 Connected' : '🔴 Disconnected'}</p>
      {callError && <p className="error">Last error: {callError.message}</p>}
      <button onClick={() => safeCallTool('myTool', {})}>
        Call Tool Safely
      </button>
    </div>
  );
}

Tool Discovery UI

function ToolExplorer() {
  const { tools, client } = useMcpClient();
  const [selectedTool, setSelectedTool] = useState(null);
  const [args, setArgs] = useState({});
  const [result, setResult] = useState(null);

  const executeTool = async () => {
    if (!selectedTool) return;
    
    const response = await client.callTool({
      name: selectedTool.name,
      arguments: args
    });
    setResult(response);
  };

  return (
    <div>
      <select onChange={(e) => {
        const tool = tools.find(t => t.name === e.target.value);
        setSelectedTool(tool);
        setArgs({});
      }}>
        <option>Select a tool</option>
        {tools.map(tool => (
          <option key={tool.name} value={tool.name}>
            {tool.name}
          </option>
        ))}
      </select>

      {selectedTool && (
        <div>
          <h4>{selectedTool.description}</h4>
          <pre>{JSON.stringify(selectedTool.inputSchema, null, 2)}</pre>
          <textarea 
            value={JSON.stringify(args, null, 2)}
            onChange={(e) => setArgs(JSON.parse(e.target.value))}
          />
          <button onClick={executeTool}>Execute</button>
        </div>
      )}

      {result && (
        <div>
          <h4>Result:</h4>
          <pre>{JSON.stringify(result, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

TypeScript Support

The package includes full TypeScript definitions. You can import types for better type safety:
import type { 
  McpClientProviderProps,
  McpServerProviderProps,
  McpMemoryProviderProps
} from '@mcp-b/mcp-react-hooks';

// Use with your own types
interface MyTool {
  name: 'customTool';
  arguments: {
    input: string;
    options?: {
      format: 'json' | 'text';
    };
  };
}

// Type your responses
interface ToolResponse {
  content: Array<{
    type: 'text' | 'image';
    text?: string;
    data?: string;
    mimeType?: string;
  }>;
}

Integration Examples

With React Router

import { BrowserRouter, Route, Routes } from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <McpClientProvider client={client} transport={transport}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/tools" element={<ToolsPage />} />
        </Routes>
      </McpClientProvider>
    </BrowserRouter>
  );
}

With State Management

import { create } from 'zustand';

const useMcpStore = create((set) => ({
  toolResults: [],
  addToolResult: (result) => set((state) => ({
    toolResults: [...state.toolResults, result]
  }))
}));

function IntegratedComponent() {
  const { client } = useMcpClient();
  const addToolResult = useMcpStore((state) => state.addToolResult);

  const callAndStore = async (toolName, args) => {
    const result = await client.callTool({
      name: toolName,
      arguments: args
    });
    addToolResult({ toolName, args, result, timestamp: Date.now() });
  };

  return <button onClick={() => callAndStore('myTool', {})}>
    Call and Store
  </button>;
}

Browser Compatibility

  • Chrome/Edge (Chromium): Full support
  • Firefox: Full support for tab transports
  • Safari: Full support for tab transports
  • All browsers require modern JavaScript features (ES2020+)

Resources