Skip to content

Commit 06267d2

Browse files
Merge pull request modelcontextprotocol#52 from modelcontextprotocol/justin/tab-specific-errors
Separate error states per tab, clear errors when clicking around
2 parents 41f8ec0 + c1c8fc2 commit 06267d2

File tree

5 files changed

+91
-54
lines changed

5 files changed

+91
-54
lines changed

client/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"lucide-react": "^0.447.0",
3434
"react": "^18.3.1",
3535
"react-dom": "^18.3.1",
36+
"react-toastify": "^10.0.6",
3637
"serve-handler": "^6.1.6",
3738
"tailwind-merge": "^2.5.3",
3839
"tailwindcss-animate": "^1.0.7",

client/src/App.tsx

Lines changed: 74 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
22
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
33
import {
4-
CompatibilityCallToolResultSchema,
4+
ClientNotification,
55
ClientRequest,
6+
CompatibilityCallToolResult,
7+
CompatibilityCallToolResultSchema,
68
CreateMessageRequestSchema,
79
CreateMessageResult,
810
EmptyResultSchema,
@@ -19,8 +21,6 @@ import {
1921
Root,
2022
ServerNotification,
2123
Tool,
22-
CompatibilityCallToolResult,
23-
ClientNotification,
2424
} from "@modelcontextprotocol/sdk/types.js";
2525
import { useCallback, useEffect, useRef, useState } from "react";
2626
// Add dark mode class based on system preference
@@ -32,21 +32,21 @@ import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
3232
import {
3333
Bell,
3434
Files,
35+
FolderTree,
3536
Hammer,
3637
Hash,
3738
MessageSquare,
3839
Send,
3940
Terminal,
40-
FolderTree,
4141
} from "lucide-react";
4242

43+
import { toast } from "react-toastify";
4344
import { ZodType } from "zod";
4445
import "./App.css";
4546
import ConsoleTab from "./components/ConsoleTab";
4647
import HistoryAndNotifications from "./components/History";
4748
import PingTab from "./components/PingTab";
4849
import PromptsTab, { Prompt } from "./components/PromptsTab";
49-
import RequestsTab from "./components/RequestsTabs";
5050
import ResourcesTab from "./components/ResourcesTab";
5151
import RootsTab from "./components/RootsTab";
5252
import SamplingTab, { PendingRequest } from "./components/SamplingTab";
@@ -67,7 +67,11 @@ const App = () => {
6767
const [tools, setTools] = useState<Tool[]>([]);
6868
const [toolResult, setToolResult] =
6969
useState<CompatibilityCallToolResult | null>(null);
70-
const [error, setError] = useState<string | null>(null);
70+
const [errors, setErrors] = useState<Record<string, string | null>>({
71+
resources: null,
72+
prompts: null,
73+
tools: null,
74+
});
7175
const [command, setCommand] = useState<string>(() => {
7276
return localStorage.getItem("lastCommand") || "mcp-server-everything";
7377
});
@@ -202,9 +206,14 @@ const App = () => {
202206
]);
203207
};
204208

209+
const clearError = (tabKey: keyof typeof errors) => {
210+
setErrors((prev) => ({ ...prev, [tabKey]: null }));
211+
};
212+
205213
const makeRequest = async <T extends ZodType<object>>(
206214
request: ClientRequest,
207215
schema: T,
216+
tabKey?: keyof typeof errors,
208217
) => {
209218
if (!mcpClient) {
210219
throw new Error("MCP client not connected");
@@ -213,9 +222,19 @@ const App = () => {
213222
try {
214223
const response = await mcpClient.request(request, schema);
215224
pushHistory(request, response);
225+
226+
if (tabKey !== undefined) {
227+
clearError(tabKey);
228+
}
229+
216230
return response;
217231
} catch (e: unknown) {
218-
setError((e as Error).message);
232+
if (tabKey === undefined) {
233+
toast.error((e as Error).message);
234+
} else {
235+
setErrors((prev) => ({ ...prev, [tabKey]: (e as Error).message }));
236+
}
237+
219238
throw e;
220239
}
221240
};
@@ -229,7 +248,7 @@ const App = () => {
229248
await mcpClient.notification(notification);
230249
pushHistory(notification);
231250
} catch (e: unknown) {
232-
setError((e as Error).message);
251+
toast.error((e as Error).message);
233252
throw e;
234253
}
235254
};
@@ -241,6 +260,7 @@ const App = () => {
241260
params: nextResourceCursor ? { cursor: nextResourceCursor } : {},
242261
},
243262
ListResourcesResultSchema,
263+
"resources",
244264
);
245265
setResources(resources.concat(response.resources ?? []));
246266
setNextResourceCursor(response.nextCursor);
@@ -255,6 +275,7 @@ const App = () => {
255275
: {},
256276
},
257277
ListResourceTemplatesResultSchema,
278+
"resources",
258279
);
259280
setResourceTemplates(
260281
resourceTemplates.concat(response.resourceTemplates ?? []),
@@ -269,6 +290,7 @@ const App = () => {
269290
params: { uri },
270291
},
271292
ReadResourceResultSchema,
293+
"resources",
272294
);
273295
setResourceContent(JSON.stringify(response, null, 2));
274296
};
@@ -280,6 +302,7 @@ const App = () => {
280302
params: nextPromptCursor ? { cursor: nextPromptCursor } : {},
281303
},
282304
ListPromptsResultSchema,
305+
"prompts",
283306
);
284307
setPrompts(response.prompts);
285308
setNextPromptCursor(response.nextCursor);
@@ -292,6 +315,7 @@ const App = () => {
292315
params: { name, arguments: args },
293316
},
294317
GetPromptResultSchema,
318+
"prompts",
295319
);
296320
setPromptContent(JSON.stringify(response, null, 2));
297321
};
@@ -303,6 +327,7 @@ const App = () => {
303327
params: nextToolCursor ? { cursor: nextToolCursor } : {},
304328
},
305329
ListToolsResultSchema,
330+
"tools",
306331
);
307332
setTools(response.tools);
308333
setNextToolCursor(response.nextCursor);
@@ -321,6 +346,7 @@ const App = () => {
321346
},
322347
},
323348
CompatibilityCallToolResultSchema,
349+
"tools",
324350
);
325351
setToolResult(response);
326352
};
@@ -445,39 +471,66 @@ const App = () => {
445471
<ResourcesTab
446472
resources={resources}
447473
resourceTemplates={resourceTemplates}
448-
listResources={listResources}
449-
listResourceTemplates={listResourceTemplates}
450-
readResource={readResource}
474+
listResources={() => {
475+
clearError("resources");
476+
listResources();
477+
}}
478+
listResourceTemplates={() => {
479+
clearError("resources");
480+
listResourceTemplates();
481+
}}
482+
readResource={(uri) => {
483+
clearError("resources");
484+
readResource(uri);
485+
}}
451486
selectedResource={selectedResource}
452-
setSelectedResource={setSelectedResource}
487+
setSelectedResource={(resource) => {
488+
clearError("resources");
489+
setSelectedResource(resource);
490+
}}
453491
resourceContent={resourceContent}
454492
nextCursor={nextResourceCursor}
455493
nextTemplateCursor={nextResourceTemplateCursor}
456-
error={error}
494+
error={errors.resources}
457495
/>
458496
<PromptsTab
459497
prompts={prompts}
460-
listPrompts={listPrompts}
461-
getPrompt={getPrompt}
498+
listPrompts={() => {
499+
clearError("prompts");
500+
listPrompts();
501+
}}
502+
getPrompt={(name, args) => {
503+
clearError("prompts");
504+
getPrompt(name, args);
505+
}}
462506
selectedPrompt={selectedPrompt}
463-
setSelectedPrompt={setSelectedPrompt}
507+
setSelectedPrompt={(prompt) => {
508+
clearError("prompts");
509+
setSelectedPrompt(prompt);
510+
}}
464511
promptContent={promptContent}
465512
nextCursor={nextPromptCursor}
466-
error={error}
513+
error={errors.prompts}
467514
/>
468-
<RequestsTab />
469515
<ToolsTab
470516
tools={tools}
471-
listTools={listTools}
472-
callTool={callTool}
517+
listTools={() => {
518+
clearError("tools");
519+
listTools();
520+
}}
521+
callTool={(name, params) => {
522+
clearError("tools");
523+
callTool(name, params);
524+
}}
473525
selectedTool={selectedTool}
474526
setSelectedTool={(tool) => {
527+
clearError("tools");
475528
setSelectedTool(tool);
476529
setToolResult(null);
477530
}}
478531
toolResult={toolResult}
479532
nextCursor={nextToolCursor}
480-
error={error}
533+
error={errors.tools}
481534
/>
482535
<ConsoleTab />
483536
<PingTab

client/src/components/RequestsTabs.tsx

Lines changed: 0 additions & 33 deletions
This file was deleted.

client/src/main.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { StrictMode } from "react";
22
import { createRoot } from "react-dom/client";
3+
import { ToastContainer } from 'react-toastify';
4+
import 'react-toastify/dist/ReactToastify.css';
35
import App from "./App.tsx";
46
import "./index.css";
57

68
createRoot(document.getElementById("root")!).render(
79
<StrictMode>
810
<App />
11+
<ToastContainer />
912
</StrictMode>,
1013
);

package-lock.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)