Skip to content

Commit 0b37722

Browse files
authored
Merge pull request modelcontextprotocol#272 from idosal/set-header
Enable Authentication header name configuration
2 parents 25cc0f6 + 6420605 commit 0b37722

File tree

5 files changed

+184
-2
lines changed

5 files changed

+184
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ For more details on ways to use the inspector, see the [Inspector section of the
4040

4141
### Authentication
4242

43-
The inspector supports bearer token authentication for SSE connections. Enter your token in the UI when connecting to an MCP server, and it will be sent in the Authorization header.
43+
The inspector supports bearer token authentication for SSE connections. Enter your token in the UI when connecting to an MCP server, and it will be sent in the Authorization header. You can override the header name using the input field in the sidebar.
4444

4545
### Security Considerations
4646

client/src/App.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ const App = () => {
117117
return localStorage.getItem("lastBearerToken") || "";
118118
});
119119

120+
const [headerName, setHeaderName] = useState<string>(() => {
121+
return localStorage.getItem("lastHeaderName") || "";
122+
});
123+
120124
const [pendingSampleRequests, setPendingSampleRequests] = useState<
121125
Array<
122126
PendingRequest & {
@@ -169,6 +173,7 @@ const App = () => {
169173
sseUrl,
170174
env,
171175
bearerToken,
176+
headerName,
172177
config,
173178
onNotification: (notification) => {
174179
setNotifications((prev) => [...prev, notification as ServerNotification]);
@@ -208,6 +213,10 @@ const App = () => {
208213
localStorage.setItem("lastBearerToken", bearerToken);
209214
}, [bearerToken]);
210215

216+
useEffect(() => {
217+
localStorage.setItem("lastHeaderName", headerName);
218+
}, [headerName]);
219+
211220
useEffect(() => {
212221
localStorage.setItem(CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config));
213222
}, [config]);
@@ -500,6 +509,8 @@ const App = () => {
500509
setConfig={setConfig}
501510
bearerToken={bearerToken}
502511
setBearerToken={setBearerToken}
512+
headerName={headerName}
513+
setHeaderName={setHeaderName}
503514
onConnect={connectMcpServer}
504515
onDisconnect={disconnectMcpServer}
505516
stdErrNotifications={stdErrNotifications}

client/src/components/Sidebar.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ interface SidebarProps {
5151
setEnv: (env: Record<string, string>) => void;
5252
bearerToken: string;
5353
setBearerToken: (token: string) => void;
54+
headerName?: string;
55+
setHeaderName?: (name: string) => void;
5456
onConnect: () => void;
5557
onDisconnect: () => void;
5658
stdErrNotifications: StdErrNotification[];
@@ -76,6 +78,8 @@ const Sidebar = ({
7678
setEnv,
7779
bearerToken,
7880
setBearerToken,
81+
headerName,
82+
setHeaderName,
7983
onConnect,
8084
onDisconnect,
8185
stdErrNotifications,
@@ -176,6 +180,7 @@ const Sidebar = ({
176180
variant="outline"
177181
onClick={() => setShowBearerToken(!showBearerToken)}
178182
className="flex items-center w-full"
183+
data-testid="auth-button"
179184
aria-expanded={showBearerToken}
180185
>
181186
{showBearerToken ? (
@@ -187,6 +192,16 @@ const Sidebar = ({
187192
</Button>
188193
{showBearerToken && (
189194
<div className="space-y-2">
195+
<label className="text-sm font-medium">Header Name</label>
196+
<Input
197+
placeholder="Authorization"
198+
onChange={(e) =>
199+
setHeaderName && setHeaderName(e.target.value)
200+
}
201+
data-testid="header-input"
202+
className="font-mono"
203+
value={headerName}
204+
/>
190205
<label
191206
className="text-sm font-medium"
192207
htmlFor="bearer-token-input"
@@ -198,6 +213,7 @@ const Sidebar = ({
198213
placeholder="Bearer Token"
199214
value={bearerToken}
200215
onChange={(e) => setBearerToken(e.target.value)}
216+
data-testid="bearer-token-input"
201217
className="font-mono"
202218
type="password"
203219
/>

client/src/components/__tests__/Sidebar.test.tsx

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { render, screen, fireEvent } from "@testing-library/react";
2+
import "@testing-library/jest-dom";
23
import { describe, it, beforeEach, jest } from "@jest/globals";
34
import Sidebar from "../Sidebar";
45
import { DEFAULT_INSPECTOR_CONFIG } from "@/lib/constants";
@@ -109,6 +110,157 @@ describe("Sidebar Environment Variables", () => {
109110
});
110111
});
111112

113+
describe("Authentication", () => {
114+
const openAuthSection = () => {
115+
const button = screen.getByTestId("auth-button");
116+
fireEvent.click(button);
117+
};
118+
119+
it("should update bearer token", () => {
120+
const setBearerToken = jest.fn();
121+
renderSidebar({
122+
bearerToken: "",
123+
setBearerToken,
124+
transportType: "sse", // Set transport type to SSE
125+
});
126+
127+
openAuthSection();
128+
129+
const tokenInput = screen.getByTestId("bearer-token-input");
130+
fireEvent.change(tokenInput, { target: { value: "new_token" } });
131+
132+
expect(setBearerToken).toHaveBeenCalledWith("new_token");
133+
});
134+
135+
it("should update header name", () => {
136+
const setHeaderName = jest.fn();
137+
renderSidebar({
138+
headerName: "Authorization",
139+
setHeaderName,
140+
transportType: "sse",
141+
});
142+
143+
openAuthSection();
144+
145+
const headerInput = screen.getByTestId("header-input");
146+
fireEvent.change(headerInput, { target: { value: "X-Custom-Auth" } });
147+
148+
expect(setHeaderName).toHaveBeenCalledWith("X-Custom-Auth");
149+
});
150+
151+
it("should clear bearer token", () => {
152+
const setBearerToken = jest.fn();
153+
renderSidebar({
154+
bearerToken: "existing_token",
155+
setBearerToken,
156+
transportType: "sse", // Set transport type to SSE
157+
});
158+
159+
openAuthSection();
160+
161+
const tokenInput = screen.getByTestId("bearer-token-input");
162+
fireEvent.change(tokenInput, { target: { value: "" } });
163+
164+
expect(setBearerToken).toHaveBeenCalledWith("");
165+
});
166+
167+
it("should properly render bearer token input", () => {
168+
const { rerender } = renderSidebar({
169+
bearerToken: "existing_token",
170+
transportType: "sse", // Set transport type to SSE
171+
});
172+
173+
openAuthSection();
174+
175+
// Token input should be a password field
176+
const tokenInput = screen.getByTestId("bearer-token-input");
177+
expect(tokenInput).toHaveProperty("type", "password");
178+
179+
// Update the token
180+
fireEvent.change(tokenInput, { target: { value: "new_token" } });
181+
182+
// Rerender with updated token
183+
rerender(
184+
<TooltipProvider>
185+
<Sidebar
186+
{...defaultProps}
187+
bearerToken="new_token"
188+
transportType="sse"
189+
/>
190+
</TooltipProvider>,
191+
);
192+
193+
// Token input should still exist after update
194+
expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
195+
});
196+
197+
it("should maintain token visibility state after update", () => {
198+
const { rerender } = renderSidebar({
199+
bearerToken: "existing_token",
200+
transportType: "sse", // Set transport type to SSE
201+
});
202+
203+
openAuthSection();
204+
205+
// Token input should be a password field
206+
const tokenInput = screen.getByTestId("bearer-token-input");
207+
expect(tokenInput).toHaveProperty("type", "password");
208+
209+
// Update the token
210+
fireEvent.change(tokenInput, { target: { value: "new_token" } });
211+
212+
// Rerender with updated token
213+
rerender(
214+
<TooltipProvider>
215+
<Sidebar
216+
{...defaultProps}
217+
bearerToken="new_token"
218+
transportType="sse"
219+
/>
220+
</TooltipProvider>,
221+
);
222+
223+
// Token input should still exist after update
224+
expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
225+
});
226+
227+
it("should maintain header name when toggling auth section", () => {
228+
renderSidebar({
229+
headerName: "X-API-Key",
230+
transportType: "sse",
231+
});
232+
233+
// Open auth section
234+
openAuthSection();
235+
236+
// Verify header name is displayed
237+
const headerInput = screen.getByTestId("header-input");
238+
expect(headerInput).toHaveValue("X-API-Key");
239+
240+
// Close auth section
241+
const authButton = screen.getByTestId("auth-button");
242+
fireEvent.click(authButton);
243+
244+
// Reopen auth section
245+
fireEvent.click(authButton);
246+
247+
// Verify header name is still preserved
248+
expect(screen.getByTestId("header-input")).toHaveValue("X-API-Key");
249+
});
250+
251+
it("should display default header name when not specified", () => {
252+
renderSidebar({
253+
headerName: undefined,
254+
transportType: "sse",
255+
});
256+
257+
openAuthSection();
258+
259+
const headerInput = screen.getByTestId("header-input");
260+
expect(headerInput).toHaveAttribute("placeholder", "Authorization");
261+
});
262+
});
263+
112264
describe("Key Editing", () => {
113265
it("should maintain order when editing first key", () => {
114266
const setEnv = jest.fn();

client/src/lib/hooks/useConnection.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ interface UseConnectionOptions {
4848
sseUrl: string;
4949
env: Record<string, string>;
5050
bearerToken?: string;
51+
headerName?: string;
5152
config: InspectorConfig;
5253
onNotification?: (notification: Notification) => void;
5354
onStdErrNotification?: (notification: Notification) => void;
@@ -64,6 +65,7 @@ export function useConnection({
6465
sseUrl,
6566
env,
6667
bearerToken,
68+
headerName,
6769
config,
6870
onNotification,
6971
onStdErrNotification,
@@ -293,7 +295,8 @@ export function useConnection({
293295
// Use manually provided bearer token if available, otherwise use OAuth tokens
294296
const token = bearerToken || (await authProvider.tokens())?.access_token;
295297
if (token) {
296-
headers["Authorization"] = `Bearer ${token}`;
298+
const authHeaderName = headerName || "Authorization";
299+
headers[authHeaderName] = `Bearer ${token}`;
297300
}
298301

299302
const clientTransport = new SSEClientTransport(mcpProxyServerUrl, {

0 commit comments

Comments
 (0)