Repository: modelcontextprotocol/inspector
Pull request #272: Enable Authentication header name configuration
Author: login: idosal
Labels: name: enhancement
description: New feature request
name: waiting on submitter
description: Waiting for the submitter to provide more info
Created: 2025-04-05T09:53:05Z | Merged: 2025-04-14T22:52:34Z

This change adds a Header Name field to the sidebar. It overrides the "Authorization" default name.

<img width="309" alt="image" src="https://github.com/user-attachments/assets/a8d112a4-3031-4e8e-954c-3a1f9dcac06a" />

## Motivation and Context
Some servers use a custom header name (e.g., Vercel Preview). The current feature doesn't support these servers.

## How Has This Been Tested?
I've added unit tests and tested it manually

## Breaking Changes
No breaking changes.

## Types of changes
<!-- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [x] Documentation update

## Checklist
<!-- Go over all the following points, and put an `x` in all the boxes that apply. -->
- [x] I have read the [MCP Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context
Please let me know if it requires any changes, and thanks for the great project!



Diff:
diff --git a/README.md b/README.md
index 0db92bfe8..2552a0118 100644
--- a/README.md
+++ b/README.md
@@ -40,7 +40,7 @@ For more details on ways to use the inspector, see the [Inspector section of the
 
 ### Authentication
 
-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.
+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.
 
 ### Security Considerations
 
diff --git a/client/src/App.tsx b/client/src/App.tsx
index 04be2040d..5964bd849 100644
--- a/client/src/App.tsx
+++ b/client/src/App.tsx
@@ -117,6 +117,10 @@ const App = () => {
     return localStorage.getItem("lastBearerToken") || "";
   });
 
+  const [headerName, setHeaderName] = useState<string>(() => {
+    return localStorage.getItem("lastHeaderName") || "";
+  });
+
   const [pendingSampleRequests, setPendingSampleRequests] = useState<
     Array<
       PendingRequest & {
@@ -169,6 +173,7 @@ const App = () => {
     sseUrl,
     env,
     bearerToken,
+    headerName,
     config,
     onNotification: (notification) => {
       setNotifications((prev) => [...prev, notification as ServerNotification]);
@@ -208,6 +213,10 @@ const App = () => {
     localStorage.setItem("lastBearerToken", bearerToken);
   }, [bearerToken]);
 
+  useEffect(() => {
+    localStorage.setItem("lastHeaderName", headerName);
+  }, [headerName]);
+
   useEffect(() => {
     localStorage.setItem(CONFIG_LOCAL_STORAGE_KEY, JSON.stringify(config));
   }, [config]);
@@ -500,6 +509,8 @@ const App = () => {
         setConfig={setConfig}
         bearerToken={bearerToken}
         setBearerToken={setBearerToken}
+        headerName={headerName}
+        setHeaderName={setHeaderName}
         onConnect={connectMcpServer}
         onDisconnect={disconnectMcpServer}
         stdErrNotifications={stdErrNotifications}
diff --git a/client/src/components/Sidebar.tsx b/client/src/components/Sidebar.tsx
index efc054a2c..c736558c5 100644
--- a/client/src/components/Sidebar.tsx
+++ b/client/src/components/Sidebar.tsx
@@ -51,6 +51,8 @@ interface SidebarProps {
   setEnv: (env: Record<string, string>) => void;
   bearerToken: string;
   setBearerToken: (token: string) => void;
+  headerName?: string;
+  setHeaderName?: (name: string) => void;
   onConnect: () => void;
   onDisconnect: () => void;
   stdErrNotifications: StdErrNotification[];
@@ -76,6 +78,8 @@ const Sidebar = ({
   setEnv,
   bearerToken,
   setBearerToken,
+  headerName,
+  setHeaderName,
   onConnect,
   onDisconnect,
   stdErrNotifications,
@@ -176,6 +180,7 @@ const Sidebar = ({
                   variant="outline"
                   onClick={() => setShowBearerToken(!showBearerToken)}
                   className="flex items-center w-full"
+                  data-testid="auth-button"
                   aria-expanded={showBearerToken}
                 >
                   {showBearerToken ? (
@@ -187,6 +192,16 @@ const Sidebar = ({
                 </Button>
                 {showBearerToken && (
                   <div className="space-y-2">
+                    <label className="text-sm font-medium">Header Name</label>
+                    <Input
+                      placeholder="Authorization"
+                      onChange={(e) =>
+                        setHeaderName && setHeaderName(e.target.value)
+                      }
+                      data-testid="header-input"
+                      className="font-mono"
+                      value={headerName}
+                    />
                     <label
                       className="text-sm font-medium"
                       htmlFor="bearer-token-input"
@@ -198,6 +213,7 @@ const Sidebar = ({
                       placeholder="Bearer Token"
                       value={bearerToken}
                       onChange={(e) => setBearerToken(e.target.value)}
+                      data-testid="bearer-token-input"
                       className="font-mono"
                       type="password"
                     />
diff --git a/client/src/components/__tests__/Sidebar.test.tsx b/client/src/components/__tests__/Sidebar.test.tsx
index bce97370d..980dc0120 100644
--- a/client/src/components/__tests__/Sidebar.test.tsx
+++ b/client/src/components/__tests__/Sidebar.test.tsx
@@ -1,4 +1,5 @@
 import { render, screen, fireEvent } from "@testing-library/react";
+import "@testing-library/jest-dom";
 import { describe, it, beforeEach, jest } from "@jest/globals";
 import Sidebar from "../Sidebar";
 import { DEFAULT_INSPECTOR_CONFIG } from "@/lib/constants";
@@ -109,6 +110,157 @@ describe("Sidebar Environment Variables", () => {
     });
   });
 
+  describe("Authentication", () => {
+    const openAuthSection = () => {
+      const button = screen.getByTestId("auth-button");
+      fireEvent.click(button);
+    };
+
+    it("should update bearer token", () => {
+      const setBearerToken = jest.fn();
+      renderSidebar({
+        bearerToken: "",
+        setBearerToken,
+        transportType: "sse", // Set transport type to SSE
+      });
+
+      openAuthSection();
+
+      const tokenInput = screen.getByTestId("bearer-token-input");
+      fireEvent.change(tokenInput, { target: { value: "new_token" } });
+
+      expect(setBearerToken).toHaveBeenCalledWith("new_token");
+    });
+
+    it("should update header name", () => {
+      const setHeaderName = jest.fn();
+      renderSidebar({
+        headerName: "Authorization",
+        setHeaderName,
+        transportType: "sse",
+      });
+
+      openAuthSection();
+
+      const headerInput = screen.getByTestId("header-input");
+      fireEvent.change(headerInput, { target: { value: "X-Custom-Auth" } });
+
+      expect(setHeaderName).toHaveBeenCalledWith("X-Custom-Auth");
+    });
+
+    it("should clear bearer token", () => {
+      const setBearerToken = jest.fn();
+      renderSidebar({
+        bearerToken: "existing_token",
+        setBearerToken,
+        transportType: "sse", // Set transport type to SSE
+      });
+
+      openAuthSection();
+
+      const tokenInput = screen.getByTestId("bearer-token-input");
+      fireEvent.change(tokenInput, { target: { value: "" } });
+
+      expect(setBearerToken).toHaveBeenCalledWith("");
+    });
+
+    it("should properly render bearer token input", () => {
+      const { rerender } = renderSidebar({
+        bearerToken: "existing_token",
+        transportType: "sse", // Set transport type to SSE
+      });
+
+      openAuthSection();
+
+      // Token input should be a password field
+      const tokenInput = screen.getByTestId("bearer-token-input");
+      expect(tokenInput).toHaveProperty("type", "password");
+
+      // Update the token
+      fireEvent.change(tokenInput, { target: { value: "new_token" } });
+
+      // Rerender with updated token
+      rerender(
+        <TooltipProvider>
+          <Sidebar
+            {...defaultProps}
+            bearerToken="new_token"
+            transportType="sse"
+          />
+        </TooltipProvider>,
+      );
+
+      // Token input should still exist after update
+      expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
+    });
+
+    it("should maintain token visibility state after update", () => {
+      const { rerender } = renderSidebar({
+        bearerToken: "existing_token",
+        transportType: "sse", // Set transport type to SSE
+      });
+
+      openAuthSection();
+
+      // Token input should be a password field
+      const tokenInput = screen.getByTestId("bearer-token-input");
+      expect(tokenInput).toHaveProperty("type", "password");
+
+      // Update the token
+      fireEvent.change(tokenInput, { target: { value: "new_token" } });
+
+      // Rerender with updated token
+      rerender(
+        <TooltipProvider>
+          <Sidebar
+            {...defaultProps}
+            bearerToken="new_token"
+            transportType="sse"
+          />
+        </TooltipProvider>,
+      );
+
+      // Token input should still exist after update
+      expect(screen.getByTestId("bearer-token-input")).toBeInTheDocument();
+    });
+
+    it("should maintain header name when toggling auth section", () => {
+      renderSidebar({
+        headerName: "X-API-Key",
+        transportType: "sse",
+      });
+
+      // Open auth section
+      openAuthSection();
+
+      // Verify header name is displayed
+      const headerInput = screen.getByTestId("header-input");
+      expect(headerInput).toHaveValue("X-API-Key");
+
+      // Close auth section
+      const authButton = screen.getByTestId("auth-button");
+      fireEvent.click(authButton);
+
+      // Reopen auth section
+      fireEvent.click(authButton);
+
+      // Verify header name is still preserved
+      expect(screen.getByTestId("header-input")).toHaveValue("X-API-Key");
+    });
+
+    it("should display default header name when not specified", () => {
+      renderSidebar({
+        headerName: undefined,
+        transportType: "sse",
+      });
+
+      openAuthSection();
+
+      const headerInput = screen.getByTestId("header-input");
+      expect(headerInput).toHaveAttribute("placeholder", "Authorization");
+    });
+  });
+
   describe("Key Editing", () => {
     it("should maintain order when editing first key", () => {
       const setEnv = jest.fn();
diff --git a/client/src/lib/hooks/useConnection.ts b/client/src/lib/hooks/useConnection.ts
index ac4605c7f..d5d70707b 100644
--- a/client/src/lib/hooks/useConnection.ts
+++ b/client/src/lib/hooks/useConnection.ts
@@ -48,6 +48,7 @@ interface UseConnectionOptions {
   sseUrl: string;
   env: Record<string, string>;
   bearerToken?: string;
+  headerName?: string;
   config: InspectorConfig;
   onNotification?: (notification: Notification) => void;
   onStdErrNotification?: (notification: Notification) => void;
@@ -64,6 +65,7 @@ export function useConnection({
   sseUrl,
   env,
   bearerToken,
+  headerName,
   config,
   onNotification,
   onStdErrNotification,
@@ -293,7 +295,8 @@ export function useConnection({
       // Use manually provided bearer token if available, otherwise use OAuth tokens
       const token = bearerToken || (await authProvider.tokens())?.access_token;
       if (token) {
-        headers["Authorization"] = `Bearer ${token}`;
+        const authHeaderName = headerName || "Authorization";
+        headers[authHeaderName] = `Bearer ${token}`;
       }
 
       const clientTransport = new SSEClientTransport(mcpProxyServerUrl, {


Reviews:
user: login: pulkitsharma07
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T18:23:00Z
user: login: idosal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T18:37:49Z
user: login: pulkitsharma07
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T21:47:17Z
user: login: idosal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T21:50:22Z
user: login: olaservo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-12T13:07:59Z
user: login: olaservo
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-12T13:09:10Z
user: login: idosal
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-12T19:55:21Z
user: login: cliffhall
body: Could we amend the README?
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-14T22:22:53Z
user: login: cliffhall
body: Weird. My review page was out of sync and it looked as if the README wasn't amended. LGTM! 👍

state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-14T22:23:43Z

Review comments:
diff_hunk: @@ -169,6 +173,9 @@ const App = () => {
     sseUrl,
     env,
     bearerToken,
+    headerName,
+    proxyServerUrl: getMCPProxyAddress(config),
path: client/src/App.tsx
user: login: pulkitsharma07
body: these are no longer there in `main`, please check
created_at: 2025-04-11T18:22:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -169,6 +173,9 @@ const App = () => {
     sseUrl,
     env,
     bearerToken,
+    headerName,
+    proxyServerUrl: getMCPProxyAddress(config),
path: client/src/App.tsx
user: login: idosal
body: Thanks, I'll remove it now
created_at: 2025-04-11T18:37:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -48,6 +48,8 @@ interface UseConnectionOptions {
   sseUrl: string;
   env: Record<string, string>;
   bearerToken?: string;
+  headerName?: string;
+  requestTimeout?: number;
path: client/src/lib/hooks/useConnection.ts
user: login: pulkitsharma07
body: `requestTimeout` is no longer there in `main`
created_at: 2025-04-11T21:45:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -45,7 +45,7 @@ import Sidebar from "./components/Sidebar";
 import ToolsTab from "./components/ToolsTab";
 import { DEFAULT_INSPECTOR_CONFIG } from "./lib/constants";
 import { InspectorConfig } from "./lib/configurationTypes";
-import { getMCPProxyAddress } from "./utils/configUtils";
+import { getMCPProxyAddress, getMCPServerRequestTimeout } from "./utils/configUtils";
path: client/src/App.tsx
user: login: pulkitsharma07
body: remove `getMCPServerRequestTimeout`
created_at: 2025-04-11T21:46:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -117,6 +117,10 @@ const App = () => {
     return localStorage.getItem("lastBearerToken") || "";
   });
 
+  const [headerName, setHeaderName] = useState<string>(() => {
+    return localStorage.getItem("lastHeaderName") || "";
path: client/src/App.tsx
user: login: pulkitsharma07
body: why is this called `lastHeaderName` ? what does `last` signify here ?
created_at: 2025-04-11T21:46:49Z
author_association: CONTRIBUTOR
diff_hunk: @@ -117,6 +117,10 @@ const App = () => {
     return localStorage.getItem("lastBearerToken") || "";
   });
 
+  const [headerName, setHeaderName] = useState<string>(() => {
+    return localStorage.getItem("lastHeaderName") || "";
path: client/src/App.tsx
user: login: idosal
body: I just kept the convention of `lastBearerToken`. I assumed `last` means "last inserted value" (which was stored).
created_at: 2025-04-11T21:50:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -293,7 +295,8 @@ export function useConnection({
       // Use manually provided bearer token if available, otherwise use OAuth tokens
       const token = bearerToken || (await authProvider.tokens())?.access_token;
       if (token) {
-        headers["Authorization"] = `Bearer ${token}`;
+        const authHeaderName = headerName || "Authorization";
+        headers[authHeaderName] = `${token}`;
path: client/src/lib/hooks/useConnection.ts
user: login: olaservo
body: Was it intentional to remove the `Bearer ` when the token value is being set?
created_at: 2025-04-12T13:07:59Z
author_association: MEMBER
diff_hunk: @@ -40,7 +40,7 @@ For more details on ways to use the inspector, see the [Inspector section of the
 
 ### Authentication
 
-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.
+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.
path: README.md
user: login: olaservo
body: ```suggestion
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.
```
created_at: 2025-04-12T13:09:09Z
author_association: MEMBER
diff_hunk: @@ -293,7 +295,8 @@ export function useConnection({
       // Use manually provided bearer token if available, otherwise use OAuth tokens
       const token = bearerToken || (await authProvider.tokens())?.access_token;
       if (token) {
-        headers["Authorization"] = `Bearer ${token}`;
+        const authHeaderName = headerName || "Authorization";
+        headers[authHeaderName] = `${token}`;
path: client/src/lib/hooks/useConnection.ts
user: login: idosal
body: Nope. I brought it back. Thanks!
created_at: 2025-04-12T19:55:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -40,7 +40,7 @@ For more details on ways to use the inspector, see the [Inspector section of the
 
 ### Authentication
 
-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.
+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.
path: README.md
user: login: cliffhall
body: @idosal I think it would be worth accepting @olaservo's change. It may not be obvious how to override.
created_at: 2025-04-14T22:18:53Z
author_association: MEMBER

PR comments:
user: login: pulkitsharma07
created_at: 2025-04-05T10:50:20Z
body: You can add tests here https://github.com/modelcontextprotocol/inspector/blob/main/client/src/components/__tests__/Sidebar.test.tsx
author_association: CONTRIBUTOR
user: login: idosal
created_at: 2025-04-11T21:14:30Z
body: @pulkitsharma07 fixed comments and added tests :)
author_association: CONTRIBUTOR
user: login: cliffhall
created_at: 2025-04-11T21:14:39Z
body: > Some servers use a custom header name (e.g., Vercel Preview). The current feature doesn't support these servers.

Hi @idosal 

Can you add a video (or loom if it's too long) of using an alternative header and an easy set of steps to reproduce?
author_association: MEMBER
user: login: idosal
created_at: 2025-04-11T21:19:01Z
body: > > Some servers use a custom header name (e.g., Vercel Preview). The current feature doesn't support these servers.
> 
> Hi @idosal
> 
> Can you add a video (or loom if it's too long) of using an alternative header and an easy set of steps to reproduce?

Hey @cliffhall ,
I'm sorry to say I no longer use Vercel, so I don't have an example handy.
author_association: CONTRIBUTOR
user: login: cliffhall
created_at: 2025-04-11T21:21:27Z
body: > > > Some servers use a custom header name (e.g., Vercel Preview). The current feature doesn't support these servers.
> > 
> > 
> > Hi @idosal
> > Can you add a video (or loom if it's too long) of using an alternative header and an easy set of steps to reproduce?
> 
> Hey @cliffhall , I'm sorry to say I no longer use Vercel, so I don't have an example handy.

Seems like a worthwhile change if there are places out there that do this. Maybe you'd like to find a site that uses something other than "Authorization" header? I wouldn't want to merge without testability.
author_association: MEMBER
user: login: idosal
created_at: 2025-04-11T21:43:43Z
body: > > > > Some servers use a custom header name (e.g., Vercel Preview). The current feature doesn't support these servers.
> > > 
> > > 
> > > Hi @idosal
> > > Can you add a video (or loom if it's too long) of using an alternative header and an easy set of steps to reproduce?
> > 
> > 
> > Hey @cliffhall , I'm sorry to say I no longer use Vercel, so I don't have an example handy.
> 
> Seems like a worthwhile change if there are places out there that do this. Maybe you'd like to find a site that uses something other than "Authorization" header? I wouldn't want to merge without testability.

That makes complete sense. I'm afraid I couldn't find a good example in the wild. Attaching a screenshot that shows the custom header being added to the request instead of `Authorization` if that helps:
<img width="1042" alt="image" src="https://github.com/user-attachments/assets/773ed12c-5ddd-40ab-bacc-0d9e10292712" />

If you feel the feature should wait until there are live examples, that's perfectly understandable.
author_association: CONTRIBUTOR
user: login: idosal
created_at: 2025-04-12T09:41:40Z
body: @pulkitsharma07 @cliffhall I've pushed all requested changes.
author_association: CONTRIBUTOR