This article was translated from Japanese by AI and may contain inaccuracies. For the most accurate content, please refer to the original Japanese version.
bird suzume 9916

ブラウザから MCP サーバーに接続する use-mcp React フック

use-mcp はリモートの MCP サーバーに接続するための React フックです。ツールの呼び出しや認証を簡単に行うことができます。この記事では、use-mcp を使用して MCP サーバーに接続し、ツールを呼び出す方法と、OAuth 認証の実装方法について解説します。

use-mcp はリモートのModel Context Protocol (MCP) サーバーに接続するための React フックです。このフックを使用すると AI システムへの認証やツールの呼び出しを簡単に行うことができます。

React コンポーネントから MCP サーバーに接続する

use-mcp フックを使用したコンポーネントの例を試してみましょう。2025-06-18 バージョンの MCP の仕様ではクライアントとサーバーのトランスポートの方法として stdioStreamable HTTP が定義されていますが、use-mcp では Streamable HTTP による接続をサポートしています。HTTP もしくは SSE(Server-Sent Events)を使用して MCP サーバーに接続します。

MCP サーバーとして Git MCP を使用します。これは GitHub の任意のレポジトリを MCP サーバーとして利用できるツールです。public なレポジトリであれば認証無しで接続できます。URL はレポジトリの URL の github.com の部分を gitmcp.io に置き換えたものを指定します。例えば https://gitmcp.io/azukiazusa1/sapper-blog-app のような形式です。

React アプリケーションを作成し、以下のコマンドで use-mcp をインストールします。

npm install use-mcp

useMcp フックに接続先の URL を指定して呼び出します。以下のコードを追加しましょう。

src/App.tsx
import { useState } from "react";
import { useMcp, type Tool } from "use-mcp/react";
 
function App() {
  const { state, tools, error } = useMcp({
    url: "https://gitmcp.io/azukiazusa1/sapper-blog-app",
    clientName: "my-mcp-client",
  });
 
  if (state === "loading" || state === "connecting") {
    return <div>Loading...</div>;
  }
  if (state === "failed") {
    return <div>Error loading MCP: {error}</div>;
  }
 
  return (
    <div>
      <h1>Git MCP Server</h1>
 
      <ul>
        {tools.map((tool) => (
          <li key={tool.name}>
            <button onClick={() => {}}>{tool.name}</button>
            <p>{tool.description}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
 
export default App;

フックの戻り値の state には MCP サーバーとの接続が成功したかどうかの状態が含まれ、以下の値を取ります。

  • discovering
  • authenticating
  • connecting
  • loading
  • ready
  • failed

この状態を使用して接続状態をレンダリングしています。MCP サーバーで利用可能なツールの一覧は tools プロパティに含まれているので、リストとして表示しています。

ツールの呼び出しを追加してみましょう。ツールを呼び出すには callTool メソッドにツール名と引数を渡します。ツールが要求する引数の型は tools プロパティの各ツールの inputSchema に定義されているので、このスキーマを参照し引数の入力フォームを表示します。

src/App.tsx
import { useState } from "react";
import { useMcp, type Tool } from "use-mcp/react";
 
function App() {
  const [toolCallResult, setToolCallResult] = useState(null);
  const [selectedTool, setSelectedTool] = useState<Tool | null>(null);
  const [formData, setFormData] = useState<Record<string, any>>({});
  const { state, tools, callTool, error } = useMcp({
    url: "https://gitmcp.io/azukiazusa1/sapper-blog-app",
    clientName: "my-mcp-client",
  });
 
  if (state === "loading") {
    return <div>Loading...</div>;
  }
  if (state === "failed") {
    return <div>Error loading MCP: {error}</div>;
  }
 
  function onClickTool(tool: Tool) {
    setSelectedTool(tool);
    setFormData({});
    setToolCallResult(null);
  }
 
  function renderInputField(name: string, schema: any) {
    const value = formData[name] || "";
 
    const handleChange = (newValue: any) => {
      setFormData((prev) => ({ ...prev, [name]: newValue }));
    };
 
    if (schema.type === "string") {
      if (schema.enum) {
        return (
          <select value={value} onChange={(e) => handleChange(e.target.value)}>
            <option value="">Select...</option>
            {schema.enum.map((option: string) => (
              <option key={option} value={option}>
                {option}
              </option>
            ))}
          </select>
        );
      }
      return (
        <input
          type="text"
          value={value}
          onChange={(e) => handleChange(e.target.value)}
          placeholder={schema.description}
        />
      );
    }
 
    if (schema.type === "number") {
      return (
        <input
          type="number"
          value={value}
          onChange={(e) => handleChange(Number(e.target.value))}
          placeholder={schema.description}
        />
      );
    }
 
    // boolean, array など他の型は省略
  }
 
  async function handleSubmit() {
    if (!selectedTool) return;
 
    try {
      const result = await callTool(selectedTool.name, formData);
      setToolCallResult(result);
    } catch (error) {
      setToolCallResult({ error: error.message });
    }
  }
 
  return (
    <div>
      <h1>Git MCP Server</h1>
 
      {!selectedTool ? (
        <ul>
          {tools.map((tool) => (
            <li key={tool.name}>
              <button onClick={() => onClickTool(tool)}>{tool.name}</button>
              <p>{tool.description}</p>
            </li>
          ))}
        </ul>
      ) : (
        <div>
          <h2>{selectedTool.name}</h2>
          <p>{selectedTool.description}</p>
 
          <form
            onSubmit={(e) => {
              e.preventDefault();
              handleSubmit();
            }}
          >
            {selectedTool.inputSchema?.properties &&
              Object.entries(selectedTool.inputSchema.properties).map(
                ([name, schema]: [string, any]) => (
                  <div key={name} style={{ marginBottom: "10px" }}>
                    <label>
                      {name}
                      {selectedTool.inputSchema.required?.includes(name) &&
                        " *"}
                      :
                    </label>
                    <div>{renderInputField(name, schema)}</div>
                    {schema.description && (
                      <small style={{ color: "#666" }}>
                        {schema.description}
                      </small>
                    )}
                  </div>
                )
              )}
 
            <button type="submit">Execute Tool</button>
            <button type="button" onClick={() => setSelectedTool(null)}>
              Back
            </button>
          </form>
        </div>
      )}
 
      {toolCallResult && (
        <div>
          <h2>Tool Call Result</h2>
          <pre>{JSON.stringify(toolCallResult, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}
 
export default App;

このコードでは、ツールを選択するとそのツールの入力フォームが表示され、必要な引数を入力してツールを実行できます。ツールの実行結果は toolCallResult に保存され、画面に表示されます。

OAuth 認証

多くの MCP サーバーではツールの呼び出しを行うために認証が必要です。MCP サーバーの認証の仕様では MCP サーバーは OAuth 2.1 を実装する必要があると定義されています。

use-mcp では OAuth 認証のフロー全体をサポートしています。ユーザーをログインページにリダイレクトし、認証後にコールバック URL にリダイレクトされることで認証トークンを取得しストレージに保存します。

ここでは Cloudflare Workers Bindings MCP Server を例にコード例を示します。これは Cloudflare の OAuth が組み込まれた MCP サーバーで、Cloudflare Workers のリソースを操作するツールを提供しています。

先程の App.tsx ファイルの useMcp フックに渡した URL を https://bindings.mcp.cloudflare.com/sse に変更しておきましょう。

src/App.tsx
import { useMcp, type Tool } from "use-mcp/react";
 
function App() {
  const { state, tools, callTool, error } = useMcp({
    url: "https://bindings.mcp.cloudflare.com/sse",
    clientName: "my-mcp-client",
  });
  // ...
}

React アプリケーションでコールバック URL が必要となるため、何らかのルーティングライブラリを使用して /oauth/callback のパスを処理する必要があります。ここでは react-router-dom を使用しましょう。

npm install react-router-dom

認証を行うためのコンポーネントを追加します。以下のコードを src/OAuthCallback.tsx として保存します。ここでは onMcpAuthorization 関数を useEffect フックで呼び出しています。

src/OAuthCallback.tsx
import { useEffect } from "react";
import { onMcpAuthorization } from "use-mcp";
 
export function OAuthCallback() {
  useEffect(() => {
    onMcpAuthorization();
  }, [])
 
  return (
    <div>
      <h1>Authenticating...</h1>
      <p>This window should close automatically.</p>
    </div>
  )
}

Routes コンポーネントを作成し、App コンポーネントと OAuthCallback コンポーネントをルーティングします。

src/Routes.tsx
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import App from "./App";
import { OAuthCallback } from "./OAuthCallback";
export function AppRoutes() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<App />} />
        <Route path="/oauth/callback" element={<OAuthCallback />} />
      </Routes>
    </Router>
  );
}

src/main.tsx を以下のように更新して、AppRoutes をレンダリングします。

src/main.tsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";
import { AppRoutes } from "./Routes.tsx";
 
createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <AppRoutes />
  </StrictMode>
);

これで認証処理が実装されました。アプリケーションを起動し http://localhost:5173 にアクセスすると、Cloudflare の OAuth 認証ページのポップアップが表示されます。

ポップアップの「Allow」ボタンをクリックして認証を許可すると、アプリケーションにリダイレクトされ、認証が完了します。これで MCP サーバーに接続し、ツールを呼び出すことができるようになります。

認証トークンはブラウザの localStorage に保存され、次回アプリケーションを起動したときに自動的に認証が行われます。ストレージをクリアする場合には useMcp フックの clearStorage メソッドを呼び出すことでトークンを削除できます。

src/App.tsx
import { useMcp, type Tool } from "use-mcp/react";
function App() {
  const { state, tools, callTool, error, clearStorage } = useMcp({
    url: "https://bindings.mcp.cloudflare.com/sse",
    clientName: "my-mcp-client",
  });
 
  // ...
 
  return (
    <div>
      <h1>Git MCP Server</h1>
 
      <button onClick={clearStorage}>Log Out</button>
 
      {/* ... */}
    </div>
  );
}

まとめ

  • use-mcp は MCP サーバーに接続するための React フックで、ツールの呼び出しや認証を簡単に行うことができる。
  • useMcp フックを使用して MCP サーバーに接続し、ツールの一覧を取得できる。
  • ツールの呼び出しには callTool メソッドを使用する。必要な引数は tools プロパティの各ツールの inputSchema に定義されている。
  • OAuth 認証をサポートしており、認証フローを簡単に実装できる。/oauth/callback のパスを設定し、onMcpAuthorization 関数を使用して認証を行う。
  • 認証後はブラウザの localStorage にトークンが保存され、次回の起動時に自動的に認証が行われる。
  • clearStorage メソッドを使用して認証トークンを削除し、ログアウトすることができる。

参考

Comprehension check

Answer the following questions to deepen your understanding of the article.

useMcp フックが返す state の値として正しくないものはどれですか?

  • discovering

    Try again

  • authenticating

    Try again

  • initialized

    Correct!

    initialized は state の値に含まれていません。正しくは discovering, authenticating, connecting, loading, ready, failed です。

  • ready

    Try again