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.
This commit is contained in:
@@ -52,16 +52,6 @@ const PROXY_SERVER_URL = `http://${window.location.hostname}:${PROXY_PORT}`;
|
|||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
// Handle OAuth callback route
|
// Handle OAuth callback route
|
||||||
if (window.location.pathname === "/oauth/callback") {
|
|
||||||
const OAuthCallback = React.lazy(
|
|
||||||
() => import("./components/OAuthCallback"),
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Suspense fallback={<div>Loading...</div>}>
|
|
||||||
<OAuthCallback />
|
|
||||||
</Suspense>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const [resources, setResources] = useState<Resource[]>([]);
|
const [resources, setResources] = useState<Resource[]>([]);
|
||||||
const [resourceTemplates, setResourceTemplates] = useState<
|
const [resourceTemplates, setResourceTemplates] = useState<
|
||||||
ResourceTemplate[]
|
ResourceTemplate[]
|
||||||
@@ -114,22 +104,6 @@ const App = () => {
|
|||||||
const nextRequestId = useRef(0);
|
const nextRequestId = useRef(0);
|
||||||
const rootsRef = useRef<Root[]>([]);
|
const rootsRef = useRef<Root[]>([]);
|
||||||
|
|
||||||
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<Resource | null>(
|
const [selectedResource, setSelectedResource] = useState<Resource | null>(
|
||||||
null,
|
null,
|
||||||
);
|
);
|
||||||
@@ -224,7 +198,7 @@ const App = () => {
|
|||||||
// Connect to the server
|
// Connect to the server
|
||||||
connectMcpServer();
|
connectMcpServer();
|
||||||
}
|
}
|
||||||
}, []);
|
}, [connectMcpServer]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch(`${PROXY_SERVER_URL}/config`)
|
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) => {
|
const clearError = (tabKey: keyof typeof errors) => {
|
||||||
setErrors((prev) => ({ ...prev, [tabKey]: null }));
|
setErrors((prev) => ({ ...prev, [tabKey]: null }));
|
||||||
};
|
};
|
||||||
@@ -425,251 +415,263 @@ const App = () => {
|
|||||||
setLogLevel(level);
|
setLogLevel(level);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
if (window.location.pathname === "/oauth/callback") {
|
||||||
<div className="flex h-screen bg-background">
|
const OAuthCallback = React.lazy(
|
||||||
<Sidebar
|
() => import("./components/OAuthCallback"),
|
||||||
connectionStatus={connectionStatus}
|
);
|
||||||
transportType={transportType}
|
return (
|
||||||
setTransportType={setTransportType}
|
<Suspense fallback={<div>Loading...</div>}>
|
||||||
command={command}
|
<OAuthCallback />
|
||||||
setCommand={setCommand}
|
</Suspense>
|
||||||
args={args}
|
);
|
||||||
setArgs={setArgs}
|
} else {
|
||||||
sseUrl={sseUrl}
|
return (
|
||||||
setSseUrl={setSseUrl}
|
<div className="flex h-screen bg-background">
|
||||||
env={env}
|
<Sidebar
|
||||||
setEnv={setEnv}
|
connectionStatus={connectionStatus}
|
||||||
bearerToken={bearerToken}
|
transportType={transportType}
|
||||||
setBearerToken={setBearerToken}
|
setTransportType={setTransportType}
|
||||||
onConnect={connectMcpServer}
|
command={command}
|
||||||
stdErrNotifications={stdErrNotifications}
|
setCommand={setCommand}
|
||||||
logLevel={logLevel}
|
args={args}
|
||||||
sendLogLevelRequest={sendLogLevelRequest}
|
setArgs={setArgs}
|
||||||
loggingSupported={!!serverCapabilities?.logging || false}
|
sseUrl={sseUrl}
|
||||||
/>
|
setSseUrl={setSseUrl}
|
||||||
<div className="flex-1 flex flex-col overflow-hidden">
|
env={env}
|
||||||
<div className="flex-1 overflow-auto">
|
setEnv={setEnv}
|
||||||
{mcpClient ? (
|
bearerToken={bearerToken}
|
||||||
<Tabs
|
setBearerToken={setBearerToken}
|
||||||
defaultValue={
|
onConnect={connectMcpServer}
|
||||||
Object.keys(serverCapabilities ?? {}).includes(
|
stdErrNotifications={stdErrNotifications}
|
||||||
window.location.hash.slice(1),
|
logLevel={logLevel}
|
||||||
)
|
sendLogLevelRequest={sendLogLevelRequest}
|
||||||
? window.location.hash.slice(1)
|
loggingSupported={!!serverCapabilities?.logging || false}
|
||||||
: serverCapabilities?.resources
|
/>
|
||||||
? "resources"
|
<div className="flex-1 flex flex-col overflow-hidden">
|
||||||
: serverCapabilities?.prompts
|
<div className="flex-1 overflow-auto">
|
||||||
? "prompts"
|
{mcpClient ? (
|
||||||
: serverCapabilities?.tools
|
<Tabs
|
||||||
? "tools"
|
defaultValue={
|
||||||
: "ping"
|
Object.keys(serverCapabilities ?? {}).includes(
|
||||||
}
|
window.location.hash.slice(1),
|
||||||
className="w-full p-4"
|
)
|
||||||
onValueChange={(value) => (window.location.hash = value)}
|
? window.location.hash.slice(1)
|
||||||
>
|
: serverCapabilities?.resources
|
||||||
<TabsList className="mb-4 p-0">
|
? "resources"
|
||||||
<TabsTrigger
|
: serverCapabilities?.prompts
|
||||||
value="resources"
|
? "prompts"
|
||||||
disabled={!serverCapabilities?.resources}
|
: serverCapabilities?.tools
|
||||||
>
|
? "tools"
|
||||||
<Files className="w-4 h-4 mr-2" />
|
: "ping"
|
||||||
Resources
|
}
|
||||||
</TabsTrigger>
|
className="w-full p-4"
|
||||||
<TabsTrigger
|
onValueChange={(value) => (window.location.hash = value)}
|
||||||
value="prompts"
|
>
|
||||||
disabled={!serverCapabilities?.prompts}
|
<TabsList className="mb-4 p-0">
|
||||||
>
|
<TabsTrigger
|
||||||
<MessageSquare className="w-4 h-4 mr-2" />
|
value="resources"
|
||||||
Prompts
|
disabled={!serverCapabilities?.resources}
|
||||||
</TabsTrigger>
|
>
|
||||||
<TabsTrigger
|
<Files className="w-4 h-4 mr-2" />
|
||||||
value="tools"
|
Resources
|
||||||
disabled={!serverCapabilities?.tools}
|
</TabsTrigger>
|
||||||
>
|
<TabsTrigger
|
||||||
<Hammer className="w-4 h-4 mr-2" />
|
value="prompts"
|
||||||
Tools
|
disabled={!serverCapabilities?.prompts}
|
||||||
</TabsTrigger>
|
>
|
||||||
<TabsTrigger value="ping">
|
<MessageSquare className="w-4 h-4 mr-2" />
|
||||||
<Bell className="w-4 h-4 mr-2" />
|
Prompts
|
||||||
Ping
|
</TabsTrigger>
|
||||||
</TabsTrigger>
|
<TabsTrigger
|
||||||
<TabsTrigger value="sampling" className="relative">
|
value="tools"
|
||||||
<Hash className="w-4 h-4 mr-2" />
|
disabled={!serverCapabilities?.tools}
|
||||||
Sampling
|
>
|
||||||
{pendingSampleRequests.length > 0 && (
|
<Hammer className="w-4 h-4 mr-2" />
|
||||||
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
|
Tools
|
||||||
{pendingSampleRequests.length}
|
</TabsTrigger>
|
||||||
</span>
|
<TabsTrigger value="ping">
|
||||||
)}
|
<Bell className="w-4 h-4 mr-2" />
|
||||||
</TabsTrigger>
|
Ping
|
||||||
<TabsTrigger value="roots">
|
</TabsTrigger>
|
||||||
<FolderTree className="w-4 h-4 mr-2" />
|
<TabsTrigger value="sampling" className="relative">
|
||||||
Roots
|
<Hash className="w-4 h-4 mr-2" />
|
||||||
</TabsTrigger>
|
Sampling
|
||||||
</TabsList>
|
{pendingSampleRequests.length > 0 && (
|
||||||
|
<span className="absolute -top-1 -right-1 bg-red-500 text-white text-xs rounded-full h-4 w-4 flex items-center justify-center">
|
||||||
|
{pendingSampleRequests.length}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="roots">
|
||||||
|
<FolderTree className="w-4 h-4 mr-2" />
|
||||||
|
Roots
|
||||||
|
</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{!serverCapabilities?.resources &&
|
{!serverCapabilities?.resources &&
|
||||||
!serverCapabilities?.prompts &&
|
!serverCapabilities?.prompts &&
|
||||||
!serverCapabilities?.tools ? (
|
!serverCapabilities?.tools ? (
|
||||||
<div className="flex items-center justify-center p-4">
|
<div className="flex items-center justify-center p-4">
|
||||||
<p className="text-lg text-gray-500">
|
<p className="text-lg text-gray-500">
|
||||||
The connected server does not support any MCP capabilities
|
The connected server does not support any MCP
|
||||||
</p>
|
capabilities
|
||||||
</div>
|
</p>
|
||||||
) : (
|
</div>
|
||||||
<>
|
) : (
|
||||||
<ResourcesTab
|
<>
|
||||||
resources={resources}
|
<ResourcesTab
|
||||||
resourceTemplates={resourceTemplates}
|
resources={resources}
|
||||||
listResources={() => {
|
resourceTemplates={resourceTemplates}
|
||||||
clearError("resources");
|
listResources={() => {
|
||||||
listResources();
|
clearError("resources");
|
||||||
}}
|
listResources();
|
||||||
clearResources={() => {
|
}}
|
||||||
setResources([]);
|
clearResources={() => {
|
||||||
setNextResourceCursor(undefined);
|
setResources([]);
|
||||||
}}
|
setNextResourceCursor(undefined);
|
||||||
listResourceTemplates={() => {
|
}}
|
||||||
clearError("resources");
|
listResourceTemplates={() => {
|
||||||
listResourceTemplates();
|
clearError("resources");
|
||||||
}}
|
listResourceTemplates();
|
||||||
clearResourceTemplates={() => {
|
}}
|
||||||
setResourceTemplates([]);
|
clearResourceTemplates={() => {
|
||||||
setNextResourceTemplateCursor(undefined);
|
setResourceTemplates([]);
|
||||||
}}
|
setNextResourceTemplateCursor(undefined);
|
||||||
readResource={(uri) => {
|
}}
|
||||||
clearError("resources");
|
readResource={(uri) => {
|
||||||
readResource(uri);
|
clearError("resources");
|
||||||
}}
|
readResource(uri);
|
||||||
selectedResource={selectedResource}
|
}}
|
||||||
setSelectedResource={(resource) => {
|
selectedResource={selectedResource}
|
||||||
clearError("resources");
|
setSelectedResource={(resource) => {
|
||||||
setSelectedResource(resource);
|
clearError("resources");
|
||||||
}}
|
setSelectedResource(resource);
|
||||||
resourceSubscriptionsSupported={
|
}}
|
||||||
serverCapabilities?.resources?.subscribe || false
|
resourceSubscriptionsSupported={
|
||||||
}
|
serverCapabilities?.resources?.subscribe || false
|
||||||
resourceSubscriptions={resourceSubscriptions}
|
}
|
||||||
subscribeToResource={(uri) => {
|
resourceSubscriptions={resourceSubscriptions}
|
||||||
clearError("resources");
|
subscribeToResource={(uri) => {
|
||||||
subscribeToResource(uri);
|
clearError("resources");
|
||||||
}}
|
subscribeToResource(uri);
|
||||||
unsubscribeFromResource={(uri) => {
|
}}
|
||||||
clearError("resources");
|
unsubscribeFromResource={(uri) => {
|
||||||
unsubscribeFromResource(uri);
|
clearError("resources");
|
||||||
}}
|
unsubscribeFromResource(uri);
|
||||||
handleCompletion={handleCompletion}
|
}}
|
||||||
completionsSupported={completionsSupported}
|
handleCompletion={handleCompletion}
|
||||||
resourceContent={resourceContent}
|
completionsSupported={completionsSupported}
|
||||||
nextCursor={nextResourceCursor}
|
resourceContent={resourceContent}
|
||||||
nextTemplateCursor={nextResourceTemplateCursor}
|
nextCursor={nextResourceCursor}
|
||||||
error={errors.resources}
|
nextTemplateCursor={nextResourceTemplateCursor}
|
||||||
/>
|
error={errors.resources}
|
||||||
<PromptsTab
|
/>
|
||||||
prompts={prompts}
|
<PromptsTab
|
||||||
listPrompts={() => {
|
prompts={prompts}
|
||||||
clearError("prompts");
|
listPrompts={() => {
|
||||||
listPrompts();
|
clearError("prompts");
|
||||||
}}
|
listPrompts();
|
||||||
clearPrompts={() => {
|
}}
|
||||||
setPrompts([]);
|
clearPrompts={() => {
|
||||||
setNextPromptCursor(undefined);
|
setPrompts([]);
|
||||||
}}
|
setNextPromptCursor(undefined);
|
||||||
getPrompt={(name, args) => {
|
}}
|
||||||
clearError("prompts");
|
getPrompt={(name, args) => {
|
||||||
getPrompt(name, args);
|
clearError("prompts");
|
||||||
}}
|
getPrompt(name, args);
|
||||||
selectedPrompt={selectedPrompt}
|
}}
|
||||||
setSelectedPrompt={(prompt) => {
|
selectedPrompt={selectedPrompt}
|
||||||
clearError("prompts");
|
setSelectedPrompt={(prompt) => {
|
||||||
setSelectedPrompt(prompt);
|
clearError("prompts");
|
||||||
}}
|
setSelectedPrompt(prompt);
|
||||||
handleCompletion={handleCompletion}
|
}}
|
||||||
completionsSupported={completionsSupported}
|
handleCompletion={handleCompletion}
|
||||||
promptContent={promptContent}
|
completionsSupported={completionsSupported}
|
||||||
nextCursor={nextPromptCursor}
|
promptContent={promptContent}
|
||||||
error={errors.prompts}
|
nextCursor={nextPromptCursor}
|
||||||
/>
|
error={errors.prompts}
|
||||||
<ToolsTab
|
/>
|
||||||
tools={tools}
|
<ToolsTab
|
||||||
listTools={() => {
|
tools={tools}
|
||||||
clearError("tools");
|
listTools={() => {
|
||||||
listTools();
|
clearError("tools");
|
||||||
}}
|
listTools();
|
||||||
clearTools={() => {
|
}}
|
||||||
setTools([]);
|
clearTools={() => {
|
||||||
setNextToolCursor(undefined);
|
setTools([]);
|
||||||
}}
|
setNextToolCursor(undefined);
|
||||||
callTool={(name, params) => {
|
}}
|
||||||
clearError("tools");
|
callTool={(name, params) => {
|
||||||
callTool(name, params);
|
clearError("tools");
|
||||||
}}
|
callTool(name, params);
|
||||||
selectedTool={selectedTool}
|
}}
|
||||||
setSelectedTool={(tool) => {
|
selectedTool={selectedTool}
|
||||||
clearError("tools");
|
setSelectedTool={(tool) => {
|
||||||
setSelectedTool(tool);
|
clearError("tools");
|
||||||
setToolResult(null);
|
setSelectedTool(tool);
|
||||||
}}
|
setToolResult(null);
|
||||||
toolResult={toolResult}
|
}}
|
||||||
nextCursor={nextToolCursor}
|
toolResult={toolResult}
|
||||||
error={errors.tools}
|
nextCursor={nextToolCursor}
|
||||||
/>
|
error={errors.tools}
|
||||||
<ConsoleTab />
|
/>
|
||||||
<PingTab
|
<ConsoleTab />
|
||||||
onPingClick={() => {
|
<PingTab
|
||||||
void makeRequest(
|
onPingClick={() => {
|
||||||
{
|
void makeRequest(
|
||||||
method: "ping" as const,
|
{
|
||||||
},
|
method: "ping" as const,
|
||||||
EmptyResultSchema,
|
},
|
||||||
);
|
EmptyResultSchema,
|
||||||
}}
|
);
|
||||||
/>
|
}}
|
||||||
<SamplingTab
|
/>
|
||||||
pendingRequests={pendingSampleRequests}
|
<SamplingTab
|
||||||
onApprove={handleApproveSampling}
|
pendingRequests={pendingSampleRequests}
|
||||||
onReject={handleRejectSampling}
|
onApprove={handleApproveSampling}
|
||||||
/>
|
onReject={handleRejectSampling}
|
||||||
<RootsTab
|
/>
|
||||||
roots={roots}
|
<RootsTab
|
||||||
setRoots={setRoots}
|
roots={roots}
|
||||||
onRootsChange={handleRootsChange}
|
setRoots={setRoots}
|
||||||
/>
|
onRootsChange={handleRootsChange}
|
||||||
</>
|
/>
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Tabs>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center justify-center h-full">
|
||||||
|
<p className="text-lg text-gray-500">
|
||||||
|
Connect to an MCP server to start inspecting
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Tabs>
|
)}
|
||||||
) : (
|
|
||||||
<div className="flex items-center justify-center h-full">
|
|
||||||
<p className="text-lg text-gray-500">
|
|
||||||
Connect to an MCP server to start inspecting
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="relative border-t border-border"
|
|
||||||
style={{
|
|
||||||
height: `${historyPaneHeight}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="absolute w-full h-4 -top-2 cursor-row-resize flex items-center justify-center hover:bg-accent/50"
|
|
||||||
onMouseDown={handleDragStart}
|
|
||||||
>
|
|
||||||
<div className="w-8 h-1 rounded-full bg-border" />
|
|
||||||
</div>
|
</div>
|
||||||
<div className="h-full overflow-auto">
|
<div
|
||||||
<HistoryAndNotifications
|
className="relative border-t border-border"
|
||||||
requestHistory={requestHistory}
|
style={{
|
||||||
serverNotifications={notifications}
|
height: `${historyPaneHeight}px`,
|
||||||
/>
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="absolute w-full h-4 -top-2 cursor-row-resize flex items-center justify-center hover:bg-accent/50"
|
||||||
|
onMouseDown={handleDragStart}
|
||||||
|
>
|
||||||
|
<div className="w-8 h-1 rounded-full bg-border" />
|
||||||
|
</div>
|
||||||
|
<div className="h-full overflow-auto">
|
||||||
|
<HistoryAndNotifications
|
||||||
|
requestHistory={requestHistory}
|
||||||
|
serverNotifications={notifications}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
|||||||
Reference in New Issue
Block a user