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 メソッドを使用して認証トークンを削除し、ログアウトすることができる。

参考

記事の理解度チェック

以下の問題に答えて、記事の理解を深めましょう。

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

  • discovering

    もう一度考えてみましょう

  • authenticating

    もう一度考えてみましょう

  • initialized

    正解!

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

  • ready

    もう一度考えてみましょう