Skip to content

Commit 58e796a

Browse files
committed
add command history
1 parent d2a7231 commit 58e796a

File tree

2 files changed

+213
-85
lines changed

2 files changed

+213
-85
lines changed

client/src/App.tsx

Lines changed: 119 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import ResourcesTab, { Resource } from "./components/ResourcesTab";
1919
import NotificationsTab from "./components/NotificationsTab";
2020
import PromptsTab, { Prompt } from "./components/PromptsTab";
2121
import ToolsTab, { Tool as ToolType } from "./components/ToolsTab";
22+
import CommandHistory from "./components/CommandHistory";
2223

2324
const App = () => {
2425
const [socket, setSocket] = useState<WebSocket | null>(null);
@@ -39,6 +40,9 @@ const App = () => {
3940
"/Users/ashwin/code/example-servers/build/everything/index.js",
4041
);
4142
const [mcpConnected, setMcpConnected] = useState<boolean>(false);
43+
const [commandHistory, setCommandHistory] = useState<
44+
Array<{ command: string; response: string | null }>
45+
>([]);
4246

4347
useEffect(() => {
4448
const ws = new WebSocket("ws://localhost:3000");
@@ -55,25 +59,33 @@ const App = () => {
5559
if (message.type === "resources") {
5660
setResources(message.data.resources);
5761
setError(null);
62+
updateCommandHistory(message);
5863
} else if (message.type === "resource") {
5964
setResourceContent(JSON.stringify(message.data, null, 2));
6065
setError(null);
66+
updateCommandHistory(message);
6167
} else if (message.type === "prompts") {
6268
setPrompts(message.data.prompts);
6369
setError(null);
70+
updateCommandHistory(message);
6471
} else if (message.type === "prompt") {
6572
setPromptContent(JSON.stringify(message.data, null, 2));
6673
setError(null);
74+
updateCommandHistory(message);
6775
} else if (message.type === "tools") {
6876
setTools(message.data.tools);
6977
setError(null);
78+
updateCommandHistory(message);
7079
} else if (message.type === "toolResult") {
7180
setToolResult(JSON.stringify(message.data, null, 2));
7281
setError(null);
82+
updateCommandHistory(message);
7383
} else if (message.type === "error") {
7484
setError(message.message);
85+
updateCommandHistory(message);
7586
} else if (message.type === "connected") {
7687
setMcpConnected(true);
88+
updateCommandHistory(message);
7789
}
7890
};
7991

@@ -89,10 +101,29 @@ const App = () => {
89101
return () => ws.close();
90102
}, []);
91103

104+
const updateCommandHistory = (response: unknown) => {
105+
setCommandHistory((prev) => {
106+
const lastCommand = prev[prev.length - 1];
107+
if (lastCommand && lastCommand.response === null) {
108+
const updatedHistory = [...prev];
109+
updatedHistory[updatedHistory.length - 1] = {
110+
...lastCommand,
111+
response: JSON.stringify(response),
112+
};
113+
return updatedHistory;
114+
}
115+
return prev;
116+
});
117+
};
118+
92119
const sendWebSocketMessage = (message: object) => {
93120
if (socket) {
94121
console.log("Sending WebSocket message:", message);
95122
socket.send(JSON.stringify(message));
123+
setCommandHistory((prev) => [
124+
...prev,
125+
{ command: JSON.stringify(message), response: null },
126+
]);
96127
}
97128
};
98129

@@ -136,97 +167,100 @@ const App = () => {
136167
<Sidebar connectionStatus={connectionStatus} />
137168
<div className="flex-1 flex flex-col overflow-hidden">
138169
<h1 className="text-2xl font-bold p-4">MCP Inspector</h1>
139-
<div className="flex-1 overflow-auto">
140-
<div className="p-4 bg-white shadow-md m-4 rounded-md">
141-
<h2 className="text-lg font-semibold mb-2">Connect MCP Server</h2>
142-
<div className="flex space-x-2 mb-2">
143-
<Input
144-
placeholder="Command"
145-
value={command}
146-
onChange={(e) => setCommand(e.target.value)}
147-
/>
148-
<Input
149-
placeholder="Arguments (space-separated)"
150-
value={args}
151-
onChange={(e) => setArgs(e.target.value)}
152-
/>
153-
<Button onClick={connectMcpServer}>
154-
<Play className="w-4 h-4 mr-2" />
155-
Connect
156-
</Button>
157-
</div>
158-
</div>
159-
{mcpConnected ? (
160-
<Tabs defaultValue="resources" className="w-full p-4">
161-
<TabsList className="mb-4 p-0">
162-
<TabsTrigger value="resources">
163-
<Files className="w-4 h-4 mr-2" />
164-
Resources
165-
</TabsTrigger>
166-
<TabsTrigger value="prompts">
167-
<MessageSquare className="w-4 h-4 mr-2" />
168-
Prompts
169-
</TabsTrigger>
170-
<TabsTrigger value="requests" disabled>
171-
<Send className="w-4 h-4 mr-2" />
172-
Requests
173-
</TabsTrigger>
174-
<TabsTrigger value="notifications" disabled>
175-
<Bell className="w-4 h-4 mr-2" />
176-
Notifications
177-
</TabsTrigger>
178-
<TabsTrigger value="tools" disabled>
179-
<Hammer className="w-4 h-4 mr-2" />
180-
Tools
181-
</TabsTrigger>
182-
<TabsTrigger value="console" disabled>
183-
<Terminal className="w-4 h-4 mr-2" />
184-
Console
185-
</TabsTrigger>
186-
</TabsList>
187-
188-
<div className="w-full">
189-
<ResourcesTab
190-
resources={resources}
191-
listResources={listResources}
192-
readResource={readResource}
193-
selectedResource={selectedResource}
194-
setSelectedResource={setSelectedResource}
195-
resourceContent={resourceContent}
196-
error={error}
170+
<div className="flex-1 overflow-auto flex">
171+
<div className="flex-1">
172+
<div className="p-4 bg-white shadow-md m-4 rounded-md">
173+
<h2 className="text-lg font-semibold mb-2">Connect MCP Server</h2>
174+
<div className="flex space-x-2 mb-2">
175+
<Input
176+
placeholder="Command"
177+
value={command}
178+
onChange={(e) => setCommand(e.target.value)}
197179
/>
198-
<NotificationsTab />
199-
<PromptsTab
200-
prompts={prompts}
201-
listPrompts={listPrompts}
202-
getPrompt={getPrompt}
203-
selectedPrompt={selectedPrompt}
204-
setSelectedPrompt={setSelectedPrompt}
205-
promptContent={promptContent}
206-
error={error}
180+
<Input
181+
placeholder="Arguments (space-separated)"
182+
value={args}
183+
onChange={(e) => setArgs(e.target.value)}
207184
/>
208-
<RequestsTab />
209-
<ToolsTab
210-
tools={tools}
211-
listTools={listTools}
212-
callTool={callTool}
213-
selectedTool={selectedTool}
214-
setSelectedTool={setSelectedTool}
215-
toolResult={toolResult}
216-
error={error}
217-
/>
218-
<ConsoleTab />
185+
<Button onClick={connectMcpServer}>
186+
<Play className="w-4 h-4 mr-2" />
187+
Connect
188+
</Button>
219189
</div>
220-
</Tabs>
221-
) : (
222-
<div className="flex items-center justify-center h-full">
223-
<p className="text-lg text-gray-500">
224-
Connect to an MCP server to start inspecting
225-
</p>
226190
</div>
227-
)}
191+
{mcpConnected ? (
192+
<Tabs defaultValue="resources" className="w-full p-4">
193+
<TabsList className="mb-4 p-0">
194+
<TabsTrigger value="resources">
195+
<Files className="w-4 h-4 mr-2" />
196+
Resources
197+
</TabsTrigger>
198+
<TabsTrigger value="prompts">
199+
<MessageSquare className="w-4 h-4 mr-2" />
200+
Prompts
201+
</TabsTrigger>
202+
<TabsTrigger value="requests" disabled>
203+
<Send className="w-4 h-4 mr-2" />
204+
Requests
205+
</TabsTrigger>
206+
<TabsTrigger value="notifications" disabled>
207+
<Bell className="w-4 h-4 mr-2" />
208+
Notifications
209+
</TabsTrigger>
210+
<TabsTrigger value="tools" disabled>
211+
<Hammer className="w-4 h-4 mr-2" />
212+
Tools
213+
</TabsTrigger>
214+
<TabsTrigger value="console" disabled>
215+
<Terminal className="w-4 h-4 mr-2" />
216+
Console
217+
</TabsTrigger>
218+
</TabsList>
219+
220+
<div className="w-full">
221+
<ResourcesTab
222+
resources={resources}
223+
listResources={listResources}
224+
readResource={readResource}
225+
selectedResource={selectedResource}
226+
setSelectedResource={setSelectedResource}
227+
resourceContent={resourceContent}
228+
error={error}
229+
/>
230+
<NotificationsTab />
231+
<PromptsTab
232+
prompts={prompts}
233+
listPrompts={listPrompts}
234+
getPrompt={getPrompt}
235+
selectedPrompt={selectedPrompt}
236+
setSelectedPrompt={setSelectedPrompt}
237+
promptContent={promptContent}
238+
error={error}
239+
/>
240+
<RequestsTab />
241+
<ToolsTab
242+
tools={tools}
243+
listTools={listTools}
244+
callTool={callTool}
245+
selectedTool={selectedTool}
246+
setSelectedTool={setSelectedTool}
247+
toolResult={toolResult}
248+
error={error}
249+
/>
250+
<ConsoleTab />
251+
</div>
252+
</Tabs>
253+
) : (
254+
<div className="flex items-center justify-center h-full">
255+
<p className="text-lg text-gray-500">
256+
Connect to an MCP server to start inspecting
257+
</p>
258+
</div>
259+
)}
260+
</div>
228261
</div>
229262
</div>
263+
<CommandHistory commandHistory={commandHistory} />
230264
</div>
231265
);
232266
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { useState } from "react";
2+
import { Copy } from "lucide-react";
3+
4+
const CommandHistory = ({
5+
commandHistory,
6+
}: {
7+
commandHistory: Array<{ command: string; response: string | null }>;
8+
}) => {
9+
const [expandedCommands, setExpandedCommands] = useState<{
10+
[key: number]: boolean;
11+
}>({});
12+
13+
const toggleCommandExpansion = (index: number) => {
14+
setExpandedCommands((prev) => ({ ...prev, [index]: !prev[index] }));
15+
};
16+
17+
const copyToClipboard = (text: string) => {
18+
navigator.clipboard.writeText(text);
19+
};
20+
21+
return (
22+
<div className="w-64 bg-white shadow-md p-4 overflow-y-auto">
23+
<h2 className="text-lg font-semibold mb-4">Command History</h2>
24+
<ul className="space-y-3">
25+
{commandHistory
26+
.slice()
27+
.reverse()
28+
.map((cmd, index) => (
29+
<li
30+
key={index}
31+
className="text-sm text-gray-600 bg-gray-100 p-2 rounded"
32+
>
33+
<div
34+
className="flex justify-between items-center cursor-pointer"
35+
onClick={() =>
36+
toggleCommandExpansion(commandHistory.length - 1 - index)
37+
}
38+
>
39+
<span className="font-mono">
40+
{commandHistory.length - index}.{" "}
41+
{JSON.parse(cmd.command).type}
42+
</span>
43+
<span>
44+
{expandedCommands[commandHistory.length - 1 - index]
45+
? "▼"
46+
: "▶"}
47+
</span>
48+
</div>
49+
{expandedCommands[commandHistory.length - 1 - index] && (
50+
<>
51+
<div className="mt-2">
52+
<div className="flex justify-between items-center mb-1">
53+
<span className="font-semibold text-blue-600">
54+
Command:
55+
</span>
56+
<button
57+
onClick={() => copyToClipboard(cmd.command)}
58+
className="text-blue-500 hover:text-blue-700"
59+
>
60+
<Copy size={16} />
61+
</button>
62+
</div>
63+
<pre className="whitespace-pre-wrap break-words bg-blue-50 p-2 rounded">
64+
{JSON.stringify(JSON.parse(cmd.command), null, 2)}
65+
</pre>
66+
</div>
67+
{cmd.response && (
68+
<div className="mt-2">
69+
<div className="flex justify-between items-center mb-1">
70+
<span className="font-semibold text-green-600">
71+
Response:
72+
</span>
73+
<button
74+
onClick={() => copyToClipboard(cmd.response!)}
75+
className="text-blue-500 hover:text-blue-700"
76+
>
77+
<Copy size={16} />
78+
</button>
79+
</div>
80+
<pre className="whitespace-pre-wrap break-words bg-green-50 p-2 rounded">
81+
{JSON.stringify(JSON.parse(cmd.response), null, 2)}
82+
</pre>
83+
</div>
84+
)}
85+
</>
86+
)}
87+
</li>
88+
))}
89+
</ul>
90+
</div>
91+
);
92+
};
93+
94+
export default CommandHistory;

0 commit comments

Comments
 (0)