Merge pull request #52 from modelcontextprotocol/justin/tab-specific-errors
Separate error states per tab, clear errors when clicking around
This commit is contained in:
@@ -33,6 +33,7 @@
|
||||
"lucide-react": "^0.447.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-toastify": "^10.0.6",
|
||||
"serve-handler": "^6.1.6",
|
||||
"tailwind-merge": "^2.5.3",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
||||
import {
|
||||
CompatibilityCallToolResultSchema,
|
||||
ClientNotification,
|
||||
ClientRequest,
|
||||
CompatibilityCallToolResult,
|
||||
CompatibilityCallToolResultSchema,
|
||||
CreateMessageRequestSchema,
|
||||
CreateMessageResult,
|
||||
EmptyResultSchema,
|
||||
@@ -19,8 +21,6 @@ import {
|
||||
Root,
|
||||
ServerNotification,
|
||||
Tool,
|
||||
CompatibilityCallToolResult,
|
||||
ClientNotification,
|
||||
} from "@modelcontextprotocol/sdk/types.js";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
// Add dark mode class based on system preference
|
||||
@@ -32,21 +32,21 @@ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
Bell,
|
||||
Files,
|
||||
FolderTree,
|
||||
Hammer,
|
||||
Hash,
|
||||
MessageSquare,
|
||||
Send,
|
||||
Terminal,
|
||||
FolderTree,
|
||||
} from "lucide-react";
|
||||
|
||||
import { toast } from "react-toastify";
|
||||
import { ZodType } from "zod";
|
||||
import "./App.css";
|
||||
import ConsoleTab from "./components/ConsoleTab";
|
||||
import HistoryAndNotifications from "./components/History";
|
||||
import PingTab from "./components/PingTab";
|
||||
import PromptsTab, { Prompt } from "./components/PromptsTab";
|
||||
import RequestsTab from "./components/RequestsTabs";
|
||||
import ResourcesTab from "./components/ResourcesTab";
|
||||
import RootsTab from "./components/RootsTab";
|
||||
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
|
||||
@@ -67,7 +67,11 @@ const App = () => {
|
||||
const [tools, setTools] = useState<Tool[]>([]);
|
||||
const [toolResult, setToolResult] =
|
||||
useState<CompatibilityCallToolResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [errors, setErrors] = useState<Record<string, string | null>>({
|
||||
resources: null,
|
||||
prompts: null,
|
||||
tools: null,
|
||||
});
|
||||
const [command, setCommand] = useState<string>(() => {
|
||||
return localStorage.getItem("lastCommand") || "mcp-server-everything";
|
||||
});
|
||||
@@ -202,9 +206,14 @@ const App = () => {
|
||||
]);
|
||||
};
|
||||
|
||||
const clearError = (tabKey: keyof typeof errors) => {
|
||||
setErrors((prev) => ({ ...prev, [tabKey]: null }));
|
||||
};
|
||||
|
||||
const makeRequest = async <T extends ZodType<object>>(
|
||||
request: ClientRequest,
|
||||
schema: T,
|
||||
tabKey?: keyof typeof errors,
|
||||
) => {
|
||||
if (!mcpClient) {
|
||||
throw new Error("MCP client not connected");
|
||||
@@ -213,9 +222,19 @@ const App = () => {
|
||||
try {
|
||||
const response = await mcpClient.request(request, schema);
|
||||
pushHistory(request, response);
|
||||
|
||||
if (tabKey !== undefined) {
|
||||
clearError(tabKey);
|
||||
}
|
||||
|
||||
return response;
|
||||
} catch (e: unknown) {
|
||||
setError((e as Error).message);
|
||||
if (tabKey === undefined) {
|
||||
toast.error((e as Error).message);
|
||||
} else {
|
||||
setErrors((prev) => ({ ...prev, [tabKey]: (e as Error).message }));
|
||||
}
|
||||
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
@@ -229,7 +248,7 @@ const App = () => {
|
||||
await mcpClient.notification(notification);
|
||||
pushHistory(notification);
|
||||
} catch (e: unknown) {
|
||||
setError((e as Error).message);
|
||||
toast.error((e as Error).message);
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
@@ -241,6 +260,7 @@ const App = () => {
|
||||
params: nextResourceCursor ? { cursor: nextResourceCursor } : {},
|
||||
},
|
||||
ListResourcesResultSchema,
|
||||
"resources",
|
||||
);
|
||||
setResources(resources.concat(response.resources ?? []));
|
||||
setNextResourceCursor(response.nextCursor);
|
||||
@@ -255,6 +275,7 @@ const App = () => {
|
||||
: {},
|
||||
},
|
||||
ListResourceTemplatesResultSchema,
|
||||
"resources",
|
||||
);
|
||||
setResourceTemplates(
|
||||
resourceTemplates.concat(response.resourceTemplates ?? []),
|
||||
@@ -269,6 +290,7 @@ const App = () => {
|
||||
params: { uri },
|
||||
},
|
||||
ReadResourceResultSchema,
|
||||
"resources",
|
||||
);
|
||||
setResourceContent(JSON.stringify(response, null, 2));
|
||||
};
|
||||
@@ -280,6 +302,7 @@ const App = () => {
|
||||
params: nextPromptCursor ? { cursor: nextPromptCursor } : {},
|
||||
},
|
||||
ListPromptsResultSchema,
|
||||
"prompts",
|
||||
);
|
||||
setPrompts(response.prompts);
|
||||
setNextPromptCursor(response.nextCursor);
|
||||
@@ -292,6 +315,7 @@ const App = () => {
|
||||
params: { name, arguments: args },
|
||||
},
|
||||
GetPromptResultSchema,
|
||||
"prompts",
|
||||
);
|
||||
setPromptContent(JSON.stringify(response, null, 2));
|
||||
};
|
||||
@@ -303,6 +327,7 @@ const App = () => {
|
||||
params: nextToolCursor ? { cursor: nextToolCursor } : {},
|
||||
},
|
||||
ListToolsResultSchema,
|
||||
"tools",
|
||||
);
|
||||
setTools(response.tools);
|
||||
setNextToolCursor(response.nextCursor);
|
||||
@@ -321,6 +346,7 @@ const App = () => {
|
||||
},
|
||||
},
|
||||
CompatibilityCallToolResultSchema,
|
||||
"tools",
|
||||
);
|
||||
setToolResult(response);
|
||||
};
|
||||
@@ -445,39 +471,66 @@ const App = () => {
|
||||
<ResourcesTab
|
||||
resources={resources}
|
||||
resourceTemplates={resourceTemplates}
|
||||
listResources={listResources}
|
||||
listResourceTemplates={listResourceTemplates}
|
||||
readResource={readResource}
|
||||
listResources={() => {
|
||||
clearError("resources");
|
||||
listResources();
|
||||
}}
|
||||
listResourceTemplates={() => {
|
||||
clearError("resources");
|
||||
listResourceTemplates();
|
||||
}}
|
||||
readResource={(uri) => {
|
||||
clearError("resources");
|
||||
readResource(uri);
|
||||
}}
|
||||
selectedResource={selectedResource}
|
||||
setSelectedResource={setSelectedResource}
|
||||
setSelectedResource={(resource) => {
|
||||
clearError("resources");
|
||||
setSelectedResource(resource);
|
||||
}}
|
||||
resourceContent={resourceContent}
|
||||
nextCursor={nextResourceCursor}
|
||||
nextTemplateCursor={nextResourceTemplateCursor}
|
||||
error={error}
|
||||
error={errors.resources}
|
||||
/>
|
||||
<PromptsTab
|
||||
prompts={prompts}
|
||||
listPrompts={listPrompts}
|
||||
getPrompt={getPrompt}
|
||||
listPrompts={() => {
|
||||
clearError("prompts");
|
||||
listPrompts();
|
||||
}}
|
||||
getPrompt={(name, args) => {
|
||||
clearError("prompts");
|
||||
getPrompt(name, args);
|
||||
}}
|
||||
selectedPrompt={selectedPrompt}
|
||||
setSelectedPrompt={setSelectedPrompt}
|
||||
setSelectedPrompt={(prompt) => {
|
||||
clearError("prompts");
|
||||
setSelectedPrompt(prompt);
|
||||
}}
|
||||
promptContent={promptContent}
|
||||
nextCursor={nextPromptCursor}
|
||||
error={error}
|
||||
error={errors.prompts}
|
||||
/>
|
||||
<RequestsTab />
|
||||
<ToolsTab
|
||||
tools={tools}
|
||||
listTools={listTools}
|
||||
callTool={callTool}
|
||||
listTools={() => {
|
||||
clearError("tools");
|
||||
listTools();
|
||||
}}
|
||||
callTool={(name, params) => {
|
||||
clearError("tools");
|
||||
callTool(name, params);
|
||||
}}
|
||||
selectedTool={selectedTool}
|
||||
setSelectedTool={(tool) => {
|
||||
clearError("tools");
|
||||
setSelectedTool(tool);
|
||||
setToolResult(null);
|
||||
}}
|
||||
toolResult={toolResult}
|
||||
nextCursor={nextToolCursor}
|
||||
error={error}
|
||||
error={errors.tools}
|
||||
/>
|
||||
<ConsoleTab />
|
||||
<PingTab
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { TabsContent } from "@/components/ui/tabs";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Send } from "lucide-react";
|
||||
|
||||
const RequestsTab = () => (
|
||||
<TabsContent value="requests" className="space-y-4">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-4">
|
||||
<div className="flex space-x-2">
|
||||
<Input placeholder="Method name" />
|
||||
<Button>
|
||||
<Send className="w-4 h-4 mr-2" />
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
<Textarea
|
||||
placeholder="Request parameters (JSON)"
|
||||
className="h-64 font-mono"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="bg-gray-50 p-4 rounded-lg h-96 font-mono text-sm overflow-auto">
|
||||
<div className="text-gray-500 mb-2">Response:</div>
|
||||
{/* Response content would go here */}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TabsContent>
|
||||
);
|
||||
|
||||
export default RequestsTab;
|
||||
@@ -1,10 +1,13 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import { ToastContainer } from 'react-toastify';
|
||||
import 'react-toastify/dist/ReactToastify.css';
|
||||
import App from "./App.tsx";
|
||||
import "./index.css";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
<ToastContainer />
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -39,6 +39,7 @@
|
||||
"lucide-react": "^0.447.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-toastify": "^10.0.6",
|
||||
"serve-handler": "^6.1.6",
|
||||
"tailwind-merge": "^2.5.3",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
@@ -4676,6 +4677,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-toastify": {
|
||||
"version": "10.0.6",
|
||||
"resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.6.tgz",
|
||||
"integrity": "sha512-yYjp+omCDf9lhZcrZHKbSq7YMuK0zcYkDFTzfRFgTXkTFHZ1ToxwAonzA4JI5CxA91JpjFLmwEsZEgfYfOqI1A==",
|
||||
"dependencies": {
|
||||
"clsx": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/read-cache": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||
|
||||
Reference in New Issue
Block a user