diff --git a/README.md b/README.md index 6c31a57..766a74e 100644 --- a/README.md +++ b/README.md @@ -1 +1,91 @@ -# `use-mcp` +# use-mcp + +**⚠️ Experimental: This library is new and under active development. Expect breaking changes. Please report all issues to [https://github.com/geelen/use-mcp/issues](https://github.com/geelen/use-mcp/issues).** + +`use-mcp` provides browser-based utilities for connecting to servers implementing the [Model Context Protocol (MCP)](https://github.com/modelcontext/protocol). It simplifies handling connections, managing authentication (including OAuth 2.0 Authorization Code Flow with PKCE suitable for public clients like web apps), and accessing resources like Tools provided by the MCP server. + +This library is designed to be used alongside the Vercel AI SDK (`ai`) or similar libraries, allowing you to easily integrate MCP tools into your LLM application's frontend. + +## Features + +* Connects to MCP servers via Server-Sent Events (SSE). +* Handles MCP JSON-RPC messaging. +* Implements browser-friendly OAuth 2.0 authentication (Authorization Code with PKCE) using popups and `localStorage`. +* Provides framework-agnostic auth utilities. +* Includes a React hook (`useMcp`) for easy integration into React applications (via `use-mcp/react`). + +## Installation + +You need to install this library and the official MCP SDK: + +```bash +npm install use-mcp @modelcontextprotocol/sdk +# or +yarn add use-mcp @modelcontextprotocol/sdk +# or +pnpm add use-mcp @modelcontextprotocol/sdk +``` + +## Core Exports + +This main package (use-mcp) exports utilities primarily for handling OAuth in the browser: + +* BrowserOAuthClientProvider: An implementation of the OAuthClientProvider interface from @modelcontextprotocol/sdk. It uses localStorage to store client information and tokens and handles the popup-based authorization flow. It's used internally by the useMcp hook but can be used independently if needed. + +* onMcpAuthorization: A function designed to be called on your OAuth callback page (e.g., /oauth/callback). It handles exchanging the authorization code (received from the auth server) for tokens, validating the state parameter, storing tokens using localStorage, and communicating success or failure back to the original application window that initiated the auth flow. + +React Integration (use-mcp/react) + +For React applications, the easiest way to use this library is via the useMcp hook. + +See the React README for detailed usage instructions and examples. +Basic OAuth Callback Setup + +You need an endpoint in your application (e.g., /oauth/callback) that the OAuth server redirects the user back to after they approve the authorization request. This endpoint should call onMcpAuthorization. + +Example (Conceptual - using a simple script in HTML or a framework handler): + +```tsx +// pages/oauth/callback.js (or similar route file) +import { useEffect } from 'react'; // Or framework equivalent +import { onMcpAuthorization } from 'use-mcp'; + +export default function OAuthCallbackPage() { + useEffect(() => { + // Extract query parameters from window.location.search + const queryParams = Object.fromEntries( + new URLSearchParams(window.location.search).entries() + ); + + // Call the handler + onMcpAuthorization(queryParams, { + // Optional: specify storage key prefix if not using default 'mcp:auth' + // storageKeyPrefix: 'my_custom_prefix' + }).then(result => { + if (!result.success) { + // Error already logged and displayed by onMcpAuthorization + console.error("OAuth callback failed:", result.error); + // Optionally show a persistent error message or redirect + } + // On success, onMcpAuthorization closes the window or redirects + }).catch(err => { + // Catch unexpected errors during the handler execution itself + console.error("Unexpected error in OAuth callback handler:", err); + // Display error in the popup/page + document.body.innerHTML = `
An unexpected error occurred during authentication.
${err.message}`;
+ });
+
+ }, []); // Run only once on component mount
+
+ // Render a loading state while the exchange happens
+ return + ${errorMessage} +
+You can close this window.
+${
+ err instanceof Error ? err.stack : ''
+ }
+ + Popup blocked?{' '} + + Authenticate manually + + {' or '} + +
+ )} + {/* Show retry button for general failures */} + {!mcp.authUrl && } +Waiting for authentication...
} + {mcp.state === 'ready' &&Connected! Tools available: {mcp.tools.length}
} + + {/* Your Chat UI */} +
+ {mcp.log.map((l, i) => [{l.level}] {new Date(l.timestamp).toLocaleTimeString()}: {l.message})}
+
+ )}
+ {/* Button to clear stored auth data */}
+
+ ${errorMessage}
-You can close this window and try again.
- - - ` - - document.body.innerHTML = errorHtml - - return { success: false, error: errorMessage } - } -} +// Re-export core Tool type for convenience when using hook result? +export type { Tool } from '@modelcontextprotocol/sdk/types.js'; \ No newline at end of file diff --git a/src/react/types.ts b/src/react/types.ts new file mode 100644 index 0000000..3c6035d --- /dev/null +++ b/src/react/types.ts @@ -0,0 +1,72 @@ +import { Tool } from '@modelcontextprotocol/sdk/types.js'; + +export type UseMcpOptions = { + /** The /sse URL of your remote MCP server */ + url: string; + /** OAuth client name for registration (if dynamic registration is used) */ + clientName?: string; + /** OAuth client URI for registration (if dynamic registration is used) */ + clientUri?: string; + /** Custom callback URL for OAuth redirect (defaults to /oauth/callback on the current origin) */ + callbackUrl?: string; + /** Storage key prefix for OAuth data in localStorage (defaults to "mcp:auth") */ + storageKeyPrefix?: string; + /** Custom configuration for the MCP client identity */ + clientConfig?: { + name?: string; + version?: string; + }; + /** Whether to enable verbose debug logging to the console and the log state */ + debug?: boolean; + /** Auto retry connection if initial connection fails, with delay in ms (default: false) */ + autoRetry?: boolean | number; + /** Auto reconnect if an established connection is lost, with delay in ms (default: 3000) */ + autoReconnect?: boolean | number; + /** Popup window features string (dimensions and behavior) for OAuth */ + popupFeatures?: string; +}; + +export type UseMcpResult = { + /** List of tools available from the connected MCP server */ + tools: Tool[]; + /** + * The current state of the MCP connection: + * - 'discovering': Checking server existence and capabilities (including auth requirements). + * - 'authenticating': Authentication is required and the process (e.g., popup) has been initiated. + * - 'connecting': Establishing the SSE connection to the server. + * - 'loading': Connected; loading resources like the tool list. + * - 'ready': Connected and ready for tool calls. + * - 'failed': Connection or authentication failed. Check the `error` property. + */ + state: 'discovering' | 'authenticating' | 'connecting' | 'loading' | 'ready' | 'failed'; + /** If the state is 'failed', this provides the error message */ + error?: string; + /** + * If authentication requires user interaction (e.g., popup was blocked), + * this URL can be presented to the user to complete authentication manually in a new tab. + */ + authUrl?: string; + /** Array of internal log messages (useful for debugging) */ + log: { level: 'debug' | 'info' | 'warn' | 'error'; message: string; timestamp: number }[]; + /** + * Function to call a tool on the MCP server. + * @param name The name of the tool to call. + * @param args Optional arguments for the tool. + * @returns A promise that resolves with the tool's result. + * @throws If the client is not in the 'ready' state or the call fails. + */ + callTool: (name: string, args?: Record