Skip to content

Commit 931bff2

Browse files
committed
feat: add OAuth proxy utility functions
- Create discoverAuthorizationServerMetadataViaProxy() - Create discoverOAuthProtectedResourceMetadataViaProxy() - Create registerClientViaProxy() for DCR - Create exchangeAuthorizationViaProxy() for token exchange - Reuse getMCPProxyAddress() and getMCPProxyAuthToken() for consistency - Provide clear error messages for debugging
1 parent 047ce22 commit 931bff2

1 file changed

Lines changed: 152 additions & 0 deletions

File tree

client/src/lib/oauth-proxy.ts

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/**
2+
* OAuth Proxy Utilities
3+
*
4+
* These functions route OAuth requests through the MCP Inspector proxy server
5+
* to avoid CORS issues when connectionType is "proxy".
6+
*/
7+
8+
import {
9+
OAuthMetadata,
10+
OAuthProtectedResourceMetadata,
11+
OAuthClientInformation,
12+
OAuthTokens,
13+
OAuthClientMetadata,
14+
} from "@modelcontextprotocol/sdk/shared/auth.js";
15+
import { getMCPProxyAddress, getMCPProxyAuthToken } from "@/utils/configUtils";
16+
import { InspectorConfig } from "./configurationTypes";
17+
18+
/**
19+
* Get proxy headers for authentication
20+
*/
21+
function getProxyHeaders(config: InspectorConfig): Record<string, string> {
22+
const { token, header } = getMCPProxyAuthToken(config);
23+
const headers: Record<string, string> = {
24+
"Content-Type": "application/json",
25+
};
26+
27+
if (token) {
28+
headers[header] = `Bearer ${token}`;
29+
}
30+
31+
return headers;
32+
}
33+
34+
/**
35+
* Discover OAuth Authorization Server Metadata via proxy
36+
*/
37+
export async function discoverAuthorizationServerMetadataViaProxy(
38+
authServerUrl: URL,
39+
config: InspectorConfig,
40+
): Promise<OAuthMetadata> {
41+
const proxyAddress = getMCPProxyAddress(config);
42+
const url = new URL("/oauth/metadata", proxyAddress);
43+
url.searchParams.set("authServerUrl", authServerUrl.toString());
44+
45+
const response = await fetch(url.toString(), {
46+
method: "GET",
47+
headers: getProxyHeaders(config),
48+
});
49+
50+
if (!response.ok) {
51+
const error = await response
52+
.json()
53+
.catch(() => ({ error: response.statusText }));
54+
throw new Error(
55+
`Failed to discover OAuth metadata: ${error.error || response.statusText}`,
56+
);
57+
}
58+
59+
return await response.json();
60+
}
61+
62+
/**
63+
* Discover OAuth Protected Resource Metadata via proxy
64+
*/
65+
export async function discoverOAuthProtectedResourceMetadataViaProxy(
66+
serverUrl: string,
67+
config: InspectorConfig,
68+
): Promise<OAuthProtectedResourceMetadata> {
69+
const proxyAddress = getMCPProxyAddress(config);
70+
const url = new URL("/oauth/resource-metadata", proxyAddress);
71+
url.searchParams.set("serverUrl", serverUrl);
72+
73+
const response = await fetch(url.toString(), {
74+
method: "GET",
75+
headers: getProxyHeaders(config),
76+
});
77+
78+
if (!response.ok) {
79+
const error = await response
80+
.json()
81+
.catch(() => ({ error: response.statusText }));
82+
throw new Error(
83+
`Failed to discover resource metadata: ${error.error || response.statusText}`,
84+
);
85+
}
86+
87+
return await response.json();
88+
}
89+
90+
/**
91+
* Register OAuth client via proxy (Dynamic Client Registration)
92+
*/
93+
export async function registerClientViaProxy(
94+
registrationEndpoint: string,
95+
clientMetadata: OAuthClientMetadata,
96+
config: InspectorConfig,
97+
): Promise<OAuthClientInformation> {
98+
const proxyAddress = getMCPProxyAddress(config);
99+
const url = new URL("/oauth/register", proxyAddress);
100+
101+
const response = await fetch(url.toString(), {
102+
method: "POST",
103+
headers: getProxyHeaders(config),
104+
body: JSON.stringify({
105+
registrationEndpoint,
106+
clientMetadata,
107+
}),
108+
});
109+
110+
if (!response.ok) {
111+
const error = await response
112+
.json()
113+
.catch(() => ({ error: response.statusText }));
114+
throw new Error(
115+
`Failed to register client: ${error.error || response.statusText}`,
116+
);
117+
}
118+
119+
return await response.json();
120+
}
121+
122+
/**
123+
* Exchange authorization code for tokens via proxy
124+
*/
125+
export async function exchangeAuthorizationViaProxy(
126+
tokenEndpoint: string,
127+
params: Record<string, string>,
128+
config: InspectorConfig,
129+
): Promise<OAuthTokens> {
130+
const proxyAddress = getMCPProxyAddress(config);
131+
const url = new URL("/oauth/token", proxyAddress);
132+
133+
const response = await fetch(url.toString(), {
134+
method: "POST",
135+
headers: getProxyHeaders(config),
136+
body: JSON.stringify({
137+
tokenEndpoint,
138+
params,
139+
}),
140+
});
141+
142+
if (!response.ok) {
143+
const error = await response
144+
.json()
145+
.catch(() => ({ error: response.statusText }));
146+
throw new Error(
147+
`Failed to exchange authorization code: ${error.error || response.statusText}`,
148+
);
149+
}
150+
151+
return await response.json();
152+
}

0 commit comments

Comments
 (0)