From 0891077402e9ccae3ec179aea1d27f6cb2b5352b Mon Sep 17 00:00:00 2001 From: cliffhall Date: Thu, 27 Mar 2025 15:59:41 -0400 Subject: [PATCH 1/3] This fixes the linter's warning that Hooks must be run in order each time. * In App.tsx - move the conditional that returns suspense if the path is oauth/callback to the end of the component after all hooks, rendering either suspense or the normal component. - move handleApproveSampling and handleRejectSampling functions down below all the hooks for clarity. There are a lot of hooks so finding the end of them is a scroll, and these function constants aren't referenced until the rendering section below anyway. --- client/src/App.tsx | 532 +++++++++++++++++++++++---------------------- 1 file changed, 267 insertions(+), 265 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 0b0a3e13d..3d35e835d 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -52,16 +52,6 @@ const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`; const App = () => { // Handle OAuth callback route - if (window.location.pathname === "/oauth/callback") { - const OAuthCallback = React.lazy( - () => import("./components/OAuthCallback"), - ); - return ( - Loading...}> - - - ); - } const [resources, setResources] = useState([]); const [resourceTemplates, setResourceTemplates] = useState< ResourceTemplate[] @@ -114,22 +104,6 @@ const App = () => { const nextRequestId = useRef(0); const rootsRef = useRef([]); - const handleApproveSampling = (id: number, result: CreateMessageResult) => { - setPendingSampleRequests((prev) => { - const request = prev.find((r) => r.id === id); - request?.resolve(result); - return prev.filter((r) => r.id !== id); - }); - }; - - const handleRejectSampling = (id: number) => { - setPendingSampleRequests((prev) => { - const request = prev.find((r) => r.id === id); - request?.reject(new Error("Sampling request rejected")); - return prev.filter((r) => r.id !== id); - }); - }; - const [selectedResource, setSelectedResource] = useState( null, ); @@ -224,7 +198,7 @@ const App = () => { // Connect to the server connectMcpServer(); } - }, []); + }, [connectMcpServer]); useEffect(() => { fetch(`${PROXY_SERVER_URL}/config`) @@ -253,6 +227,22 @@ const App = () => { } }, []); + const handleApproveSampling = (id: number, result: CreateMessageResult) => { + setPendingSampleRequests((prev) => { + const request = prev.find((r) => r.id === id); + request?.resolve(result); + return prev.filter((r) => r.id !== id); + }); + }; + + const handleRejectSampling = (id: number) => { + setPendingSampleRequests((prev) => { + const request = prev.find((r) => r.id === id); + request?.reject(new Error("Sampling request rejected")); + return prev.filter((r) => r.id !== id); + }); + }; + const clearError = (tabKey: keyof typeof errors) => { setErrors((prev) => ({ ...prev, [tabKey]: null })); }; @@ -425,251 +415,263 @@ const App = () => { setLogLevel(level); }; - return ( -
- -
-
- {mcpClient ? ( - (window.location.hash = value)} - > - - - - Resources - - - - Prompts - - - - Tools - - - - Ping - - - - Sampling - {pendingSampleRequests.length > 0 && ( - - {pendingSampleRequests.length} - + if (window.location.pathname === "/oauth/callback") { + const OAuthCallback = React.lazy( + () => import("./components/OAuthCallback"), + ); + return ( + Loading...
}> + + + ); + } else { + return ( +
+ +
+
+ {mcpClient ? ( + (window.location.hash = value)} + > + + + + Resources + + + + Prompts + + + + Tools + + + + Ping + + + + Sampling + {pendingSampleRequests.length > 0 && ( + + {pendingSampleRequests.length} + + )} + + + + Roots + + + +
+ {!serverCapabilities?.resources && + !serverCapabilities?.prompts && + !serverCapabilities?.tools ? ( +
+

+ The connected server does not support any MCP + capabilities +

+
+ ) : ( + <> + { + clearError("resources"); + listResources(); + }} + clearResources={() => { + setResources([]); + setNextResourceCursor(undefined); + }} + listResourceTemplates={() => { + clearError("resources"); + listResourceTemplates(); + }} + clearResourceTemplates={() => { + setResourceTemplates([]); + setNextResourceTemplateCursor(undefined); + }} + readResource={(uri) => { + clearError("resources"); + readResource(uri); + }} + selectedResource={selectedResource} + setSelectedResource={(resource) => { + clearError("resources"); + setSelectedResource(resource); + }} + resourceSubscriptionsSupported={ + serverCapabilities?.resources?.subscribe || false + } + resourceSubscriptions={resourceSubscriptions} + subscribeToResource={(uri) => { + clearError("resources"); + subscribeToResource(uri); + }} + unsubscribeFromResource={(uri) => { + clearError("resources"); + unsubscribeFromResource(uri); + }} + handleCompletion={handleCompletion} + completionsSupported={completionsSupported} + resourceContent={resourceContent} + nextCursor={nextResourceCursor} + nextTemplateCursor={nextResourceTemplateCursor} + error={errors.resources} + /> + { + clearError("prompts"); + listPrompts(); + }} + clearPrompts={() => { + setPrompts([]); + setNextPromptCursor(undefined); + }} + getPrompt={(name, args) => { + clearError("prompts"); + getPrompt(name, args); + }} + selectedPrompt={selectedPrompt} + setSelectedPrompt={(prompt) => { + clearError("prompts"); + setSelectedPrompt(prompt); + }} + handleCompletion={handleCompletion} + completionsSupported={completionsSupported} + promptContent={promptContent} + nextCursor={nextPromptCursor} + error={errors.prompts} + /> + { + clearError("tools"); + listTools(); + }} + clearTools={() => { + setTools([]); + setNextToolCursor(undefined); + }} + callTool={(name, params) => { + clearError("tools"); + callTool(name, params); + }} + selectedTool={selectedTool} + setSelectedTool={(tool) => { + clearError("tools"); + setSelectedTool(tool); + setToolResult(null); + }} + toolResult={toolResult} + nextCursor={nextToolCursor} + error={errors.tools} + /> + + { + void makeRequest( + { + method: "ping" as const, + }, + EmptyResultSchema, + ); + }} + /> + + + )} - - - - Roots - - - -
- {!serverCapabilities?.resources && - !serverCapabilities?.prompts && - !serverCapabilities?.tools ? ( -
-

- The connected server does not support any MCP capabilities -

-
- ) : ( - <> - { - clearError("resources"); - listResources(); - }} - clearResources={() => { - setResources([]); - setNextResourceCursor(undefined); - }} - listResourceTemplates={() => { - clearError("resources"); - listResourceTemplates(); - }} - clearResourceTemplates={() => { - setResourceTemplates([]); - setNextResourceTemplateCursor(undefined); - }} - readResource={(uri) => { - clearError("resources"); - readResource(uri); - }} - selectedResource={selectedResource} - setSelectedResource={(resource) => { - clearError("resources"); - setSelectedResource(resource); - }} - resourceSubscriptionsSupported={ - serverCapabilities?.resources?.subscribe || false - } - resourceSubscriptions={resourceSubscriptions} - subscribeToResource={(uri) => { - clearError("resources"); - subscribeToResource(uri); - }} - unsubscribeFromResource={(uri) => { - clearError("resources"); - unsubscribeFromResource(uri); - }} - handleCompletion={handleCompletion} - completionsSupported={completionsSupported} - resourceContent={resourceContent} - nextCursor={nextResourceCursor} - nextTemplateCursor={nextResourceTemplateCursor} - error={errors.resources} - /> - { - clearError("prompts"); - listPrompts(); - }} - clearPrompts={() => { - setPrompts([]); - setNextPromptCursor(undefined); - }} - getPrompt={(name, args) => { - clearError("prompts"); - getPrompt(name, args); - }} - selectedPrompt={selectedPrompt} - setSelectedPrompt={(prompt) => { - clearError("prompts"); - setSelectedPrompt(prompt); - }} - handleCompletion={handleCompletion} - completionsSupported={completionsSupported} - promptContent={promptContent} - nextCursor={nextPromptCursor} - error={errors.prompts} - /> - { - clearError("tools"); - listTools(); - }} - clearTools={() => { - setTools([]); - setNextToolCursor(undefined); - }} - callTool={(name, params) => { - clearError("tools"); - callTool(name, params); - }} - selectedTool={selectedTool} - setSelectedTool={(tool) => { - clearError("tools"); - setSelectedTool(tool); - setToolResult(null); - }} - toolResult={toolResult} - nextCursor={nextToolCursor} - error={errors.tools} - /> - - { - void makeRequest( - { - method: "ping" as const, - }, - EmptyResultSchema, - ); - }} - /> - - - - )} +
+ + ) : ( +
+

+ Connect to an MCP server to start inspecting +

- - ) : ( -
-

- Connect to an MCP server to start inspecting -

-
- )} -
-
+ )} +
-
-
-
- +
+
+
+
+ +
-
- ); + ); + } }; export default App; From 84335ae5f4e364346c5a4c4a2de35ddb49c53b82 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 28 Mar 2025 15:56:10 -0400 Subject: [PATCH 2/3] Remove else block wrapper for final component return to reduce indenting and hopefully make the PR diff easier to approve. --- client/src/App.tsx | 482 ++++++++++++++++++++++----------------------- 1 file changed, 241 insertions(+), 241 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 3d35e835d..37aff6f20 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -424,254 +424,254 @@ const App = () => { ); - } else { - return ( -
- -
-
- {mcpClient ? ( - (window.location.hash = value)} - > - - - - Resources - - - - Prompts - - - - Tools - - - - Ping - - - - Sampling - {pendingSampleRequests.length > 0 && ( - - {pendingSampleRequests.length} - - )} - - - - Roots - - - -
- {!serverCapabilities?.resources && - !serverCapabilities?.prompts && - !serverCapabilities?.tools ? ( -
-

- The connected server does not support any MCP - capabilities -

-
- ) : ( - <> - { - clearError("resources"); - listResources(); - }} - clearResources={() => { - setResources([]); - setNextResourceCursor(undefined); - }} - listResourceTemplates={() => { - clearError("resources"); - listResourceTemplates(); - }} - clearResourceTemplates={() => { - setResourceTemplates([]); - setNextResourceTemplateCursor(undefined); - }} - readResource={(uri) => { - clearError("resources"); - readResource(uri); - }} - selectedResource={selectedResource} - setSelectedResource={(resource) => { - clearError("resources"); - setSelectedResource(resource); - }} - resourceSubscriptionsSupported={ - serverCapabilities?.resources?.subscribe || false - } - resourceSubscriptions={resourceSubscriptions} - subscribeToResource={(uri) => { - clearError("resources"); - subscribeToResource(uri); - }} - unsubscribeFromResource={(uri) => { - clearError("resources"); - unsubscribeFromResource(uri); - }} - handleCompletion={handleCompletion} - completionsSupported={completionsSupported} - resourceContent={resourceContent} - nextCursor={nextResourceCursor} - nextTemplateCursor={nextResourceTemplateCursor} - error={errors.resources} - /> - { - clearError("prompts"); - listPrompts(); - }} - clearPrompts={() => { - setPrompts([]); - setNextPromptCursor(undefined); - }} - getPrompt={(name, args) => { - clearError("prompts"); - getPrompt(name, args); - }} - selectedPrompt={selectedPrompt} - setSelectedPrompt={(prompt) => { - clearError("prompts"); - setSelectedPrompt(prompt); - }} - handleCompletion={handleCompletion} - completionsSupported={completionsSupported} - promptContent={promptContent} - nextCursor={nextPromptCursor} - error={errors.prompts} - /> - { - clearError("tools"); - listTools(); - }} - clearTools={() => { - setTools([]); - setNextToolCursor(undefined); - }} - callTool={(name, params) => { - clearError("tools"); - callTool(name, params); - }} - selectedTool={selectedTool} - setSelectedTool={(tool) => { - clearError("tools"); - setSelectedTool(tool); - setToolResult(null); - }} - toolResult={toolResult} - nextCursor={nextToolCursor} - error={errors.tools} - /> - - { - void makeRequest( - { - method: "ping" as const, - }, - EmptyResultSchema, - ); - }} - /> - - - + } + + return ( +
+ +
+
+ {mcpClient ? ( + (window.location.hash = value)} + > + + + + Resources + + + + Prompts + + + + Tools + + + + Ping + + + + Sampling + {pendingSampleRequests.length > 0 && ( + + {pendingSampleRequests.length} + )} -
- - ) : ( -
-

- Connect to an MCP server to start inspecting -

+ + + + Roots + + + +
+ {!serverCapabilities?.resources && + !serverCapabilities?.prompts && + !serverCapabilities?.tools ? ( +
+

+ The connected server does not support any MCP + capabilities +

+
+ ) : ( + <> + { + clearError("resources"); + listResources(); + }} + clearResources={() => { + setResources([]); + setNextResourceCursor(undefined); + }} + listResourceTemplates={() => { + clearError("resources"); + listResourceTemplates(); + }} + clearResourceTemplates={() => { + setResourceTemplates([]); + setNextResourceTemplateCursor(undefined); + }} + readResource={(uri) => { + clearError("resources"); + readResource(uri); + }} + selectedResource={selectedResource} + setSelectedResource={(resource) => { + clearError("resources"); + setSelectedResource(resource); + }} + resourceSubscriptionsSupported={ + serverCapabilities?.resources?.subscribe || false + } + resourceSubscriptions={resourceSubscriptions} + subscribeToResource={(uri) => { + clearError("resources"); + subscribeToResource(uri); + }} + unsubscribeFromResource={(uri) => { + clearError("resources"); + unsubscribeFromResource(uri); + }} + handleCompletion={handleCompletion} + completionsSupported={completionsSupported} + resourceContent={resourceContent} + nextCursor={nextResourceCursor} + nextTemplateCursor={nextResourceTemplateCursor} + error={errors.resources} + /> + { + clearError("prompts"); + listPrompts(); + }} + clearPrompts={() => { + setPrompts([]); + setNextPromptCursor(undefined); + }} + getPrompt={(name, args) => { + clearError("prompts"); + getPrompt(name, args); + }} + selectedPrompt={selectedPrompt} + setSelectedPrompt={(prompt) => { + clearError("prompts"); + setSelectedPrompt(prompt); + }} + handleCompletion={handleCompletion} + completionsSupported={completionsSupported} + promptContent={promptContent} + nextCursor={nextPromptCursor} + error={errors.prompts} + /> + { + clearError("tools"); + listTools(); + }} + clearTools={() => { + setTools([]); + setNextToolCursor(undefined); + }} + callTool={(name, params) => { + clearError("tools"); + callTool(name, params); + }} + selectedTool={selectedTool} + setSelectedTool={(tool) => { + clearError("tools"); + setSelectedTool(tool); + setToolResult(null); + }} + toolResult={toolResult} + nextCursor={nextToolCursor} + error={errors.tools} + /> + + { + void makeRequest( + { + method: "ping" as const, + }, + EmptyResultSchema, + ); + }} + /> + + + + )}
- )} -
+ + ) : ( +
+

+ Connect to an MCP server to start inspecting +

+
+ )} +
+
-
-
-
-
- -
+
+
+
+
- ); - } +
+ ); }; export default App; From 7227909df337cc549cf7b34981160ea78dee4a0a Mon Sep 17 00:00:00 2001 From: cliffhall Date: Fri, 28 Mar 2025 16:03:39 -0400 Subject: [PATCH 3/3] Fix spurious linebreak --- client/src/App.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/src/App.tsx b/client/src/App.tsx index 37aff6f20..27078edfa 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -515,8 +515,7 @@ const App = () => { !serverCapabilities?.tools ? (

- The connected server does not support any MCP - capabilities + The connected server does not support any MCP capabilities

) : (