@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.
Files changed (41) hide show
  1. package/README.md +11 -10
  2. package/dist/api/index.js +4 -0
  3. package/dist/domains/executor/executor-types.d.ts +3 -1
  4. package/dist/domains/executor/local-executor.d.ts +1 -1
  5. package/dist/domains/executor/local-executor.js +3 -3
  6. package/dist/domains/executor/sandbox-executor.d.ts +1 -1
  7. package/dist/domains/executor/sandbox-executor.js +3 -3
  8. package/dist/domains/oauth/__tests__/oauth-session.test.d.ts +1 -0
  9. package/dist/domains/oauth/__tests__/oauth-session.test.js +272 -0
  10. package/dist/domains/oauth/__tests__/oauth-utils.test.d.ts +1 -0
  11. package/dist/domains/oauth/__tests__/oauth-utils.test.js +284 -0
  12. package/dist/domains/oauth/index.d.ts +9 -0
  13. package/dist/domains/oauth/index.js +9 -0
  14. package/dist/domains/oauth/oauth-handler.d.ts +65 -0
  15. package/dist/domains/oauth/oauth-handler.js +355 -0
  16. package/dist/domains/oauth/oauth-route.d.ts +11 -0
  17. package/dist/domains/oauth/oauth-route.js +138 -0
  18. package/dist/domains/oauth/oauth-schema.d.ts +257 -0
  19. package/dist/domains/oauth/oauth-schema.js +119 -0
  20. package/dist/domains/oauth/oauth-session.d.ts +54 -0
  21. package/dist/domains/oauth/oauth-session.js +116 -0
  22. package/dist/domains/oauth/oauth-types.d.ts +148 -0
  23. package/dist/domains/oauth/oauth-types.js +9 -0
  24. package/dist/domains/oauth/oauth-utils.d.ts +99 -0
  25. package/dist/domains/oauth/oauth-utils.js +267 -0
  26. package/dist/domains/package/package-handler.d.ts +2 -2
  27. package/dist/domains/package/package-handler.js +4 -4
  28. package/dist/domains/package/package-route.js +5 -5
  29. package/dist/domains/package/package-schema.d.ts +81 -4
  30. package/dist/domains/package/package-schema.js +17 -0
  31. package/dist/domains/package/package-so.d.ts +11 -3
  32. package/dist/domains/package/package-so.js +4 -3
  33. package/dist/shared/schemas/common-schema.d.ts +92 -4
  34. package/dist/shared/schemas/common-schema.js +13 -0
  35. package/dist/shared/scripts-helpers/index.d.ts +9 -1
  36. package/dist/shared/utils/mcp-client-util.d.ts +3 -3
  37. package/dist/shared/utils/mcp-client-util.js +22 -1
  38. package/indexes/categories-list.json +1 -0
  39. package/indexes/packages-list.json +15 -0
  40. package/package.json +2 -1
  41. 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();