> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mcp-use.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React Integration

> React hooks and providers for MCP client connections

Import from `@mcp-use/client/react`:

```typescript theme={null}
import {
  McpClientProvider,
  useMcpClient,
  useMcpServer,
  useMcp,
  onMcpAuthorization,
} from "@mcp-use/client/react";
```

Use **`McpClientProvider`** for multi-server apps. Use standalone **`useMcp`** only for a single server.

## Provider setup

```tsx theme={null}
import { useEffect } from "react";
import {
  McpClientProvider,
  useMcpClient,
  useMcpServer,
} from "@mcp-use/client/react";

function App() {
  return (
    <McpClientProvider>
      <Dashboard />
    </McpClientProvider>
  );
}

function Dashboard() {
  const { addServer, servers, storageLoaded } = useMcpClient();

  useEffect(() => {
    // Wait for the provider's initial server load before adding servers.
    if (!storageLoaded) return;
    addServer("demo", {
      url: "http://localhost:3000/mcp",
      displayName: "Demo",
    });
  }, [addServer, storageLoaded]);

  return servers.map((s) => <ServerPanel key={s.id} serverId={s.id} />);
}

function ServerPanel({ serverId }: { serverId: string }) {
  const server = useMcpServer(serverId);
  if (!server || server.state !== "ready") {
    return <div>{server?.state ?? "loading"}…</div>;
  }

  return (
    <div>
      <h3>{server.displayName}</h3>
      <p>{server.tools.length} tools</p>
      <button onClick={() => server.callTool("echo", { msg: "hi" })}>
        Call echo
      </button>
    </div>
  );
}
```

Call `addServer` only after `storageLoaded` is `true`. When the provider finishes
its initial load, it replaces its server list with the servers from
`storageProvider` and the `mcpServers` prop, if either is set, so a server
added earlier is dropped. `addServer` ignores an ID that is already registered.

## Connection states

| State | Meaning |
| - | - |
| `discovering` | Connecting |
| `pending_auth` | OAuth required — call `server.authenticate()` |
| `authenticating` | OAuth in progress |
| `ready` | Connected |
| `failed` | Error — call `server.retry()` |

OAuth does not auto-start by default (`preventAutoAuth: true`). Show a sign-in button when `state === "pending_auth"`.

Mixed auth is reported separately from the connection state. A mixed-auth
server remains `ready` because its public operations are usable, while
`authorization` describes the optional OAuth state:

```tsx theme={null}
function ServerAuth({ serverId }: { serverId: string }) {
  const server = useMcpServer(serverId);
  if (!server) return null;

  const needsOptionalAuth =
    server.authorization?.mode === "mixed" &&
    !server.authorization.authenticated;

  return needsOptionalAuth ? (
    <div>
      <p>This server is using mixed auth. Public tools are available.</p>
      <button onClick={() => void server.authenticate()}>Authenticate</button>
    </div>
  ) : null;
}
```

The hook detects mixed auth from RFC 9728 protected-resource metadata after an
anonymous connection. Set `detectMixedAuth: false` to disable this best-effort
discovery. With the default explicit browser flow, a wire-level OAuth challenge
from a protected operation prepares the auth action; call `authenticate()` and
retry that operation. Tool-level `isError` results do not trigger OAuth.

## OAuth callback

Create a route (default: `/oauth/callback`):

```tsx theme={null}
import { onMcpAuthorization } from "@mcp-use/client/react";
import { useEffect } from "react";

export default function OAuthCallback() {
  useEffect(() => {
    void onMcpAuthorization();
  }, []);
  return <div>Completing sign-in…</div>;
}
```

Override with `defaultCallbackUrl` on the provider or `callbackUrl` per server.

`McpClientProvider` skips connecting its servers only on a page whose path ends
in `/oauth/callback`. A connection attempt on the callback page can replace the
in-flight PKCE verifier before `onMcpAuthorization()` exchanges the code. If
your callback URL uses a different path, render that route outside
`McpClientProvider`.

## Proxy fallback

When direct browser connections fail (CORS / FastMCP), retry through a proxy:

```tsx theme={null}
<McpClientProvider
  defaultProxyConfig={{ proxyAddress: "https://app.example.com/api/proxy" }}
  defaultOAuthProxyUrl="https://app.example.com/api/oauth"
  defaultAutoProxyFallback
>
  <App />
</McpClientProvider>
```

MCP traffic and OAuth use separate proxy URLs so resource identity stays the upstream MCP URL.

## Persistence

```tsx theme={null}
import { LocalStorageProvider } from "@mcp-use/client/react";

<McpClientProvider storageProvider={new LocalStorageProvider("my-servers")}>
  <App />
</McpClientProvider>;
```

Built-in providers persist only non-secret connection settings. Supply bearer
tokens, request headers, and proxy headers again at runtime after a reload.
Browser OAuth tokens remain persistent by default and are encrypted with
AES-256-GCM using a non-extractable origin key stored in IndexedDB.

## Standalone `useMcp`

For one server without the provider:

```tsx theme={null}
const mcp = useMcp({ url: "http://localhost:3000/mcp" });

if (mcp.state === "pending_auth") {
  return <button onClick={mcp.authenticate}>Sign in</button>;
}
if (mcp.state !== "ready") return <div>Connecting…</div>;

return (
  <>
    {mcp.authorization?.mode === "mixed" &&
      !mcp.authorization.authenticated && (
        <button onClick={mcp.authenticate}>Authenticate protected tools</button>
      )}
    <pre>{mcp.tools.map((t) => t.name).join("\n")}</pre>
  </>
);
```

## OAuth UserInfo

Set `oauth.fetchUserInfo: true` to fetch the authenticated account's UserInfo
after the MCP connection becomes ready. The fetch runs independently of the
connection; a UserInfo error does not change `mcp.state`. The authorization
server must advertise a `userinfo_endpoint`, and the requested scopes must
permit the claims you need (often `openid profile email`).

```tsx theme={null}
function Account() {
  const mcp = useMcp({
    url: "https://example.com/mcp",
    oauth: { scope: "openid profile email", fetchUserInfo: true },
  });

  return (
    <div>
      {mcp.userInfo.status === "available" &&
        (mcp.userInfo.claims.email ?? mcp.userInfo.claims.sub)}
      <button onClick={() => void mcp.getUserInfo()}>Refresh account</button>
    </div>
  );
}
```

`mcp.userInfo` is reactive and has these states:

| Status | Meaning |
| - | - |
| `idle` | No request is in progress. |
| `loading` | UserInfo is being fetched. |
| `available` | Read `claims` and optional `issuer`; claims such as `email` may be absent. |
| `unavailable` | Read `reason`: `not_authenticated`, `no_userinfo_endpoint`, or `session_changed`. |
| `error` | Read `error`; call `getUserInfo()` to retry. |

Automatic fetching is opt-in and retries an error once after a short delay.
You can omit `fetchUserInfo` and call `await mcp.getUserInfo()` whenever you
want to fetch the account explicitly; the method returns the same state it
publishes to `mcp.userInfo`.

## Types

* **`McpServerConfig`** — server options passed to `addServer` (replaces deprecated `McpServerOptions`).
* **`displayName`** — your label for the server in UI.
* **`serverInfo.name`** — name returned by the server at init.
* **`authorization`** — optional mixed-auth metadata with `mode`,
  `authenticated`, `resource`, and `scopesSupported`.

Sampling, elicitation, and notifications are queued on each `McpServer` (`pendingSamplingRequests`, `pendingElicitationRequests`, `notifications`). See [Sampling](/v2/typescript/client/sampling) and [Elicitation](/v2/typescript/client/elicitation).

## Reference

[React client API reference](https://mcp-use-typescript-api-reference.vercel.app/modules/_mcp-use_client.react.html) — provider props, hook return values, and storage types.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.