sunpeak 0.20.29 → 0.20.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/commands/inspect.mjs
CHANGED
|
@@ -19,6 +19,8 @@ const { existsSync, readdirSync, readFileSync } = fs;
|
|
|
19
19
|
const { join, resolve, dirname, sep } = path;
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
21
21
|
import { createServer as createHttpServer } from 'http';
|
|
22
|
+
import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js';
|
|
23
|
+
import { OAuthProtectedResourceMetadataSchema } from '@modelcontextprotocol/sdk/shared/auth.js';
|
|
22
24
|
import { getPort } from '../lib/get-port.mjs';
|
|
23
25
|
import { startSandboxServer } from '../lib/sandbox-server.mjs';
|
|
24
26
|
import { getDevOverlayScript } from '../lib/dev-overlay.mjs';
|
|
@@ -176,6 +178,82 @@ function createInMemoryOAuthProvider(redirectUrl, opts = {}) {
|
|
|
176
178
|
};
|
|
177
179
|
}
|
|
178
180
|
|
|
181
|
+
/**
|
|
182
|
+
* @param {URL} serverUrl
|
|
183
|
+
* @returns {{ pathMetadataUrl: URL, rootMetadataUrl: URL } | undefined}
|
|
184
|
+
*/
|
|
185
|
+
function getMcpResourceMetadataUrls(serverUrl) {
|
|
186
|
+
if (serverUrl.protocol !== 'http:' && serverUrl.protocol !== 'https:') return undefined;
|
|
187
|
+
if (serverUrl.pathname === '/' || serverUrl.pathname === '') return undefined;
|
|
188
|
+
|
|
189
|
+
const pathname = serverUrl.pathname.endsWith('/')
|
|
190
|
+
? serverUrl.pathname.slice(0, -1)
|
|
191
|
+
: serverUrl.pathname;
|
|
192
|
+
|
|
193
|
+
const pathMetadataUrl = new URL(`/.well-known/oauth-protected-resource${pathname}`, serverUrl);
|
|
194
|
+
pathMetadataUrl.search = serverUrl.search;
|
|
195
|
+
|
|
196
|
+
const rootMetadataUrl = new URL('/.well-known/oauth-protected-resource', serverUrl.origin);
|
|
197
|
+
return { pathMetadataUrl, rootMetadataUrl };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* MCP auth discovery supports both endpoint-path and root protected-resource
|
|
202
|
+
* metadata. When no WWW-Authenticate resource_metadata URL is available, the
|
|
203
|
+
* current MCP authorization draft says clients must try the endpoint path
|
|
204
|
+
* first, then the root well-known URI.
|
|
205
|
+
*
|
|
206
|
+
* The SDK already falls back from endpoint-path to root on 4xx responses. This
|
|
207
|
+
* helper detects the remaining common invalid-endpoint case before OAuth starts:
|
|
208
|
+
* the endpoint-path URL returns 200 but serves a text/html or text/plain landing
|
|
209
|
+
* page instead of protected-resource metadata JSON. In that case, start OAuth
|
|
210
|
+
* directly with the root metadata URL so no partial provider state is carried
|
|
211
|
+
* across a failed SDK auth attempt.
|
|
212
|
+
*
|
|
213
|
+
* @param {string | URL} serverUrl
|
|
214
|
+
* @param {typeof fetch} [fetchFn]
|
|
215
|
+
* @returns {Promise<string | undefined>}
|
|
216
|
+
*/
|
|
217
|
+
export async function resolveMcpResourceMetadataUrl(serverUrl, fetchFn = fetch) {
|
|
218
|
+
let parsed;
|
|
219
|
+
try {
|
|
220
|
+
parsed = serverUrl instanceof URL ? serverUrl : new URL(serverUrl);
|
|
221
|
+
} catch {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const urls = getMcpResourceMetadataUrls(parsed);
|
|
226
|
+
if (!urls) return undefined;
|
|
227
|
+
|
|
228
|
+
let response;
|
|
229
|
+
try {
|
|
230
|
+
response = await fetchFn(urls.pathMetadataUrl, {
|
|
231
|
+
headers: { 'MCP-Protocol-Version': LATEST_PROTOCOL_VERSION },
|
|
232
|
+
});
|
|
233
|
+
} catch {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (!response.ok) {
|
|
238
|
+
await response.body?.cancel();
|
|
239
|
+
return undefined;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
243
|
+
if (!contentType.toLowerCase().includes('application/json')) {
|
|
244
|
+
await response.body?.cancel();
|
|
245
|
+
return urls.rootMetadataUrl.toString();
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
OAuthProtectedResourceMetadataSchema.parse(await response.json());
|
|
250
|
+
} catch {
|
|
251
|
+
return urls.rootMetadataUrl.toString();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
|
|
179
257
|
/**
|
|
180
258
|
* Negotiate OAuth with an MCP server and return an authenticated provider.
|
|
181
259
|
*
|
|
@@ -197,10 +275,14 @@ async function negotiateOAuth(serverUrl) {
|
|
|
197
275
|
|
|
198
276
|
const oauthState = createInMemoryOAuthProvider(callbackUrl);
|
|
199
277
|
const { provider } = oauthState;
|
|
278
|
+
const resourceMetadataUrl = await resolveMcpResourceMetadataUrl(serverUrl);
|
|
200
279
|
|
|
201
280
|
// First call to auth() — discovers metadata, registers client, and either
|
|
202
281
|
// returns AUTHORIZED (client_credentials) or REDIRECT (authorization_code).
|
|
203
|
-
const result = await auth(provider, {
|
|
282
|
+
const result = await auth(provider, {
|
|
283
|
+
serverUrl: new URL(serverUrl),
|
|
284
|
+
...(resourceMetadataUrl ? { resourceMetadataUrl: new URL(resourceMetadataUrl) } : {}),
|
|
285
|
+
});
|
|
204
286
|
|
|
205
287
|
if (result === 'AUTHORIZED') {
|
|
206
288
|
return provider;
|
|
@@ -1054,6 +1136,7 @@ function sunpeakInspectEndpointsPlugin(getClient, setClient, pluginOpts = {}) {
|
|
|
1054
1136
|
// Always create a fresh provider for an explicit Authorize click.
|
|
1055
1137
|
// This ensures the user's current credentials (or lack thereof) are
|
|
1056
1138
|
// used, not stale ones from a previous attempt.
|
|
1139
|
+
const resourceMetadataUrl = await resolveMcpResourceMetadataUrl(serverUrl);
|
|
1057
1140
|
const oauthState = createInMemoryOAuthProvider(callbackUrl, { clientId, clientSecret });
|
|
1058
1141
|
oauthProviders.set(serverUrl, oauthState);
|
|
1059
1142
|
|
|
@@ -1062,6 +1145,7 @@ function sunpeakInspectEndpointsPlugin(getClient, setClient, pluginOpts = {}) {
|
|
|
1062
1145
|
const result = await auth(oauthState.provider, {
|
|
1063
1146
|
serverUrl,
|
|
1064
1147
|
scope,
|
|
1148
|
+
...(resourceMetadataUrl ? { resourceMetadataUrl: new URL(resourceMetadataUrl) } : {}),
|
|
1065
1149
|
});
|
|
1066
1150
|
|
|
1067
1151
|
if (result === 'REDIRECT') {
|
package/package.json
CHANGED