refactor: simplify capability handling and remove context provider

- Remove redundant useEffect for capability checking
- Remove CapabilityContext provider pattern
- Set default tab to first supported capability
- Add fallback UI for unsupported capabilities
- Delete unused contexts.ts file
This commit is contained in:
Devin AI
2024-12-09 10:11:03 +00:00
parent e96b3be159
commit d857e1462b
2 changed files with 178 additions and 159 deletions

View File

@@ -19,10 +19,11 @@ import {
Request, Request,
Resource, Resource,
ResourceTemplate, ResourceTemplate,
Result,
Root, Root,
ServerNotification, ServerNotification,
Tool, Tool,
ServerCapabilitiesSchema,
Result,
} from "@modelcontextprotocol/sdk/types.js"; } from "@modelcontextprotocol/sdk/types.js";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
@@ -43,7 +44,7 @@ import {
} from "lucide-react"; } from "lucide-react";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import { ZodType } from "zod"; import { z, type ZodType } from "zod";
import "./App.css"; import "./App.css";
import ConsoleTab from "./components/ConsoleTab"; import ConsoleTab from "./components/ConsoleTab";
import HistoryAndNotifications from "./components/History"; import HistoryAndNotifications from "./components/History";
@@ -55,7 +56,12 @@ import SamplingTab, { PendingRequest } from "./components/SamplingTab";
import Sidebar from "./components/Sidebar"; import Sidebar from "./components/Sidebar";
import ToolsTab from "./components/ToolsTab"; import ToolsTab from "./components/ToolsTab";
import { CapabilityContext, ServerCapabilities } from "@/lib/contexts"; type ServerCapabilities = z.infer<typeof ServerCapabilitiesSchema>;
type ServerCapabilities = z.infer<typeof ServerCapabilitiesSchema>;
type ServerCapabilities = z.infer<typeof ServerCapabilitiesSchema>;
const DEFAULT_REQUEST_TIMEOUT_MSEC = 10000; const DEFAULT_REQUEST_TIMEOUT_MSEC = 10000;
@@ -220,12 +226,7 @@ const App = () => {
rootsRef.current = roots; rootsRef.current = roots;
}, [roots]); }, [roots]);
useEffect(() => {
if (mcpClient) {
const capabilities = mcpClient.getServerCapabilities();
setServerCapabilities(capabilities ?? null);
}
}, [mcpClient]);
const pushHistory = (request: object, response?: object) => { const pushHistory = (request: object, response?: object) => {
setRequestHistory((prev) => [ setRequestHistory((prev) => [
@@ -498,145 +499,175 @@ const App = () => {
<div className="flex-1 flex flex-col overflow-hidden"> <div className="flex-1 flex flex-col overflow-hidden">
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
{mcpClient ? ( {mcpClient ? (
<CapabilityContext.Provider value={serverCapabilities}> <Tabs
<Tabs defaultValue="resources" className="w-full p-4"> defaultValue={
<TabsList className="mb-4 p-0"> serverCapabilities?.resources ? "resources" :
<TabsTrigger value="resources" disabled={!serverCapabilities?.resources}> serverCapabilities?.prompts ? "prompts" :
<Files className="w-4 h-4 mr-2" /> serverCapabilities?.tools ? "tools" :
Resources "ping"
</TabsTrigger> }
<TabsTrigger value="prompts" disabled={!serverCapabilities?.prompts}> className="w-full p-4"
<MessageSquare className="w-4 h-4 mr-2" /> >
Prompts <TabsList className="mb-4 p-0">
</TabsTrigger> <TabsTrigger value="resources">
<TabsTrigger value="tools" disabled={!serverCapabilities?.tools}> <Files className="w-4 h-4 mr-2" />
<Hammer className="w-4 h-4 mr-2" /> Resources
Tools </TabsTrigger>
</TabsTrigger> <TabsTrigger value="prompts">
<TabsTrigger value="ping"> <MessageSquare className="w-4 h-4 mr-2" />
<Bell className="w-4 h-4 mr-2" /> Prompts
Ping </TabsTrigger>
</TabsTrigger> <TabsTrigger value="tools">
<TabsTrigger value="sampling" className="relative"> <Hammer className="w-4 h-4 mr-2" />
<Hash className="w-4 h-4 mr-2" /> Tools
Sampling </TabsTrigger>
{pendingSampleRequests.length > 0 && ( <TabsTrigger value="ping">
<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"> <Bell className="w-4 h-4 mr-2" />
{pendingSampleRequests.length} Ping
</span> </TabsTrigger>
)} <TabsTrigger value="sampling" className="relative">
</TabsTrigger> <Hash className="w-4 h-4 mr-2" />
<TabsTrigger value="roots"> Sampling
<FolderTree className="w-4 h-4 mr-2" /> {pendingSampleRequests.length > 0 && (
Roots <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">
</TabsTrigger> {pendingSampleRequests.length}
</TabsList> </span>
)}
</TabsTrigger>
<TabsTrigger value="roots">
<FolderTree className="w-4 h-4 mr-2" />
Roots
</TabsTrigger>
</TabsList>
<div className="w-full"> <div className="w-full">
<ResourcesTab {!serverCapabilities?.resources && !serverCapabilities?.prompts && !serverCapabilities?.tools ? (
resources={resources} <div className="flex items-center justify-center p-4">
resourceTemplates={resourceTemplates} <p className="text-lg text-gray-500">
listResources={() => { The connected server does not support any MCP capabilities
clearError("resources"); </p>
listResources(); </div>
}} ) : (
clearResources={() => { <>
setResources([]); <ResourcesTab
setNextResourceCursor(undefined); resources={resources}
}} resourceTemplates={resourceTemplates}
listResourceTemplates={() => { listResources={() => {
clearError("resources"); clearError("resources");
listResourceTemplates(); if (serverCapabilities?.resources) {
}} listResources();
clearResourceTemplates={() => { }
setResourceTemplates([]); }}
setNextResourceTemplateCursor(undefined); clearResources={() => {
}} setResources([]);
readResource={(uri) => { setNextResourceCursor(undefined);
clearError("resources"); }}
readResource(uri); listResourceTemplates={() => {
}} clearError("resources");
selectedResource={selectedResource} if (serverCapabilities?.resources) {
setSelectedResource={(resource) => { listResourceTemplates();
clearError("resources"); }
setSelectedResource(resource); }}
}} clearResourceTemplates={() => {
resourceContent={resourceContent} setResourceTemplates([]);
nextCursor={nextResourceCursor} setNextResourceTemplateCursor(undefined);
nextTemplateCursor={nextResourceTemplateCursor} }}
error={errors.resources} readResource={(uri) => {
/> clearError("resources");
<PromptsTab if (serverCapabilities?.resources) {
prompts={prompts} readResource(uri);
listPrompts={() => { }
clearError("prompts"); }}
listPrompts(); selectedResource={selectedResource}
}} setSelectedResource={(resource) => {
clearPrompts={() => { clearError("resources");
setPrompts([]); setSelectedResource(resource);
setNextPromptCursor(undefined); }}
}} resourceContent={resourceContent}
getPrompt={(name, args) => { nextCursor={nextResourceCursor}
clearError("prompts"); nextTemplateCursor={nextResourceTemplateCursor}
getPrompt(name, args); error={errors.resources}
}} />
selectedPrompt={selectedPrompt} <PromptsTab
setSelectedPrompt={(prompt) => { prompts={prompts}
clearError("prompts"); listPrompts={() => {
setSelectedPrompt(prompt); clearError("prompts");
}} if (serverCapabilities?.prompts) {
promptContent={promptContent} listPrompts();
nextCursor={nextPromptCursor} }
error={errors.prompts} }}
/> clearPrompts={() => {
<ToolsTab setPrompts([]);
tools={tools} setNextPromptCursor(undefined);
listTools={() => { }}
clearError("tools"); getPrompt={(name, args) => {
listTools(); clearError("prompts");
}} if (serverCapabilities?.prompts) {
clearTools={() => { getPrompt(name, args);
setTools([]); }
setNextToolCursor(undefined); }}
}} selectedPrompt={selectedPrompt}
callTool={(name, params) => { setSelectedPrompt={(prompt) => {
clearError("tools"); clearError("prompts");
callTool(name, params); setSelectedPrompt(prompt);
}} }}
selectedTool={selectedTool} promptContent={promptContent}
setSelectedTool={(tool) => { nextCursor={nextPromptCursor}
clearError("tools"); error={errors.prompts}
setSelectedTool(tool); />
setToolResult(null); <ToolsTab
}} tools={tools}
toolResult={toolResult} listTools={() => {
nextCursor={nextToolCursor} clearError("tools");
error={errors.tools} if (serverCapabilities?.tools) {
/> listTools();
<ConsoleTab /> }
<PingTab }}
onPingClick={() => { clearTools={() => {
void makeRequest( setTools([]);
{ setNextToolCursor(undefined);
method: "ping" as const, }}
}, callTool={(name, params) => {
EmptyResultSchema, clearError("tools");
); if (serverCapabilities?.tools) {
}} callTool(name, params);
/> }
<SamplingTab }}
pendingRequests={pendingSampleRequests} selectedTool={selectedTool}
onApprove={handleApproveSampling} setSelectedTool={(tool) => {
onReject={handleRejectSampling} clearError("tools");
/> setSelectedTool(tool);
<RootsTab setToolResult(null);
roots={roots} }}
setRoots={setRoots} toolResult={toolResult}
onRootsChange={handleRootsChange} nextCursor={nextToolCursor}
/> error={errors.tools}
</div> />
</Tabs> <ConsoleTab />
</CapabilityContext.Provider> <PingTab
onPingClick={() => {
void makeRequest(
{
method: "ping" as const,
},
EmptyResultSchema,
);
}}
/>
<SamplingTab
pendingRequests={pendingSampleRequests}
onApprove={handleApproveSampling}
onReject={handleRejectSampling}
/>
<RootsTab
roots={roots}
setRoots={setRoots}
onRootsChange={handleRootsChange}
/>
</>
)}
</div>
</Tabs>
) : ( ) : (
<div className="flex items-center justify-center h-full"> <div className="flex items-center justify-center h-full">
<p className="text-lg text-gray-500"> <p className="text-lg text-gray-500">

View File

@@ -1,12 +0,0 @@
import { createContext, useContext } from "react";
import { ServerCapabilitiesSchema } from "@modelcontextprotocol/sdk/types.js";
import type { z } from "zod";
export type ServerCapabilities = z.infer<typeof ServerCapabilitiesSchema>;
export const CapabilityContext = createContext<ServerCapabilities | null>(null);
export function useServerCapability(capability: keyof ServerCapabilities): boolean {
const capabilities = useContext(CapabilityContext);
return !!capabilities?.[capability];
}