Skip to content

Commit ae95758

Browse files
committed
feat: add OAuth proxy support to eliminate CORS issues
Add server-side proxy endpoints for OAuth discovery, client registration, and token exchange to avoid CORS issues when using proxy connection mode. Server proxy endpoints: - GET /oauth/resource-metadata - proxies OAuth protected resource metadata - GET /oauth/metadata - proxies OAuth authorization server metadata - POST /oauth/register - proxies dynamic client registration - POST /oauth/token - proxies token exchange Client changes: - Construct correct RFC 9728/8414 path-aware well-known URLs client-side - Route all OAuth discovery through proxy when in proxy connection mode - Eliminate direct browser fetches that fail with CORS in proxy mode
1 parent 6ce441a commit ae95758

File tree

12 files changed

+987
-45
lines changed

12 files changed

+987
-45
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
- uses: actions/checkout@v6
1616

1717
- name: Check formatting
18-
run: npx prettier --check .
18+
run: npx prettier@3.7.4 --check .
1919

2020
- uses: actions/setup-node@v6
2121
with:

client/src/App.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -622,9 +622,14 @@ const App = () => {
622622
};
623623

624624
try {
625-
const stateMachine = new OAuthStateMachine(sseUrl, (updates) => {
626-
currentState = { ...currentState, ...updates };
627-
});
625+
const stateMachine = new OAuthStateMachine(
626+
sseUrl,
627+
(updates) => {
628+
currentState = { ...currentState, ...updates };
629+
},
630+
connectionType,
631+
config,
632+
);
628633

629634
while (
630635
currentState.oauthStep !== "complete" &&
@@ -1264,6 +1269,8 @@ const App = () => {
12641269
onBack={() => setIsAuthDebuggerVisible(false)}
12651270
authState={authState}
12661271
updateAuthState={updateAuthState}
1272+
connectionType={connectionType}
1273+
config={config}
12671274
/>
12681275
</TabsContent>
12691276
);

client/src/components/AuthDebugger.tsx

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ import { OAuthFlowProgress } from "./OAuthFlowProgress";
77
import { OAuthStateMachine } from "../lib/oauth-state-machine";
88
import { SESSION_KEYS } from "../lib/constants";
99
import { validateRedirectUrl } from "@/utils/urlValidation";
10+
import { InspectorConfig } from "../lib/configurationTypes";
1011

1112
export interface AuthDebuggerProps {
1213
serverUrl: string;
1314
onBack: () => void;
1415
authState: AuthDebuggerState;
1516
updateAuthState: (updates: Partial<AuthDebuggerState>) => void;
17+
connectionType: "direct" | "proxy";
18+
config: InspectorConfig;
1619
}
1720

1821
interface StatusMessageProps {
@@ -60,6 +63,8 @@ const AuthDebugger = ({
6063
onBack,
6164
authState,
6265
updateAuthState,
66+
connectionType,
67+
config,
6368
}: AuthDebuggerProps) => {
6469
// Check for existing tokens on mount
6570
useEffect(() => {
@@ -103,8 +108,9 @@ const AuthDebugger = ({
103108
}, [serverUrl, updateAuthState]);
104109

105110
const stateMachine = useMemo(
106-
() => new OAuthStateMachine(serverUrl, updateAuthState),
107-
[serverUrl, updateAuthState],
111+
() =>
112+
new OAuthStateMachine(serverUrl, updateAuthState, connectionType, config),
113+
[serverUrl, updateAuthState, connectionType, config],
108114
);
109115

110116
const proceedToNextStep = useCallback(async () => {
@@ -150,11 +156,16 @@ const AuthDebugger = ({
150156
latestError: null,
151157
};
152158

153-
const oauthMachine = new OAuthStateMachine(serverUrl, (updates) => {
154-
// Update our temporary state during the process
155-
currentState = { ...currentState, ...updates };
156-
// But don't call updateAuthState yet
157-
});
159+
const oauthMachine = new OAuthStateMachine(
160+
serverUrl,
161+
(updates) => {
162+
// Update our temporary state during the process
163+
currentState = { ...currentState, ...updates };
164+
// But don't call updateAuthState yet
165+
},
166+
connectionType,
167+
config,
168+
);
158169

159170
// Manually step through each stage of the OAuth flow
160171
while (currentState.oauthStep !== "complete") {

client/src/lib/__tests__/auth.test.ts

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { discoverScopes } from "../auth";
22
import { discoverAuthorizationServerMetadata } from "@modelcontextprotocol/sdk/client/auth.js";
3+
import { InspectorConfig } from "../configurationTypes";
34

45
jest.mock("@modelcontextprotocol/sdk/client/auth.js", () => ({
56
discoverAuthorizationServerMetadata: jest.fn(),
@@ -10,6 +11,39 @@ const mockDiscoverAuth =
1011
typeof discoverAuthorizationServerMetadata
1112
>;
1213

14+
const mockConfig: InspectorConfig = {
15+
MCP_SERVER_REQUEST_TIMEOUT: {
16+
label: "Request Timeout",
17+
description: "Timeout for MCP requests",
18+
value: 30000,
19+
is_session_item: false,
20+
},
21+
MCP_REQUEST_TIMEOUT_RESET_ON_PROGRESS: {
22+
label: "Reset Timeout on Progress",
23+
description: "Reset timeout on progress notifications",
24+
value: true,
25+
is_session_item: false,
26+
},
27+
MCP_REQUEST_MAX_TOTAL_TIMEOUT: {
28+
label: "Max Total Timeout",
29+
description: "Maximum total timeout",
30+
value: 300000,
31+
is_session_item: false,
32+
},
33+
MCP_PROXY_FULL_ADDRESS: {
34+
label: "Proxy Address",
35+
description: "Full address of the MCP proxy",
36+
value: "http://localhost:6277",
37+
is_session_item: false,
38+
},
39+
MCP_PROXY_AUTH_TOKEN: {
40+
label: "Proxy Auth Token",
41+
description: "Authentication token for the proxy",
42+
value: "",
43+
is_session_item: false,
44+
},
45+
};
46+
1347
const baseMetadata = {
1448
issuer: "https://test.com",
1549
authorization_endpoint: "https://test.com/authorize",
@@ -129,7 +163,12 @@ describe("discoverScopes", () => {
129163
}) => {
130164
mockDiscoverAuth.mockResolvedValue(mockResolves);
131165

132-
const result = await discoverScopes(serverUrl, resourceMetadata);
166+
const result = await discoverScopes(
167+
serverUrl,
168+
"direct",
169+
mockConfig,
170+
resourceMetadata,
171+
);
133172

134173
expect(result).toBe(expected);
135174
if (expectedCallUrl) {
@@ -147,7 +186,12 @@ describe("discoverScopes", () => {
147186
mockDiscoverAuth.mockResolvedValue(mockResolves);
148187
}
149188

150-
const result = await discoverScopes(serverUrl, resourceMetadata);
189+
const result = await discoverScopes(
190+
serverUrl,
191+
"direct",
192+
mockConfig,
193+
resourceMetadata,
194+
);
151195

152196
expect(result).toBeUndefined();
153197
},

0 commit comments

Comments
 (0)