@toolsdk.ai/registry 1.0.131 → 1.0.133
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/README.md +11 -10
- package/dist/api/index.js +4 -0
- package/dist/domains/executor/executor-types.d.ts +3 -1
- package/dist/domains/executor/local-executor.d.ts +1 -1
- package/dist/domains/executor/local-executor.js +3 -3
- package/dist/domains/executor/sandbox-executor.d.ts +1 -1
- package/dist/domains/executor/sandbox-executor.js +3 -3
- package/dist/domains/oauth/__tests__/oauth-session.test.d.ts +1 -0
- package/dist/domains/oauth/__tests__/oauth-session.test.js +272 -0
- package/dist/domains/oauth/__tests__/oauth-utils.test.d.ts +1 -0
- package/dist/domains/oauth/__tests__/oauth-utils.test.js +284 -0
- package/dist/domains/oauth/index.d.ts +9 -0
- package/dist/domains/oauth/index.js +9 -0
- package/dist/domains/oauth/oauth-handler.d.ts +65 -0
- package/dist/domains/oauth/oauth-handler.js +355 -0
- package/dist/domains/oauth/oauth-route.d.ts +11 -0
- package/dist/domains/oauth/oauth-route.js +138 -0
- package/dist/domains/oauth/oauth-schema.d.ts +257 -0
- package/dist/domains/oauth/oauth-schema.js +119 -0
- package/dist/domains/oauth/oauth-session.d.ts +54 -0
- package/dist/domains/oauth/oauth-session.js +116 -0
- package/dist/domains/oauth/oauth-types.d.ts +148 -0
- package/dist/domains/oauth/oauth-types.js +9 -0
- package/dist/domains/oauth/oauth-utils.d.ts +99 -0
- package/dist/domains/oauth/oauth-utils.js +267 -0
- package/dist/domains/package/package-handler.d.ts +2 -2
- package/dist/domains/package/package-handler.js +4 -4
- package/dist/domains/package/package-route.js +5 -5
- package/dist/domains/package/package-schema.d.ts +81 -4
- package/dist/domains/package/package-schema.js +17 -0
- package/dist/domains/package/package-so.d.ts +11 -3
- package/dist/domains/package/package-so.js +4 -3
- package/dist/shared/schemas/common-schema.d.ts +92 -4
- package/dist/shared/schemas/common-schema.js +13 -0
- package/dist/shared/scripts-helpers/index.d.ts +9 -1
- package/dist/shared/utils/mcp-client-util.d.ts +3 -3
- package/dist/shared/utils/mcp-client-util.js +22 -1
- package/indexes/categories-list.json +1 -0
- package/indexes/packages-list.json +15 -0
- package/package.json +2 -1
- package/packages/developer-tools/github-mcp.json +19 -0
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Handler
|
|
3
|
+
*
|
|
4
|
+
* Business logic for OAuth flow:
|
|
5
|
+
* 1. Prepare: Discovery + Registration + Build Auth URL
|
|
6
|
+
* 2. Callback: Exchange code for tokens + Notify caller
|
|
7
|
+
* 3. Refresh: Refresh access token
|
|
8
|
+
*/
|
|
9
|
+
import { getServerPort } from "../../shared/config/environment";
|
|
10
|
+
import { createErrorResponse, createResponse } from "../../shared/utils/response-util";
|
|
11
|
+
import { repository } from "../package/package-handler";
|
|
12
|
+
import { oauthSessionStore } from "./oauth-session";
|
|
13
|
+
import { buildAuthorizationUrl, discoverAuthServerMetadata, discoverProtectedResourceMetadata, exchangeCodeForTokens, generatePKCE, generateSessionId, generateState, getCanonicalResourceUri, refreshAccessToken, registerClient, verifyPKCESupport, } from "./oauth-utils";
|
|
14
|
+
/**
|
|
15
|
+
* Get the Registry's OAuth callback URL
|
|
16
|
+
*/
|
|
17
|
+
function getRegistryCallbackUrl() {
|
|
18
|
+
const port = getServerPort();
|
|
19
|
+
const host = process.env.REGISTRY_HOST || `localhost:${port}`;
|
|
20
|
+
const protocol = process.env.REGISTRY_PROTOCOL || "http";
|
|
21
|
+
return `${protocol}://${host}/api/v1/oauth/callback`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get MCP Server URL from package configuration
|
|
25
|
+
* Returns the first streamable-http remote URL if available
|
|
26
|
+
*/
|
|
27
|
+
function getMcpServerUrl(packageName) {
|
|
28
|
+
try {
|
|
29
|
+
const config = repository.getPackageConfig(packageName);
|
|
30
|
+
if (config.remotes && config.remotes.length > 0) {
|
|
31
|
+
const httpRemote = config.remotes.find((r) => r.type === "streamable-http");
|
|
32
|
+
if (httpRemote) {
|
|
33
|
+
return httpRemote.url;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
catch (_a) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Prepare OAuth flow
|
|
44
|
+
*
|
|
45
|
+
* 1. Get MCP Server URL from package config
|
|
46
|
+
* 2. Discover Protected Resource Metadata
|
|
47
|
+
* 3. Discover Authorization Server Metadata
|
|
48
|
+
* 4. Verify PKCE support
|
|
49
|
+
* 5. Register client (Dynamic Client Registration)
|
|
50
|
+
* 6. Generate PKCE + state + session
|
|
51
|
+
* 7. Build authorization URL
|
|
52
|
+
*/
|
|
53
|
+
export async function prepareOAuth(request) {
|
|
54
|
+
var _a, _b;
|
|
55
|
+
const { packageName, callbackBaseUrl, mcpServerUrl: directMcpServerUrl } = request;
|
|
56
|
+
// 1. Get MCP Server URL - prefer directly provided URL over package config
|
|
57
|
+
const mcpServerUrl = directMcpServerUrl || getMcpServerUrl(packageName);
|
|
58
|
+
if (!mcpServerUrl) {
|
|
59
|
+
return createErrorResponse(`Package '${packageName}' does not support OAuth (no streamable-http remote configured). ` +
|
|
60
|
+
"You can provide mcpServerUrl directly in the request.", 400);
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
// 2. Discover Protected Resource Metadata
|
|
64
|
+
console.log(`[OAuth] Discovering protected resource metadata for ${mcpServerUrl}`);
|
|
65
|
+
const resourceMetadata = await discoverProtectedResourceMetadata(mcpServerUrl);
|
|
66
|
+
if (!resourceMetadata.authorization_servers ||
|
|
67
|
+
resourceMetadata.authorization_servers.length === 0) {
|
|
68
|
+
return createErrorResponse("No authorization servers found in resource metadata", 400);
|
|
69
|
+
}
|
|
70
|
+
const authServerUrl = resourceMetadata.authorization_servers[0];
|
|
71
|
+
// 3. Discover Authorization Server Metadata
|
|
72
|
+
console.log(`[OAuth] Discovering auth server metadata from ${authServerUrl}`);
|
|
73
|
+
const oauthMetadata = await discoverAuthServerMetadata(authServerUrl);
|
|
74
|
+
// 4. Verify PKCE support (MCP spec requirement)
|
|
75
|
+
const pkceStatus = verifyPKCESupport(oauthMetadata);
|
|
76
|
+
if (!pkceStatus.supported) {
|
|
77
|
+
return createErrorResponse("Authorization server explicitly does not support PKCE with S256 method (required by MCP spec)", 400);
|
|
78
|
+
}
|
|
79
|
+
if (!pkceStatus.advertised) {
|
|
80
|
+
console.log(`[OAuth] Warning: Authorization server does not advertise code_challenge_methods_supported, ` +
|
|
81
|
+
`proceeding with PKCE anyway (many servers support it without advertising)`);
|
|
82
|
+
}
|
|
83
|
+
// 5. Register client
|
|
84
|
+
let clientInfo;
|
|
85
|
+
const redirectUri = getRegistryCallbackUrl();
|
|
86
|
+
if (oauthMetadata.registration_endpoint) {
|
|
87
|
+
console.log(`[OAuth] Registering client at ${oauthMetadata.registration_endpoint}`);
|
|
88
|
+
clientInfo = await registerClient(oauthMetadata.registration_endpoint, {
|
|
89
|
+
redirect_uris: [redirectUri],
|
|
90
|
+
client_name: "MCP Registry",
|
|
91
|
+
grant_types: ["authorization_code", "refresh_token"],
|
|
92
|
+
response_types: ["code"],
|
|
93
|
+
token_endpoint_auth_method: "none",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
// If no DCR, we need pre-configured client credentials
|
|
98
|
+
// For now, use a default client_id (this should be configurable)
|
|
99
|
+
console.log("[OAuth] No registration endpoint, using default client");
|
|
100
|
+
clientInfo = {
|
|
101
|
+
client_id: process.env.MCP_OAUTH_CLIENT_ID || "mcp-registry",
|
|
102
|
+
client_secret: process.env.MCP_OAUTH_CLIENT_SECRET,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
// 6. Generate PKCE + state + session
|
|
106
|
+
const pkce = generatePKCE();
|
|
107
|
+
const state = generateState();
|
|
108
|
+
const sessionId = generateSessionId();
|
|
109
|
+
const session = {
|
|
110
|
+
sessionId,
|
|
111
|
+
state,
|
|
112
|
+
codeVerifier: pkce.codeVerifier,
|
|
113
|
+
codeChallenge: pkce.codeChallenge,
|
|
114
|
+
clientInfo,
|
|
115
|
+
callbackBaseUrl,
|
|
116
|
+
mcpServerUrl,
|
|
117
|
+
packageName,
|
|
118
|
+
oauthMetadata,
|
|
119
|
+
createdAt: Date.now(),
|
|
120
|
+
};
|
|
121
|
+
oauthSessionStore.set(session);
|
|
122
|
+
// 7. Build authorization URL
|
|
123
|
+
const scope = ((_a = resourceMetadata.scopes_supported) === null || _a === void 0 ? void 0 : _a.join(" ")) || ((_b = oauthMetadata.scopes_supported) === null || _b === void 0 ? void 0 : _b.join(" "));
|
|
124
|
+
const resource = getCanonicalResourceUri(mcpServerUrl);
|
|
125
|
+
const authUrl = buildAuthorizationUrl({
|
|
126
|
+
authorizationEndpoint: oauthMetadata.authorization_endpoint,
|
|
127
|
+
clientId: clientInfo.client_id,
|
|
128
|
+
redirectUri,
|
|
129
|
+
state,
|
|
130
|
+
codeChallenge: pkce.codeChallenge,
|
|
131
|
+
codeChallengeMethod: pkce.codeChallengeMethod,
|
|
132
|
+
scope,
|
|
133
|
+
resource,
|
|
134
|
+
});
|
|
135
|
+
const response = {
|
|
136
|
+
authUrl,
|
|
137
|
+
sessionId,
|
|
138
|
+
mcpServerUrl,
|
|
139
|
+
};
|
|
140
|
+
console.log(`[OAuth] Prepared OAuth flow for ${packageName}, sessionId: ${sessionId}`);
|
|
141
|
+
return createResponse(response);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
console.error("[OAuth] Prepare error:", error);
|
|
145
|
+
const message = error instanceof Error ? error.message : "Unknown error during OAuth preparation";
|
|
146
|
+
return createErrorResponse(message, 500);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Handle OAuth callback
|
|
151
|
+
*
|
|
152
|
+
* 1. Find session by state
|
|
153
|
+
* 2. Exchange code for tokens
|
|
154
|
+
* 3. POST tokens + clientInfo to callbackBaseUrl
|
|
155
|
+
* 4. Delete session
|
|
156
|
+
* 5. Return HTML to close popup
|
|
157
|
+
*/
|
|
158
|
+
export async function handleCallback(params) {
|
|
159
|
+
const { code, state, error, error_description } = params;
|
|
160
|
+
// Handle OAuth error
|
|
161
|
+
if (error) {
|
|
162
|
+
console.error(`[OAuth] Callback error: ${error} - ${error_description}`);
|
|
163
|
+
return {
|
|
164
|
+
success: false,
|
|
165
|
+
error,
|
|
166
|
+
error_description,
|
|
167
|
+
html: generateCallbackHtml(false, error_description || error),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
if (!code || !state) {
|
|
171
|
+
return {
|
|
172
|
+
success: false,
|
|
173
|
+
error: "invalid_request",
|
|
174
|
+
error_description: "Missing code or state parameter",
|
|
175
|
+
html: generateCallbackHtml(false, "Missing code or state parameter"),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// 1. Find session by state
|
|
179
|
+
const session = oauthSessionStore.getByState(state);
|
|
180
|
+
if (!session) {
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
error: "invalid_state",
|
|
184
|
+
error_description: "Invalid or expired state parameter",
|
|
185
|
+
html: generateCallbackHtml(false, "Invalid or expired authorization session"),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
// 2. Exchange code for tokens
|
|
190
|
+
const redirectUri = getRegistryCallbackUrl();
|
|
191
|
+
const resource = getCanonicalResourceUri(session.mcpServerUrl);
|
|
192
|
+
console.log(`[OAuth] Exchanging code for tokens, sessionId: ${session.sessionId}`);
|
|
193
|
+
const tokens = await exchangeCodeForTokens({
|
|
194
|
+
tokenEndpoint: session.oauthMetadata.token_endpoint,
|
|
195
|
+
code,
|
|
196
|
+
redirectUri,
|
|
197
|
+
clientId: session.clientInfo.client_id,
|
|
198
|
+
clientSecret: session.clientInfo.client_secret,
|
|
199
|
+
codeVerifier: session.codeVerifier,
|
|
200
|
+
resource,
|
|
201
|
+
});
|
|
202
|
+
// 3. POST to callbackBaseUrl
|
|
203
|
+
const callbackData = {
|
|
204
|
+
sessionId: session.sessionId,
|
|
205
|
+
tokens,
|
|
206
|
+
clientInfo: session.clientInfo,
|
|
207
|
+
mcpServerUrl: session.mcpServerUrl,
|
|
208
|
+
packageName: session.packageName,
|
|
209
|
+
};
|
|
210
|
+
console.log(`[OAuth] Posting callback data to ${session.callbackBaseUrl}`);
|
|
211
|
+
try {
|
|
212
|
+
const callbackResponse = await fetch(session.callbackBaseUrl, {
|
|
213
|
+
method: "POST",
|
|
214
|
+
headers: {
|
|
215
|
+
"Content-Type": "application/json",
|
|
216
|
+
},
|
|
217
|
+
body: JSON.stringify(callbackData),
|
|
218
|
+
});
|
|
219
|
+
if (!callbackResponse.ok) {
|
|
220
|
+
console.warn(`[OAuth] Callback POST returned ${callbackResponse.status}`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (callbackError) {
|
|
224
|
+
// Log but don't fail - the caller might handle this differently
|
|
225
|
+
console.warn("[OAuth] Failed to POST to callbackBaseUrl:", callbackError);
|
|
226
|
+
}
|
|
227
|
+
// 4. Delete session
|
|
228
|
+
oauthSessionStore.delete(session.sessionId);
|
|
229
|
+
// 5. Return success HTML
|
|
230
|
+
console.log(`[OAuth] OAuth flow completed successfully for ${session.packageName}`);
|
|
231
|
+
return {
|
|
232
|
+
success: true,
|
|
233
|
+
sessionId: session.sessionId,
|
|
234
|
+
html: generateCallbackHtml(true, undefined, session.sessionId),
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
catch (error) {
|
|
238
|
+
console.error("[OAuth] Callback processing error:", error);
|
|
239
|
+
const message = error instanceof Error ? error.message : "Token exchange failed";
|
|
240
|
+
// Delete session on error
|
|
241
|
+
oauthSessionStore.delete(session.sessionId);
|
|
242
|
+
return {
|
|
243
|
+
success: false,
|
|
244
|
+
error: "token_exchange_failed",
|
|
245
|
+
error_description: message,
|
|
246
|
+
html: generateCallbackHtml(false, message),
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Refresh access token
|
|
252
|
+
*/
|
|
253
|
+
export async function handleRefresh(request) {
|
|
254
|
+
const { mcpServerUrl, refreshToken, clientId, clientSecret } = request;
|
|
255
|
+
try {
|
|
256
|
+
// Discover auth server to get token endpoint
|
|
257
|
+
const resourceMetadata = await discoverProtectedResourceMetadata(mcpServerUrl);
|
|
258
|
+
if (!resourceMetadata.authorization_servers ||
|
|
259
|
+
resourceMetadata.authorization_servers.length === 0) {
|
|
260
|
+
return createErrorResponse("No authorization servers found", 400);
|
|
261
|
+
}
|
|
262
|
+
const authServerUrl = resourceMetadata.authorization_servers[0];
|
|
263
|
+
const oauthMetadata = await discoverAuthServerMetadata(authServerUrl);
|
|
264
|
+
const resource = getCanonicalResourceUri(mcpServerUrl);
|
|
265
|
+
const tokens = await refreshAccessToken({
|
|
266
|
+
tokenEndpoint: oauthMetadata.token_endpoint,
|
|
267
|
+
refreshToken,
|
|
268
|
+
clientId,
|
|
269
|
+
clientSecret,
|
|
270
|
+
resource,
|
|
271
|
+
});
|
|
272
|
+
return createResponse(tokens);
|
|
273
|
+
}
|
|
274
|
+
catch (error) {
|
|
275
|
+
console.error("[OAuth] Refresh error:", error);
|
|
276
|
+
const message = error instanceof Error ? error.message : "Token refresh failed";
|
|
277
|
+
return createErrorResponse(message, 500);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Generate HTML response for OAuth callback
|
|
282
|
+
* Uses postMessage to notify parent window
|
|
283
|
+
*/
|
|
284
|
+
function generateCallbackHtml(success, errorMessage, sessionId) {
|
|
285
|
+
const data = success
|
|
286
|
+
? JSON.stringify({ success: true, sessionId })
|
|
287
|
+
: JSON.stringify({ success: false, error: errorMessage });
|
|
288
|
+
return `<!DOCTYPE html>
|
|
289
|
+
<html>
|
|
290
|
+
<head>
|
|
291
|
+
<title>OAuth Callback</title>
|
|
292
|
+
<style>
|
|
293
|
+
body {
|
|
294
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
295
|
+
display: flex;
|
|
296
|
+
justify-content: center;
|
|
297
|
+
align-items: center;
|
|
298
|
+
height: 100vh;
|
|
299
|
+
margin: 0;
|
|
300
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
301
|
+
}
|
|
302
|
+
.container {
|
|
303
|
+
text-align: center;
|
|
304
|
+
padding: 40px;
|
|
305
|
+
background: white;
|
|
306
|
+
border-radius: 15px;
|
|
307
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
|
308
|
+
}
|
|
309
|
+
.success { color: #10B981; }
|
|
310
|
+
.error { color: #EF4444; }
|
|
311
|
+
.icon { font-size: 48px; margin-bottom: 20px; }
|
|
312
|
+
h1 { margin: 0 0 10px 0; font-size: 24px; }
|
|
313
|
+
p { color: #666; margin: 0; }
|
|
314
|
+
</style>
|
|
315
|
+
</head>
|
|
316
|
+
<body>
|
|
317
|
+
<div class="container">
|
|
318
|
+
${success
|
|
319
|
+
? `
|
|
320
|
+
<div class="icon">✅</div>
|
|
321
|
+
<h1 class="success">Authorization Successful</h1>
|
|
322
|
+
<p>You can close this window.</p>
|
|
323
|
+
`
|
|
324
|
+
: `
|
|
325
|
+
<div class="icon">❌</div>
|
|
326
|
+
<h1 class="error">Authorization Failed</h1>
|
|
327
|
+
<p>${errorMessage || "An error occurred"}</p>
|
|
328
|
+
`}
|
|
329
|
+
</div>
|
|
330
|
+
<script>
|
|
331
|
+
(function() {
|
|
332
|
+
const data = ${data};
|
|
333
|
+
|
|
334
|
+
// Try postMessage to parent/opener
|
|
335
|
+
if (window.opener) {
|
|
336
|
+
window.opener.postMessage({ type: 'oauth-callback', ...data }, '*');
|
|
337
|
+
setTimeout(() => window.close(), 1500);
|
|
338
|
+
} else if (window.parent !== window) {
|
|
339
|
+
window.parent.postMessage({ type: 'oauth-callback', ...data }, '*');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Auto-close after delay
|
|
343
|
+
setTimeout(() => {
|
|
344
|
+
try { window.close(); } catch(e) {}
|
|
345
|
+
}, 3000);
|
|
346
|
+
})();
|
|
347
|
+
</script>
|
|
348
|
+
</body>
|
|
349
|
+
</html>`;
|
|
350
|
+
}
|
|
351
|
+
export const oauthHandler = {
|
|
352
|
+
prepareOAuth,
|
|
353
|
+
handleCallback,
|
|
354
|
+
handleRefresh,
|
|
355
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Routes
|
|
3
|
+
*
|
|
4
|
+
* API endpoints for MCP OAuth flow:
|
|
5
|
+
* - POST /api/v1/oauth/prepare - Start OAuth flow
|
|
6
|
+
* - GET /api/v1/oauth/callback - OAuth callback handler
|
|
7
|
+
* - POST /api/v1/oauth/refresh - Refresh access token
|
|
8
|
+
*/
|
|
9
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
10
|
+
export declare const oauthRoutes: OpenAPIHono<import("hono").Env, {}, "/">;
|
|
11
|
+
export declare const oauthDemoRoutes: OpenAPIHono<import("hono").Env, {}, "/">;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth Routes
|
|
3
|
+
*
|
|
4
|
+
* API endpoints for MCP OAuth flow:
|
|
5
|
+
* - POST /api/v1/oauth/prepare - Start OAuth flow
|
|
6
|
+
* - GET /api/v1/oauth/callback - OAuth callback handler
|
|
7
|
+
* - POST /api/v1/oauth/refresh - Refresh access token
|
|
8
|
+
*/
|
|
9
|
+
import { createRoute, OpenAPIHono } from "@hono/zod-openapi";
|
|
10
|
+
import { createRouteResponses } from "../../shared/utils/response-util";
|
|
11
|
+
import { oauthHandler } from "./oauth-handler";
|
|
12
|
+
import { OAuthPrepareRequestSchema, OAuthPrepareResponseSchema, OAuthRefreshRequestSchema, OAuthRefreshResponseSchema, } from "./oauth-schema";
|
|
13
|
+
export const oauthRoutes = new OpenAPIHono();
|
|
14
|
+
// ============ POST /prepare ============
|
|
15
|
+
const prepareRoute = createRoute({
|
|
16
|
+
method: "post",
|
|
17
|
+
path: "/prepare",
|
|
18
|
+
tags: ["OAuth"],
|
|
19
|
+
summary: "Prepare OAuth flow",
|
|
20
|
+
description: `
|
|
21
|
+
Start the OAuth authorization flow for an MCP package.
|
|
22
|
+
|
|
23
|
+
This endpoint:
|
|
24
|
+
1. Discovers the MCP server's OAuth configuration
|
|
25
|
+
2. Registers a client with the authorization server (if DCR supported)
|
|
26
|
+
3. Generates PKCE parameters (required by MCP spec)
|
|
27
|
+
4. Returns an authorization URL for the user to complete authorization
|
|
28
|
+
|
|
29
|
+
After the user authorizes, the Registry will:
|
|
30
|
+
1. Exchange the authorization code for tokens
|
|
31
|
+
2. POST the tokens and client info to your callbackBaseUrl
|
|
32
|
+
3. Return an HTML page that closes the popup window
|
|
33
|
+
`,
|
|
34
|
+
request: {
|
|
35
|
+
body: {
|
|
36
|
+
content: {
|
|
37
|
+
"application/json": {
|
|
38
|
+
schema: OAuthPrepareRequestSchema,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
required: true,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
responses: createRouteResponses(OAuthPrepareResponseSchema, {
|
|
45
|
+
includeErrorResponses: true,
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
oauthRoutes.openapi(prepareRoute, async (c) => {
|
|
49
|
+
const body = c.req.valid("json");
|
|
50
|
+
const result = await oauthHandler.prepareOAuth(body);
|
|
51
|
+
return c.json(result, result.success ? 200 : result.code);
|
|
52
|
+
});
|
|
53
|
+
// ============ GET /callback ============
|
|
54
|
+
// Using standard Hono route for HTML response (not OpenAPI)
|
|
55
|
+
oauthRoutes.get("/callback", async (c) => {
|
|
56
|
+
const code = c.req.query("code");
|
|
57
|
+
const state = c.req.query("state");
|
|
58
|
+
const error = c.req.query("error");
|
|
59
|
+
const error_description = c.req.query("error_description");
|
|
60
|
+
const result = await oauthHandler.handleCallback({
|
|
61
|
+
code,
|
|
62
|
+
state,
|
|
63
|
+
error,
|
|
64
|
+
error_description,
|
|
65
|
+
});
|
|
66
|
+
return c.html(result.html);
|
|
67
|
+
});
|
|
68
|
+
// ============ POST /refresh ============
|
|
69
|
+
const refreshRoute = createRoute({
|
|
70
|
+
method: "post",
|
|
71
|
+
path: "/refresh",
|
|
72
|
+
tags: ["OAuth"],
|
|
73
|
+
summary: "Refresh access token",
|
|
74
|
+
description: `
|
|
75
|
+
Refresh an OAuth access token using a refresh token.
|
|
76
|
+
|
|
77
|
+
This endpoint discovers the authorization server and exchanges
|
|
78
|
+
the refresh token for a new access token.
|
|
79
|
+
`,
|
|
80
|
+
request: {
|
|
81
|
+
body: {
|
|
82
|
+
content: {
|
|
83
|
+
"application/json": {
|
|
84
|
+
schema: OAuthRefreshRequestSchema,
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: true,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
responses: createRouteResponses(OAuthRefreshResponseSchema, {
|
|
91
|
+
includeErrorResponses: true,
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
oauthRoutes.openapi(refreshRoute, async (c) => {
|
|
95
|
+
const body = c.req.valid("json");
|
|
96
|
+
const result = await oauthHandler.handleRefresh(body);
|
|
97
|
+
return c.json(result, result.success ? 200 : result.code);
|
|
98
|
+
});
|
|
99
|
+
// ============ Demo Page Route ============
|
|
100
|
+
export const oauthDemoRoutes = new OpenAPIHono();
|
|
101
|
+
oauthDemoRoutes.get("/oauth", async (c) => {
|
|
102
|
+
const { readFileSync } = await import("node:fs");
|
|
103
|
+
const { join } = await import("node:path");
|
|
104
|
+
const { getDirname } = await import("../../shared/utils/file-util");
|
|
105
|
+
const __dirname = getDirname(import.meta.url);
|
|
106
|
+
const htmlPath = join(__dirname, "demo-oauth.html");
|
|
107
|
+
try {
|
|
108
|
+
const htmlContent = readFileSync(htmlPath, "utf-8");
|
|
109
|
+
return c.html(htmlContent);
|
|
110
|
+
}
|
|
111
|
+
catch (_error) {
|
|
112
|
+
return c.text("Demo page not found", 404);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
// Demo callback endpoint (acts as a mock client)
|
|
116
|
+
oauthDemoRoutes.post("/oauth/callback", async (c) => {
|
|
117
|
+
const body = await c.req.json();
|
|
118
|
+
console.log("[OAuthDemo] Received callback data:", JSON.stringify(body, null, 2));
|
|
119
|
+
// Store in a simple in-memory map for demo purposes
|
|
120
|
+
const sessionId = body.sessionId;
|
|
121
|
+
if (sessionId) {
|
|
122
|
+
demoCallbackStore.set(sessionId, body);
|
|
123
|
+
// Auto-cleanup after 5 minutes
|
|
124
|
+
setTimeout(() => demoCallbackStore.delete(sessionId), 5 * 60 * 1000);
|
|
125
|
+
}
|
|
126
|
+
return c.json({ success: true, message: "Callback received" });
|
|
127
|
+
});
|
|
128
|
+
// Endpoint to retrieve callback data (for polling in demo)
|
|
129
|
+
oauthDemoRoutes.get("/oauth/callback/:sessionId", async (c) => {
|
|
130
|
+
const sessionId = c.req.param("sessionId");
|
|
131
|
+
const data = demoCallbackStore.get(sessionId);
|
|
132
|
+
if (data) {
|
|
133
|
+
return c.json({ success: true, data });
|
|
134
|
+
}
|
|
135
|
+
return c.json({ success: false, message: "No data found" }, 404);
|
|
136
|
+
});
|
|
137
|
+
// Simple in-memory store for demo
|
|
138
|
+
const demoCallbackStore = new Map();
|