From 78f0e364a64bc482f881dd93b2b6f7b60ca5226e Mon Sep 17 00:00:00 2001 From: Glen Maddern Date: Mon, 23 Jun 2025 20:19:10 +1000 Subject: [PATCH] Implement OAuth callback support for MCP servers - Add react-router-dom dependency for routing support - Create OAuthCallback component with animated background and loading spinner - Add routing to App.tsx with /oauth/callback route - Match styling with main app using animated-bg-container - Use onMcpAuthorization from use-mcp library for OAuth handling - Library already handles multiple server differentiation via state parameter - Each MCP server gets unique OAuth flow identified by state containing server URL - Clean up unused imports and variables in McpServerModal --- examples/chat-ui/package.json | 1 + examples/chat-ui/pnpm-lock.yaml | 45 +++++++++++++++++++ examples/chat-ui/src/App.tsx | 11 +++-- .../chat-ui/src/components/McpServerModal.tsx | 4 +- .../chat-ui/src/components/OAuthCallback.tsx | 22 +++++++++ 5 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 examples/chat-ui/src/components/OAuthCallback.tsx diff --git a/examples/chat-ui/package.json b/examples/chat-ui/package.json index 7d80ae2..628ce41 100644 --- a/examples/chat-ui/package.json +++ b/examples/chat-ui/package.json @@ -26,6 +26,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-markdown": "^10.1.0", + "react-router-dom": "^7.6.2", "rehype-highlight": "^7.0.2", "remark-gfm": "^4.0.1", "tailwindcss": "^4.0.14", diff --git a/examples/chat-ui/pnpm-lock.yaml b/examples/chat-ui/pnpm-lock.yaml index 8e011de..1058abf 100644 --- a/examples/chat-ui/pnpm-lock.yaml +++ b/examples/chat-ui/pnpm-lock.yaml @@ -41,6 +41,9 @@ importers: react-markdown: specifier: ^10.1.0 version: 10.1.0(@types/react@19.1.3)(react@19.1.0) + react-router-dom: + specifier: ^7.6.2 + version: 7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) rehype-highlight: specifier: ^7.0.2 version: 7.0.2 @@ -1196,6 +1199,10 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -2139,6 +2146,23 @@ packages: resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} engines: {node: '>=0.10.0'} + react-router-dom@7.6.2: + resolution: {integrity: sha512-Q8zb6VlTbdYKK5JJBLQEN06oTUa/RAbG/oQS1auK1I0TbJOXktqm+QENEVJU6QvWynlXPRBXI3fiOQcSEA78rA==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.6.2: + resolution: {integrity: sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + react@19.1.0: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} @@ -2207,6 +2231,9 @@ packages: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} + set-cookie-parser@2.7.1: + resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -3520,6 +3547,8 @@ snapshots: cookie@0.7.2: {} + cookie@1.0.2: {} + cors@2.8.5: dependencies: object-assign: 4.1.1 @@ -4669,6 +4698,20 @@ snapshots: react-refresh@0.17.0: {} + react-router-dom@7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + react-router: 7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + + react-router@7.6.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + dependencies: + cookie: 1.0.2 + react: 19.1.0 + set-cookie-parser: 2.7.1 + optionalDependencies: + react-dom: 19.1.0(react@19.1.0) + react@19.1.0: {} rehype-highlight@7.0.2: @@ -4794,6 +4837,8 @@ snapshots: transitivePeerDependencies: - supports-color + set-cookie-parser@2.7.1: {} + setprototypeof@1.2.0: {} sharp@0.33.5: diff --git a/examples/chat-ui/src/App.tsx b/examples/chat-ui/src/App.tsx index b93f8f2..63358dc 100644 --- a/examples/chat-ui/src/App.tsx +++ b/examples/chat-ui/src/App.tsx @@ -1,10 +1,15 @@ +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom' import ChatApp from "./components/ChatApp"; +import { OAuthCallback } from "./components/OAuthCallback"; function App() { return ( - <> - - + + + } /> + } /> + + ); } diff --git a/examples/chat-ui/src/components/McpServerModal.tsx b/examples/chat-ui/src/components/McpServerModal.tsx index 15a9b43..a91943c 100644 --- a/examples/chat-ui/src/components/McpServerModal.tsx +++ b/examples/chat-ui/src/components/McpServerModal.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from 'react' +import React, { useState, useEffect } from 'react' import { X, Info, Settings, Plus, Trash2, Power, PowerOff } from 'lucide-react' import { useMcp, type Tool } from 'use-mcp/react' @@ -63,7 +63,7 @@ const McpServerModal: React.FC = ({ const stored = localStorage.getItem('mcpServerToolCounts') return stored ? JSON.parse(stored) : {} }) - const logRef = useRef(null) + // const logRef = useRef(null) // Removed for now as debug logs not implemented in multi-server version // Save servers to localStorage whenever they change useEffect(() => { diff --git a/examples/chat-ui/src/components/OAuthCallback.tsx b/examples/chat-ui/src/components/OAuthCallback.tsx new file mode 100644 index 0000000..b7a288b --- /dev/null +++ b/examples/chat-ui/src/components/OAuthCallback.tsx @@ -0,0 +1,22 @@ +import { useEffect } from 'react' +import { onMcpAuthorization } from 'use-mcp' + +export function OAuthCallback() { + useEffect(() => { + onMcpAuthorization() + }, []) + + return ( +
+
+

Authenticating...

+

Please wait while we complete your authentication.

+

This window should close automatically.

+ +
+
+
+
+
+ ) +}