@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,284 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildAuthorizationUrl, generatePKCE, generateSessionId, generateState, getCanonicalResourceUri, parseWWWAuthenticate, verifyPKCESupport, } from "../oauth-utils";
|
|
3
|
+
describe("oauth-utils", () => {
|
|
4
|
+
describe("generatePKCE", () => {
|
|
5
|
+
it("should generate valid PKCE parameters", () => {
|
|
6
|
+
// Act
|
|
7
|
+
const pkce = generatePKCE();
|
|
8
|
+
// Assert
|
|
9
|
+
expect(pkce).toHaveProperty("codeVerifier");
|
|
10
|
+
expect(pkce).toHaveProperty("codeChallenge");
|
|
11
|
+
expect(pkce).toHaveProperty("codeChallengeMethod");
|
|
12
|
+
expect(pkce.codeChallengeMethod).toBe("S256");
|
|
13
|
+
});
|
|
14
|
+
it("should generate code verifier with correct length (43 chars base64url)", () => {
|
|
15
|
+
// Act
|
|
16
|
+
const pkce = generatePKCE();
|
|
17
|
+
// Assert - 32 bytes base64url encoded = 43 characters
|
|
18
|
+
expect(pkce.codeVerifier.length).toBe(43);
|
|
19
|
+
});
|
|
20
|
+
it("should generate code challenge with correct length", () => {
|
|
21
|
+
// Act
|
|
22
|
+
const pkce = generatePKCE();
|
|
23
|
+
// Assert - SHA256 hash (32 bytes) base64url encoded = 43 characters
|
|
24
|
+
expect(pkce.codeChallenge.length).toBe(43);
|
|
25
|
+
});
|
|
26
|
+
it("should generate different values each time", () => {
|
|
27
|
+
// Act
|
|
28
|
+
const pkce1 = generatePKCE();
|
|
29
|
+
const pkce2 = generatePKCE();
|
|
30
|
+
// Assert
|
|
31
|
+
expect(pkce1.codeVerifier).not.toBe(pkce2.codeVerifier);
|
|
32
|
+
expect(pkce1.codeChallenge).not.toBe(pkce2.codeChallenge);
|
|
33
|
+
});
|
|
34
|
+
it("should generate base64url encoded values (no +, /, =)", () => {
|
|
35
|
+
// Act
|
|
36
|
+
const pkce = generatePKCE();
|
|
37
|
+
// Assert - base64url should not contain +, /, or =
|
|
38
|
+
expect(pkce.codeVerifier).not.toMatch(/[+/=]/);
|
|
39
|
+
expect(pkce.codeChallenge).not.toMatch(/[+/=]/);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
describe("generateState", () => {
|
|
43
|
+
it("should generate a valid UUID", () => {
|
|
44
|
+
// Act
|
|
45
|
+
const state = generateState();
|
|
46
|
+
// Assert - UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
|
47
|
+
expect(state).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
|
48
|
+
});
|
|
49
|
+
it("should generate different values each time", () => {
|
|
50
|
+
// Act
|
|
51
|
+
const state1 = generateState();
|
|
52
|
+
const state2 = generateState();
|
|
53
|
+
// Assert
|
|
54
|
+
expect(state1).not.toBe(state2);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
describe("generateSessionId", () => {
|
|
58
|
+
it("should generate a valid UUID", () => {
|
|
59
|
+
// Act
|
|
60
|
+
const sessionId = generateSessionId();
|
|
61
|
+
// Assert
|
|
62
|
+
expect(sessionId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
|
|
63
|
+
});
|
|
64
|
+
it("should generate different values each time", () => {
|
|
65
|
+
// Act
|
|
66
|
+
const id1 = generateSessionId();
|
|
67
|
+
const id2 = generateSessionId();
|
|
68
|
+
// Assert
|
|
69
|
+
expect(id1).not.toBe(id2);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
describe("parseWWWAuthenticate", () => {
|
|
73
|
+
it("should parse complete WWW-Authenticate header", () => {
|
|
74
|
+
// Arrange
|
|
75
|
+
const header = 'Bearer realm="mcp", resource_metadata="http://localhost:3001/.well-known/oauth-protected-resource", scope="read write"';
|
|
76
|
+
// Act
|
|
77
|
+
const result = parseWWWAuthenticate(header);
|
|
78
|
+
// Assert
|
|
79
|
+
expect(result).toEqual({
|
|
80
|
+
realm: "mcp",
|
|
81
|
+
resourceMetadataUrl: "http://localhost:3001/.well-known/oauth-protected-resource",
|
|
82
|
+
scope: "read write",
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
it("should parse header with only realm", () => {
|
|
86
|
+
// Arrange
|
|
87
|
+
const header = 'Bearer realm="example"';
|
|
88
|
+
// Act
|
|
89
|
+
const result = parseWWWAuthenticate(header);
|
|
90
|
+
// Assert
|
|
91
|
+
expect(result).toEqual({
|
|
92
|
+
realm: "example",
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
it("should parse header with only resource_metadata", () => {
|
|
96
|
+
// Arrange
|
|
97
|
+
const header = 'Bearer resource_metadata="http://example.com/.well-known/oauth-protected-resource"';
|
|
98
|
+
// Act
|
|
99
|
+
const result = parseWWWAuthenticate(header);
|
|
100
|
+
// Assert
|
|
101
|
+
expect(result).toEqual({
|
|
102
|
+
resourceMetadataUrl: "http://example.com/.well-known/oauth-protected-resource",
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
it("should return empty object for empty header", () => {
|
|
106
|
+
// Arrange
|
|
107
|
+
const header = "Bearer";
|
|
108
|
+
// Act
|
|
109
|
+
const result = parseWWWAuthenticate(header);
|
|
110
|
+
// Assert
|
|
111
|
+
expect(result).toEqual({});
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
describe("verifyPKCESupport", () => {
|
|
115
|
+
it("should return supported=true when S256 is in supported methods", () => {
|
|
116
|
+
// Arrange
|
|
117
|
+
const metadata = {
|
|
118
|
+
issuer: "http://localhost:3001",
|
|
119
|
+
authorization_endpoint: "http://localhost:3001/authorize",
|
|
120
|
+
token_endpoint: "http://localhost:3001/token",
|
|
121
|
+
code_challenge_methods_supported: ["S256", "plain"],
|
|
122
|
+
};
|
|
123
|
+
// Act
|
|
124
|
+
const result = verifyPKCESupport(metadata);
|
|
125
|
+
// Assert
|
|
126
|
+
expect(result).toEqual({ supported: true, advertised: true });
|
|
127
|
+
});
|
|
128
|
+
it("should return supported=false when S256 is not in supported methods", () => {
|
|
129
|
+
// Arrange
|
|
130
|
+
const metadata = {
|
|
131
|
+
issuer: "http://localhost:3001",
|
|
132
|
+
authorization_endpoint: "http://localhost:3001/authorize",
|
|
133
|
+
token_endpoint: "http://localhost:3001/token",
|
|
134
|
+
code_challenge_methods_supported: ["plain"],
|
|
135
|
+
};
|
|
136
|
+
// Act
|
|
137
|
+
const result = verifyPKCESupport(metadata);
|
|
138
|
+
// Assert
|
|
139
|
+
expect(result).toEqual({ supported: false, advertised: true });
|
|
140
|
+
});
|
|
141
|
+
it("should return supported=true, advertised=false when code_challenge_methods_supported is missing", () => {
|
|
142
|
+
// Arrange
|
|
143
|
+
const metadata = {
|
|
144
|
+
issuer: "http://localhost:3001",
|
|
145
|
+
authorization_endpoint: "http://localhost:3001/authorize",
|
|
146
|
+
token_endpoint: "http://localhost:3001/token",
|
|
147
|
+
};
|
|
148
|
+
// Act
|
|
149
|
+
const result = verifyPKCESupport(metadata);
|
|
150
|
+
// Assert
|
|
151
|
+
expect(result).toEqual({ supported: true, advertised: false });
|
|
152
|
+
});
|
|
153
|
+
it("should return supported=true, advertised=false when code_challenge_methods_supported is empty", () => {
|
|
154
|
+
// Arrange
|
|
155
|
+
const metadata = {
|
|
156
|
+
issuer: "http://localhost:3001",
|
|
157
|
+
authorization_endpoint: "http://localhost:3001/authorize",
|
|
158
|
+
token_endpoint: "http://localhost:3001/token",
|
|
159
|
+
code_challenge_methods_supported: [],
|
|
160
|
+
};
|
|
161
|
+
// Act
|
|
162
|
+
const result = verifyPKCESupport(metadata);
|
|
163
|
+
// Assert
|
|
164
|
+
expect(result).toEqual({ supported: true, advertised: false });
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
describe("buildAuthorizationUrl", () => {
|
|
168
|
+
it("should build URL with all required parameters", () => {
|
|
169
|
+
// Arrange
|
|
170
|
+
const params = {
|
|
171
|
+
authorizationEndpoint: "http://localhost:3001/authorize",
|
|
172
|
+
clientId: "test-client",
|
|
173
|
+
redirectUri: "http://localhost:3003/callback",
|
|
174
|
+
state: "random-state",
|
|
175
|
+
codeChallenge: "test-challenge",
|
|
176
|
+
codeChallengeMethod: "S256",
|
|
177
|
+
};
|
|
178
|
+
// Act
|
|
179
|
+
const result = buildAuthorizationUrl(params);
|
|
180
|
+
// Assert
|
|
181
|
+
const url = new URL(result);
|
|
182
|
+
expect(url.origin).toBe("http://localhost:3001");
|
|
183
|
+
expect(url.pathname).toBe("/authorize");
|
|
184
|
+
expect(url.searchParams.get("response_type")).toBe("code");
|
|
185
|
+
expect(url.searchParams.get("client_id")).toBe("test-client");
|
|
186
|
+
expect(url.searchParams.get("redirect_uri")).toBe("http://localhost:3003/callback");
|
|
187
|
+
expect(url.searchParams.get("state")).toBe("random-state");
|
|
188
|
+
expect(url.searchParams.get("code_challenge")).toBe("test-challenge");
|
|
189
|
+
expect(url.searchParams.get("code_challenge_method")).toBe("S256");
|
|
190
|
+
});
|
|
191
|
+
it("should include scope when provided", () => {
|
|
192
|
+
// Arrange
|
|
193
|
+
const params = {
|
|
194
|
+
authorizationEndpoint: "http://localhost:3001/authorize",
|
|
195
|
+
clientId: "test-client",
|
|
196
|
+
redirectUri: "http://localhost:3003/callback",
|
|
197
|
+
state: "random-state",
|
|
198
|
+
codeChallenge: "test-challenge",
|
|
199
|
+
codeChallengeMethod: "S256",
|
|
200
|
+
scope: "read write",
|
|
201
|
+
};
|
|
202
|
+
// Act
|
|
203
|
+
const result = buildAuthorizationUrl(params);
|
|
204
|
+
// Assert
|
|
205
|
+
const url = new URL(result);
|
|
206
|
+
expect(url.searchParams.get("scope")).toBe("read write");
|
|
207
|
+
});
|
|
208
|
+
it("should include resource when provided", () => {
|
|
209
|
+
// Arrange
|
|
210
|
+
const params = {
|
|
211
|
+
authorizationEndpoint: "http://localhost:3001/authorize",
|
|
212
|
+
clientId: "test-client",
|
|
213
|
+
redirectUri: "http://localhost:3003/callback",
|
|
214
|
+
state: "random-state",
|
|
215
|
+
codeChallenge: "test-challenge",
|
|
216
|
+
codeChallengeMethod: "S256",
|
|
217
|
+
resource: "http://localhost:3001/mcp",
|
|
218
|
+
};
|
|
219
|
+
// Act
|
|
220
|
+
const result = buildAuthorizationUrl(params);
|
|
221
|
+
// Assert
|
|
222
|
+
const url = new URL(result);
|
|
223
|
+
expect(url.searchParams.get("resource")).toBe("http://localhost:3001/mcp");
|
|
224
|
+
});
|
|
225
|
+
it("should not include scope when not provided", () => {
|
|
226
|
+
// Arrange
|
|
227
|
+
const params = {
|
|
228
|
+
authorizationEndpoint: "http://localhost:3001/authorize",
|
|
229
|
+
clientId: "test-client",
|
|
230
|
+
redirectUri: "http://localhost:3003/callback",
|
|
231
|
+
state: "random-state",
|
|
232
|
+
codeChallenge: "test-challenge",
|
|
233
|
+
codeChallengeMethod: "S256",
|
|
234
|
+
};
|
|
235
|
+
// Act
|
|
236
|
+
const result = buildAuthorizationUrl(params);
|
|
237
|
+
// Assert
|
|
238
|
+
const url = new URL(result);
|
|
239
|
+
expect(url.searchParams.has("scope")).toBe(false);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
describe("getCanonicalResourceUri", () => {
|
|
243
|
+
it("should return URL without trailing slash", () => {
|
|
244
|
+
// Arrange
|
|
245
|
+
const mcpServerUrl = "http://localhost:3001/mcp/";
|
|
246
|
+
// Act
|
|
247
|
+
const result = getCanonicalResourceUri(mcpServerUrl);
|
|
248
|
+
// Assert
|
|
249
|
+
expect(result).toBe("http://localhost:3001/mcp");
|
|
250
|
+
});
|
|
251
|
+
it("should keep URL without trailing slash unchanged", () => {
|
|
252
|
+
// Arrange
|
|
253
|
+
const mcpServerUrl = "http://localhost:3001/mcp";
|
|
254
|
+
// Act
|
|
255
|
+
const result = getCanonicalResourceUri(mcpServerUrl);
|
|
256
|
+
// Assert
|
|
257
|
+
expect(result).toBe("http://localhost:3001/mcp");
|
|
258
|
+
});
|
|
259
|
+
it("should handle root URL correctly", () => {
|
|
260
|
+
// Arrange
|
|
261
|
+
const mcpServerUrl = "http://localhost:3001/";
|
|
262
|
+
// Act
|
|
263
|
+
const result = getCanonicalResourceUri(mcpServerUrl);
|
|
264
|
+
// Assert
|
|
265
|
+
expect(result).toBe("http://localhost:3001/");
|
|
266
|
+
});
|
|
267
|
+
it("should strip query parameters", () => {
|
|
268
|
+
// Arrange
|
|
269
|
+
const mcpServerUrl = "http://localhost:3001/mcp?param=value";
|
|
270
|
+
// Act
|
|
271
|
+
const result = getCanonicalResourceUri(mcpServerUrl);
|
|
272
|
+
// Assert
|
|
273
|
+
expect(result).toBe("http://localhost:3001/mcp");
|
|
274
|
+
});
|
|
275
|
+
it("should strip fragment", () => {
|
|
276
|
+
// Arrange
|
|
277
|
+
const mcpServerUrl = "http://localhost:3001/mcp#section";
|
|
278
|
+
// Act
|
|
279
|
+
const result = getCanonicalResourceUri(mcpServerUrl);
|
|
280
|
+
// Assert
|
|
281
|
+
expect(result).toBe("http://localhost:3001/mcp");
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
});
|
|
@@ -0,0 +1,65 @@
|
|
|
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 type { OAuthPrepareRequest, OAuthRefreshRequest } from "./oauth-types";
|
|
10
|
+
/**
|
|
11
|
+
* Prepare OAuth flow
|
|
12
|
+
*
|
|
13
|
+
* 1. Get MCP Server URL from package config
|
|
14
|
+
* 2. Discover Protected Resource Metadata
|
|
15
|
+
* 3. Discover Authorization Server Metadata
|
|
16
|
+
* 4. Verify PKCE support
|
|
17
|
+
* 5. Register client (Dynamic Client Registration)
|
|
18
|
+
* 6. Generate PKCE + state + session
|
|
19
|
+
* 7. Build authorization URL
|
|
20
|
+
*/
|
|
21
|
+
export declare function prepareOAuth(request: OAuthPrepareRequest): Promise<{
|
|
22
|
+
success: boolean;
|
|
23
|
+
code: number;
|
|
24
|
+
message: string;
|
|
25
|
+
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Handle OAuth callback
|
|
28
|
+
*
|
|
29
|
+
* 1. Find session by state
|
|
30
|
+
* 2. Exchange code for tokens
|
|
31
|
+
* 3. POST tokens + clientInfo to callbackBaseUrl
|
|
32
|
+
* 4. Delete session
|
|
33
|
+
* 5. Return HTML to close popup
|
|
34
|
+
*/
|
|
35
|
+
export declare function handleCallback(params: {
|
|
36
|
+
code?: string;
|
|
37
|
+
state?: string;
|
|
38
|
+
error?: string;
|
|
39
|
+
error_description?: string;
|
|
40
|
+
}): Promise<{
|
|
41
|
+
success: boolean;
|
|
42
|
+
error: string;
|
|
43
|
+
error_description: string | undefined;
|
|
44
|
+
html: string;
|
|
45
|
+
sessionId?: undefined;
|
|
46
|
+
} | {
|
|
47
|
+
success: boolean;
|
|
48
|
+
sessionId: string;
|
|
49
|
+
html: string;
|
|
50
|
+
error?: undefined;
|
|
51
|
+
error_description?: undefined;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Refresh access token
|
|
55
|
+
*/
|
|
56
|
+
export declare function handleRefresh(request: OAuthRefreshRequest): Promise<{
|
|
57
|
+
success: boolean;
|
|
58
|
+
code: number;
|
|
59
|
+
message: string;
|
|
60
|
+
}>;
|
|
61
|
+
export declare const oauthHandler: {
|
|
62
|
+
prepareOAuth: typeof prepareOAuth;
|
|
63
|
+
handleCallback: typeof handleCallback;
|
|
64
|
+
handleRefresh: typeof handleRefresh;
|
|
65
|
+
};
|