Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions client/bin/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,15 @@ async function startDevServer(serverOptions) {
abort,
transport,
serverUrl,
command,
mcpServerArgs,
} = serverOptions;
const serverCommand = "npx";
const serverArgs = ["tsx", "watch", "--clear-screen=false", "src/index.ts"];
// Forward the MCP server command and arguments to the proxy server in dev mode
if (command) serverArgs.push(`--command=${command}`);
if (mcpServerArgs.length > 0)
serverArgs.push(`--args=${mcpServerArgs.join(" ")}`);
const isWindows = process.platform === "win32";

const spawnOptions = {
Expand Down Expand Up @@ -268,9 +274,9 @@ async function main() {
} else {
envVars[envVar] = "";
}
} else if (!command && !isDev) {
} else if (!command) {
command = arg;
} else if (!isDev) {
} else {
mcpServerArgs.push(arg);
}
}
Expand Down
106 changes: 54 additions & 52 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import SamplingTab, { PendingRequest } from "./components/SamplingTab";
import Sidebar from "./components/Sidebar";
import ToolsTab from "./components/ToolsTab";
import TasksTab from "./components/TasksTab";
import { Resizer } from "./components/Resizer";
import { InspectorConfig } from "./lib/configurationTypes";
import {
getMCPProxyAddress,
Expand Down Expand Up @@ -326,11 +327,17 @@ const App = () => {
}, 100);
};

const { height: historyPaneHeight, handleDragStart } = useDraggablePane(300);
const {
height: historyPaneHeight,
isDragging: isHistoryDragging,
handleDragStart,
toggleCollapse: toggleHistory,
} = useDraggablePane(300);
const {
width: sidebarWidth,
isDragging: isSidebarDragging,
handleDragStart: handleSidebarDragStart,
toggleCollapse: toggleSidebar,
} = useDraggableSidebar(320);

const selectedTaskRef = useRef<Task | null>(null);
Expand Down Expand Up @@ -1215,57 +1222,50 @@ const App = () => {
<div
style={{
width: sidebarWidth,
minWidth: 200,
minWidth: 0,
maxWidth: 600,
transition: isSidebarDragging ? "none" : "width 0.15s",
}}
className="bg-card border-r border-border flex flex-col h-full relative"
className="relative h-full flex-shrink-0"
>
<Sidebar
connectionStatus={connectionStatus}
transportType={transportType}
setTransportType={setTransportType}
command={command}
setCommand={setCommand}
args={args}
setArgs={setArgs}
sseUrl={sseUrl}
setSseUrl={setSseUrl}
env={env}
setEnv={setEnv}
config={config}
setConfig={setConfig}
customHeaders={customHeaders}
setCustomHeaders={setCustomHeaders}
oauthClientId={oauthClientId}
setOauthClientId={setOauthClientId}
oauthClientSecret={oauthClientSecret}
setOauthClientSecret={setOauthClientSecret}
oauthScope={oauthScope}
setOauthScope={setOauthScope}
onConnect={connectMcpServer}
onDisconnect={disconnectMcpServer}
logLevel={logLevel}
sendLogLevelRequest={sendLogLevelRequest}
loggingSupported={!!serverCapabilities?.logging || false}
connectionType={connectionType}
setConnectionType={setConnectionType}
serverImplementation={serverImplementation}
/>
<div
<div className="bg-card border-r border-border flex flex-col h-full overflow-hidden">
<Sidebar
connectionStatus={connectionStatus}
transportType={transportType}
setTransportType={setTransportType}
command={command}
setCommand={setCommand}
args={args}
setArgs={setArgs}
sseUrl={sseUrl}
setSseUrl={setSseUrl}
env={env}
setEnv={setEnv}
config={config}
setConfig={setConfig}
customHeaders={customHeaders}
setCustomHeaders={setCustomHeaders}
oauthClientId={oauthClientId}
setOauthClientId={setOauthClientId}
oauthClientSecret={oauthClientSecret}
setOauthClientSecret={setOauthClientSecret}
oauthScope={oauthScope}
setOauthScope={setOauthScope}
onConnect={connectMcpServer}
onDisconnect={disconnectMcpServer}
logLevel={logLevel}
sendLogLevelRequest={sendLogLevelRequest}
loggingSupported={!!serverCapabilities?.logging || false}
connectionType={connectionType}
setConnectionType={setConnectionType}
serverImplementation={serverImplementation}
/>
</div>
<Resizer
axis="x"
onMouseDown={handleSidebarDragStart}
style={{
cursor: "col-resize",
position: "absolute",
top: 0,
right: 0,
width: 6,
height: "100%",
zIndex: 10,
background: isSidebarDragging ? "rgba(0,0,0,0.08)" : "transparent",
}}
aria-label="Resize sidebar"
data-testid="sidebar-drag-handle"
onDoubleClick={toggleSidebar}
className="absolute top-0 right-[-8px]"
/>
</div>
<div className="flex-1 flex flex-col overflow-hidden">
Expand Down Expand Up @@ -1477,6 +1477,7 @@ const App = () => {
clearError("resources");
readResource(uri);
}}
config={config}
/>
<TasksTab
tasks={tasks}
Expand Down Expand Up @@ -1563,14 +1564,15 @@ const App = () => {
className="relative border-t border-border"
style={{
height: `${historyPaneHeight}px`,
transition: isHistoryDragging ? "none" : "height 0.15s",
}}
>
<div
className="absolute w-full h-4 -top-2 cursor-row-resize flex items-center justify-center hover:bg-accent/50 dark:hover:bg-input/40"
<Resizer
axis="y"
onMouseDown={handleDragStart}
>
<div className="w-8 h-1 rounded-full bg-border" />
</div>
onDoubleClick={toggleHistory}
className="absolute top-[-8px] left-0"
/>
<div className="h-full overflow-auto">
<HistoryAndNotifications
requestHistory={requestHistory}
Expand Down
6 changes: 0 additions & 6 deletions client/src/components/DynamicJsonForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ interface DynamicJsonFormProps {

export interface DynamicJsonFormRef {
validateJson: () => { isValid: boolean; error: string | null };
hasJsonError: () => boolean;
}

const isTypeSupported = (
Expand Down Expand Up @@ -130,10 +129,6 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(
// on every keystroke which would be inefficient and error-prone
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();

const hasJsonError = () => {
return !!jsonError;
};

// Debounce JSON parsing and parent updates to handle typing gracefully
const debouncedUpdateParent = useCallback(
(jsonString: string) => {
Expand Down Expand Up @@ -258,7 +253,6 @@ const DynamicJsonForm = forwardRef<DynamicJsonFormRef, DynamicJsonFormProps>(

useImperativeHandle(ref, () => ({
validateJson,
hasJsonError,
}));

const renderFormFields = (
Expand Down
159 changes: 95 additions & 64 deletions client/src/components/HistoryAndNotifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { ServerNotification } from "@modelcontextprotocol/sdk/types.js";
import { useState } from "react";
import JsonView from "./JsonView";
import { Button } from "@/components/ui/button";
import { useResizable } from "../lib/hooks/useDraggablePane";
import { Resizer } from "./Resizer";

const HistoryAndNotifications = ({
requestHistory,
Expand All @@ -21,6 +23,19 @@ const HistoryAndNotifications = ({
[key: number]: boolean;
}>({});

const {
size: notificationsWidth,
isDragging,
handleDragStart,
toggleCollapse,
} = useResizable({
initialSize: window.innerWidth / 2,
axis: "x",
reverse: true,
minSize: 0,
maxSize: window.innerWidth - 400,
});

const toggleRequestExpansion = (index: number) => {
setExpandedRequests((prev) => ({ ...prev, [index]: !prev[index] }));
};
Expand All @@ -31,7 +46,7 @@ const HistoryAndNotifications = ({

return (
<div className="bg-card overflow-hidden flex h-full">
<div className="flex-1 overflow-y-auto p-4 border-r">
<div className="flex-1 overflow-y-auto p-4 border-r border-border min-w-0">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">History</h2>
<Button
Expand Down Expand Up @@ -107,71 +122,87 @@ const HistoryAndNotifications = ({
</ul>
)}
</div>
<div className="flex-1 overflow-y-auto p-4">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">Server Notifications</h2>
<Button
variant="outline"
size="sm"
onClick={onClearNotifications}
disabled={serverNotifications.length === 0}
>
Clear
</Button>
</div>
{serverNotifications.length === 0 ? (
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
No notifications yet
</p>
) : (
<ul className="space-y-3">
{serverNotifications
.slice()
.reverse()
.map((notification, index) => (
<li
key={index}
className="text-sm text-foreground bg-secondary py-2 px-3 rounded"
>
<div
className="flex justify-between items-center cursor-pointer"
onClick={() =>
toggleNotificationExpansion(
serverNotifications.length - 1 - index,
)
}

<div
className="relative h-full flex-shrink-0 border-l border-border"
style={{
width: notificationsWidth,
transition: isDragging ? "none" : "width 0.15s",
}}
>
<Resizer
axis="x"
onMouseDown={handleDragStart}
onDoubleClick={toggleCollapse}
className="absolute top-0 left-[-8px]"
/>

<div className="flex-1 overflow-y-auto p-4 h-full">
<div className="flex items-center justify-between mb-4">
<h2 className="text-lg font-semibold">Server Notifications</h2>
<Button
variant="outline"
size="sm"
onClick={onClearNotifications}
disabled={serverNotifications.length === 0}
>
Clear
</Button>
</div>
{serverNotifications.length === 0 ? (
<p className="text-sm text-gray-500 dark:text-gray-400 italic">
No notifications yet
</p>
) : (
<ul className="space-y-3">
{serverNotifications
.slice()
.reverse()
.map((notification, index) => (
<li
key={index}
className="text-sm text-foreground bg-secondary py-2 px-3 rounded"
>
<span className="font-mono">
{serverNotifications.length - index}.{" "}
{notification.method}
</span>
<span>
{expandedNotifications[
serverNotifications.length - 1 - index
]
? "▼"
: "▶"}
</span>
</div>
{expandedNotifications[
serverNotifications.length - 1 - index
] && (
<div className="mt-2">
<div className="flex justify-between items-center mb-1">
<span className="font-semibold text-purple-600">
Details:
</span>
</div>
<JsonView
data={JSON.stringify(notification, null, 2)}
className="bg-background"
/>
<div
className="flex justify-between items-center cursor-pointer"
onClick={() =>
toggleNotificationExpansion(
serverNotifications.length - 1 - index,
)
}
>
<span className="font-mono">
{serverNotifications.length - index}.{" "}
{notification.method}
</span>
<span>
{expandedNotifications[
serverNotifications.length - 1 - index
]
? "▼"
: "▶"}
</span>
</div>
)}
</li>
))}
</ul>
)}
{expandedNotifications[
serverNotifications.length - 1 - index
] && (
<div className="mt-2">
<div className="flex justify-between items-center mb-1">
<span className="font-semibold text-purple-600">
Details:
</span>
</div>
<JsonView
data={JSON.stringify(notification, null, 2)}
className="bg-background"
/>
</div>
)}
</li>
))}
</ul>
)}
</div>
</div>
</div>
);
Expand Down
Loading