add: support custom headers
This commit is contained in:
@@ -40,7 +40,8 @@ import {
|
||||
} from "@/components/ui/tooltip";
|
||||
import { useToast } from "../lib/hooks/useToast";
|
||||
|
||||
interface SidebarProps {
|
||||
export interface SidebarProps {
|
||||
|
||||
connectionStatus: ConnectionStatus;
|
||||
transportType: "stdio" | "sse" | "streamable-http";
|
||||
setTransportType: (type: "stdio" | "sse" | "streamable-http") => void;
|
||||
@@ -56,6 +57,8 @@ interface SidebarProps {
|
||||
setBearerToken: (token: string) => void;
|
||||
headerName?: string;
|
||||
setHeaderName?: (name: string) => void;
|
||||
customHeaders: [string, string][];
|
||||
setCustomHeaders: (headers: [string, string][]) => void;
|
||||
onConnect: () => void;
|
||||
onDisconnect: () => void;
|
||||
stdErrNotifications: StdErrNotification[];
|
||||
@@ -83,6 +86,8 @@ const Sidebar = ({
|
||||
setBearerToken,
|
||||
headerName,
|
||||
setHeaderName,
|
||||
customHeaders,
|
||||
setCustomHeaders,
|
||||
onConnect,
|
||||
onDisconnect,
|
||||
stdErrNotifications,
|
||||
@@ -101,6 +106,7 @@ const Sidebar = ({
|
||||
const [copiedServerEntry, setCopiedServerEntry] = useState(false);
|
||||
const [copiedServerFile, setCopiedServerFile] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const [showCustomHeaders, setShowCustomHeaders] = useState(false);
|
||||
|
||||
// Reusable error reporter for copy actions
|
||||
const reportError = useCallback(
|
||||
@@ -213,6 +219,22 @@ const Sidebar = ({
|
||||
}
|
||||
}, [generateMCPServerFile, toast, reportError]);
|
||||
|
||||
const removeCustomHeader = (index: number) => {
|
||||
const newHeaders = [...customHeaders];
|
||||
newHeaders.splice(index, 1);
|
||||
setCustomHeaders(newHeaders);
|
||||
};
|
||||
|
||||
const updateCustomHeader = (index: number, field: 'key' | 'value', value: string) => {
|
||||
const newArr = [...customHeaders];
|
||||
const [oldKey, oldValue] = newArr[index];
|
||||
const newTuple: [string, string] = field === 'key'
|
||||
? [value, oldValue]
|
||||
: [oldKey, value];
|
||||
newArr[index] = newTuple;
|
||||
setCustomHeaders(newArr);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-80 bg-card border-r border-border flex flex-col h-full">
|
||||
<div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-800">
|
||||
@@ -720,6 +742,62 @@ const Sidebar = ({
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => setShowCustomHeaders(!showCustomHeaders)}
|
||||
className="flex items-center w-full"
|
||||
data-testid="custom-headers-button"
|
||||
aria-expanded={showCustomHeaders}
|
||||
>
|
||||
{showCustomHeaders ? (
|
||||
<ChevronDown className="w-4 h-4 mr-2" />
|
||||
) : (
|
||||
<ChevronRight className="w-4 h-4 mr-2" />
|
||||
)}
|
||||
Custom Headers
|
||||
</Button>
|
||||
{showCustomHeaders && (
|
||||
<div className="space-y-2">
|
||||
{customHeaders.map((header, index) => (
|
||||
<div key={index} className="space-y-2">
|
||||
<label className="text-sm font-medium">Header Name</label>
|
||||
<Input
|
||||
placeholder="Header Name"
|
||||
value={header[0]}
|
||||
onChange={(e) => updateCustomHeader(index, 'key', e.target.value)}
|
||||
className="font-mono"
|
||||
/>
|
||||
<label className="text-sm font-medium">Header Value</label>
|
||||
<Input
|
||||
placeholder="Header Value"
|
||||
value={header[1]}
|
||||
onChange={(e) => updateCustomHeader(index, 'value', e.target.value)}
|
||||
className="font-mono"
|
||||
/>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => removeCustomHeader(index)}
|
||||
className="w-full"
|
||||
>
|
||||
Remove Header
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
<Button
|
||||
variant="outline"
|
||||
className="w-full mt-2"
|
||||
onClick={() => {
|
||||
setCustomHeaders([ ...customHeaders, ["", ""]]);
|
||||
}}
|
||||
>
|
||||
Add Custom Header
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 border-t">
|
||||
|
||||
@@ -31,32 +31,36 @@ Object.defineProperty(navigator, "clipboard", {
|
||||
// Setup fake timers
|
||||
jest.useFakeTimers();
|
||||
|
||||
describe("Sidebar Environment Variables", () => {
|
||||
const defaultProps = {
|
||||
connectionStatus: "disconnected" as const,
|
||||
transportType: "stdio" as const,
|
||||
setTransportType: jest.fn(),
|
||||
command: "",
|
||||
setCommand: jest.fn(),
|
||||
args: "",
|
||||
setArgs: jest.fn(),
|
||||
sseUrl: "",
|
||||
setSseUrl: jest.fn(),
|
||||
env: {},
|
||||
setEnv: jest.fn(),
|
||||
bearerToken: "",
|
||||
setBearerToken: jest.fn(),
|
||||
onConnect: jest.fn(),
|
||||
onDisconnect: jest.fn(),
|
||||
stdErrNotifications: [],
|
||||
clearStdErrNotifications: jest.fn(),
|
||||
logLevel: "info" as const,
|
||||
sendLogLevelRequest: jest.fn(),
|
||||
loggingSupported: true,
|
||||
config: DEFAULT_INSPECTOR_CONFIG,
|
||||
setConfig: jest.fn(),
|
||||
};
|
||||
const defaultProps = {
|
||||
connectionStatus: "disconnected" as const,
|
||||
transportType: "stdio" as const,
|
||||
setTransportType: jest.fn(),
|
||||
command: "",
|
||||
setCommand: jest.fn(),
|
||||
args: "",
|
||||
setArgs: jest.fn(),
|
||||
sseUrl: "",
|
||||
setSseUrl: jest.fn(),
|
||||
env: {},
|
||||
setEnv: jest.fn(),
|
||||
bearerToken: "",
|
||||
setBearerToken: jest.fn(),
|
||||
headerName: "",
|
||||
setHeaderName: jest.fn(),
|
||||
customHeaders: [],
|
||||
setCustomHeaders: jest.fn(),
|
||||
onConnect: jest.fn(),
|
||||
onDisconnect: jest.fn(),
|
||||
stdErrNotifications: [],
|
||||
clearStdErrNotifications: jest.fn(),
|
||||
logLevel: "debug" as const,
|
||||
sendLogLevelRequest: jest.fn(),
|
||||
loggingSupported: false,
|
||||
config: DEFAULT_INSPECTOR_CONFIG,
|
||||
setConfig: jest.fn(),
|
||||
};
|
||||
|
||||
describe("Sidebar Environment Variables", () => {
|
||||
const renderSidebar = (props = {}) => {
|
||||
return render(
|
||||
<TooltipProvider>
|
||||
@@ -870,3 +874,79 @@ describe("Sidebar Environment Variables", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("Sidebar", () => {
|
||||
it("renders", () => {
|
||||
render(<Sidebar {...defaultProps} />);
|
||||
expect(screen.getByText("MCP Inspector")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows connect button when disconnected", () => {
|
||||
render(<Sidebar {...defaultProps} />);
|
||||
expect(screen.getByText("Connect")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows disconnect button when connected", () => {
|
||||
render(
|
||||
<Sidebar
|
||||
{...defaultProps}
|
||||
connectionStatus="connected"
|
||||
customHeaders={[]}
|
||||
setCustomHeaders={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Disconnect")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows reconnect button when connected", () => {
|
||||
render(
|
||||
<Sidebar
|
||||
{...defaultProps}
|
||||
connectionStatus="connected"
|
||||
transportType="sse"
|
||||
customHeaders={[]}
|
||||
setCustomHeaders={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Reconnect")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows restart button when connected with stdio transport", () => {
|
||||
render(
|
||||
<Sidebar
|
||||
{...defaultProps}
|
||||
connectionStatus="connected"
|
||||
transportType="stdio"
|
||||
customHeaders={[]}
|
||||
setCustomHeaders={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByText("Restart")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows environment variables section when stdio transport is selected", () => {
|
||||
render(
|
||||
<Sidebar
|
||||
{...defaultProps}
|
||||
env={{ NEW_KEY: "new_value" }}
|
||||
customHeaders={[]}
|
||||
setCustomHeaders={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const envButton = screen.getByText("Environment Variables");
|
||||
expect(envButton).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows configuration section", () => {
|
||||
render(
|
||||
<Sidebar
|
||||
{...defaultProps}
|
||||
config={DEFAULT_INSPECTOR_CONFIG}
|
||||
customHeaders={[]}
|
||||
setCustomHeaders={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
const configButton = screen.getByText("Configuration");
|
||||
expect(configButton).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user