@claude-flow/mcp 3.0.0-alpha.1

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 (92) hide show
  1. package/.agentic-flow/intelligence.json +16 -0
  2. package/README.md +428 -0
  3. package/__tests__/integration.test.ts +449 -0
  4. package/__tests__/mcp.test.ts +641 -0
  5. package/dist/connection-pool.d.ts +36 -0
  6. package/dist/connection-pool.d.ts.map +1 -0
  7. package/dist/connection-pool.js +273 -0
  8. package/dist/connection-pool.js.map +1 -0
  9. package/dist/index.d.ts +75 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +85 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/oauth.d.ts +146 -0
  14. package/dist/oauth.d.ts.map +1 -0
  15. package/dist/oauth.js +318 -0
  16. package/dist/oauth.js.map +1 -0
  17. package/dist/prompt-registry.d.ts +90 -0
  18. package/dist/prompt-registry.d.ts.map +1 -0
  19. package/dist/prompt-registry.js +209 -0
  20. package/dist/prompt-registry.js.map +1 -0
  21. package/dist/rate-limiter.d.ts +86 -0
  22. package/dist/rate-limiter.d.ts.map +1 -0
  23. package/dist/rate-limiter.js +197 -0
  24. package/dist/rate-limiter.js.map +1 -0
  25. package/dist/resource-registry.d.ts +144 -0
  26. package/dist/resource-registry.d.ts.map +1 -0
  27. package/dist/resource-registry.js +405 -0
  28. package/dist/resource-registry.js.map +1 -0
  29. package/dist/sampling.d.ts +102 -0
  30. package/dist/sampling.d.ts.map +1 -0
  31. package/dist/sampling.js +268 -0
  32. package/dist/sampling.js.map +1 -0
  33. package/dist/schema-validator.d.ts +30 -0
  34. package/dist/schema-validator.d.ts.map +1 -0
  35. package/dist/schema-validator.js +182 -0
  36. package/dist/schema-validator.js.map +1 -0
  37. package/dist/server.d.ts +122 -0
  38. package/dist/server.d.ts.map +1 -0
  39. package/dist/server.js +829 -0
  40. package/dist/server.js.map +1 -0
  41. package/dist/session-manager.d.ts +55 -0
  42. package/dist/session-manager.d.ts.map +1 -0
  43. package/dist/session-manager.js +252 -0
  44. package/dist/session-manager.js.map +1 -0
  45. package/dist/task-manager.d.ts +81 -0
  46. package/dist/task-manager.d.ts.map +1 -0
  47. package/dist/task-manager.js +337 -0
  48. package/dist/task-manager.js.map +1 -0
  49. package/dist/tool-registry.d.ts +88 -0
  50. package/dist/tool-registry.d.ts.map +1 -0
  51. package/dist/tool-registry.js +353 -0
  52. package/dist/tool-registry.js.map +1 -0
  53. package/dist/transport/http.d.ts +55 -0
  54. package/dist/transport/http.d.ts.map +1 -0
  55. package/dist/transport/http.js +446 -0
  56. package/dist/transport/http.js.map +1 -0
  57. package/dist/transport/index.d.ts +50 -0
  58. package/dist/transport/index.d.ts.map +1 -0
  59. package/dist/transport/index.js +181 -0
  60. package/dist/transport/index.js.map +1 -0
  61. package/dist/transport/stdio.d.ts +43 -0
  62. package/dist/transport/stdio.d.ts.map +1 -0
  63. package/dist/transport/stdio.js +194 -0
  64. package/dist/transport/stdio.js.map +1 -0
  65. package/dist/transport/websocket.d.ts +65 -0
  66. package/dist/transport/websocket.d.ts.map +1 -0
  67. package/dist/transport/websocket.js +314 -0
  68. package/dist/transport/websocket.js.map +1 -0
  69. package/dist/types.d.ts +473 -0
  70. package/dist/types.d.ts.map +1 -0
  71. package/dist/types.js +40 -0
  72. package/dist/types.js.map +1 -0
  73. package/package.json +42 -0
  74. package/src/connection-pool.ts +344 -0
  75. package/src/index.ts +253 -0
  76. package/src/oauth.ts +447 -0
  77. package/src/prompt-registry.ts +296 -0
  78. package/src/rate-limiter.ts +266 -0
  79. package/src/resource-registry.ts +530 -0
  80. package/src/sampling.ts +363 -0
  81. package/src/schema-validator.ts +213 -0
  82. package/src/server.ts +1134 -0
  83. package/src/session-manager.ts +339 -0
  84. package/src/task-manager.ts +427 -0
  85. package/src/tool-registry.ts +475 -0
  86. package/src/transport/http.ts +532 -0
  87. package/src/transport/index.ts +233 -0
  88. package/src/transport/stdio.ts +252 -0
  89. package/src/transport/websocket.ts +396 -0
  90. package/src/types.ts +664 -0
  91. package/tsconfig.json +20 -0
  92. package/vitest.config.ts +13 -0
package/src/oauth.ts ADDED
@@ -0,0 +1,447 @@
1
+ /**
2
+ * @claude-flow/mcp - OAuth 2.1 Authentication
3
+ *
4
+ * MCP 2025-11-25 compliant OAuth 2.1 with PKCE
5
+ */
6
+
7
+ import { EventEmitter } from 'events';
8
+ import * as crypto from 'crypto';
9
+ import type { ILogger } from './types.js';
10
+
11
+ /**
12
+ * OAuth 2.1 configuration
13
+ */
14
+ export interface OAuthConfig {
15
+ /** Client ID */
16
+ clientId: string;
17
+ /** Client secret (for confidential clients) */
18
+ clientSecret?: string;
19
+ /** Authorization endpoint */
20
+ authorizationEndpoint: string;
21
+ /** Token endpoint */
22
+ tokenEndpoint: string;
23
+ /** Redirect URI */
24
+ redirectUri: string;
25
+ /** Scopes to request */
26
+ scopes?: string[];
27
+ /** Token storage adapter */
28
+ tokenStorage?: TokenStorage;
29
+ /** Enable PKCE (default: true) */
30
+ usePKCE?: boolean;
31
+ /** State parameter generator */
32
+ stateGenerator?: () => string;
33
+ }
34
+
35
+ /**
36
+ * OAuth tokens
37
+ */
38
+ export interface OAuthTokens {
39
+ accessToken: string;
40
+ refreshToken?: string;
41
+ tokenType: string;
42
+ expiresIn?: number;
43
+ expiresAt?: number;
44
+ scope?: string;
45
+ }
46
+
47
+ /**
48
+ * Token storage interface
49
+ */
50
+ export interface TokenStorage {
51
+ save(key: string, tokens: OAuthTokens): Promise<void>;
52
+ load(key: string): Promise<OAuthTokens | null>;
53
+ delete(key: string): Promise<void>;
54
+ }
55
+
56
+ /**
57
+ * Authorization request
58
+ */
59
+ export interface AuthorizationRequest {
60
+ url: string;
61
+ state: string;
62
+ codeVerifier?: string;
63
+ }
64
+
65
+ /**
66
+ * Token response from OAuth server
67
+ */
68
+ interface TokenResponse {
69
+ access_token: string;
70
+ refresh_token?: string;
71
+ token_type: string;
72
+ expires_in?: number;
73
+ scope?: string;
74
+ }
75
+
76
+ /**
77
+ * In-memory token storage (for development)
78
+ */
79
+ export class InMemoryTokenStorage implements TokenStorage {
80
+ private tokens: Map<string, OAuthTokens> = new Map();
81
+
82
+ async save(key: string, tokens: OAuthTokens): Promise<void> {
83
+ this.tokens.set(key, tokens);
84
+ }
85
+
86
+ async load(key: string): Promise<OAuthTokens | null> {
87
+ return this.tokens.get(key) || null;
88
+ }
89
+
90
+ async delete(key: string): Promise<void> {
91
+ this.tokens.delete(key);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * OAuth 2.1 Manager
97
+ */
98
+ export class OAuthManager extends EventEmitter {
99
+ private readonly config: OAuthConfig;
100
+ private readonly tokenStorage: TokenStorage;
101
+ private pendingRequests: Map<string, { codeVerifier?: string; timestamp: number }> = new Map();
102
+ private cleanupTimer?: NodeJS.Timeout;
103
+
104
+ constructor(
105
+ private readonly logger: ILogger,
106
+ config: OAuthConfig
107
+ ) {
108
+ super();
109
+ this.config = {
110
+ usePKCE: true,
111
+ scopes: [],
112
+ stateGenerator: () => this.generateRandomString(32),
113
+ ...config,
114
+ };
115
+ this.tokenStorage = config.tokenStorage || new InMemoryTokenStorage();
116
+ this.startCleanup();
117
+ }
118
+
119
+ /**
120
+ * Generate authorization URL for OAuth flow
121
+ */
122
+ createAuthorizationRequest(): AuthorizationRequest {
123
+ const state = this.config.stateGenerator!();
124
+ let codeVerifier: string | undefined;
125
+ let codeChallenge: string | undefined;
126
+
127
+ if (this.config.usePKCE) {
128
+ codeVerifier = this.generateCodeVerifier();
129
+ codeChallenge = this.generateCodeChallenge(codeVerifier);
130
+ }
131
+
132
+ const params = new URLSearchParams({
133
+ response_type: 'code',
134
+ client_id: this.config.clientId,
135
+ redirect_uri: this.config.redirectUri,
136
+ state,
137
+ });
138
+
139
+ if (this.config.scopes && this.config.scopes.length > 0) {
140
+ params.set('scope', this.config.scopes.join(' '));
141
+ }
142
+
143
+ if (codeChallenge) {
144
+ params.set('code_challenge', codeChallenge);
145
+ params.set('code_challenge_method', 'S256');
146
+ }
147
+
148
+ // Store pending request for validation
149
+ this.pendingRequests.set(state, {
150
+ codeVerifier,
151
+ timestamp: Date.now(),
152
+ });
153
+
154
+ const url = `${this.config.authorizationEndpoint}?${params.toString()}`;
155
+
156
+ this.logger.debug('Created authorization request', { state, usePKCE: !!codeVerifier });
157
+ this.emit('authorization:created', { state });
158
+
159
+ return { url, state, codeVerifier };
160
+ }
161
+
162
+ /**
163
+ * Exchange authorization code for tokens
164
+ */
165
+ async exchangeCode(code: string, state: string): Promise<OAuthTokens> {
166
+ const pending = this.pendingRequests.get(state);
167
+ if (!pending) {
168
+ throw new Error('Invalid or expired state parameter');
169
+ }
170
+
171
+ this.pendingRequests.delete(state);
172
+
173
+ const params = new URLSearchParams({
174
+ grant_type: 'authorization_code',
175
+ code,
176
+ redirect_uri: this.config.redirectUri,
177
+ client_id: this.config.clientId,
178
+ });
179
+
180
+ if (this.config.clientSecret) {
181
+ params.set('client_secret', this.config.clientSecret);
182
+ }
183
+
184
+ if (pending.codeVerifier) {
185
+ params.set('code_verifier', pending.codeVerifier);
186
+ }
187
+
188
+ const response = await fetch(this.config.tokenEndpoint, {
189
+ method: 'POST',
190
+ headers: {
191
+ 'Content-Type': 'application/x-www-form-urlencoded',
192
+ },
193
+ body: params.toString(),
194
+ });
195
+
196
+ if (!response.ok) {
197
+ const error = await response.text();
198
+ this.logger.error('Token exchange failed', { status: response.status, error });
199
+ throw new Error(`Token exchange failed: ${response.status}`);
200
+ }
201
+
202
+ const data = (await response.json()) as TokenResponse;
203
+ const tokens = this.parseTokenResponse(data);
204
+
205
+ await this.tokenStorage.save('default', tokens);
206
+ this.logger.info('Token exchange successful');
207
+ this.emit('tokens:received', { expiresIn: tokens.expiresIn });
208
+
209
+ return tokens;
210
+ }
211
+
212
+ /**
213
+ * Refresh access token using refresh token
214
+ */
215
+ async refreshTokens(storageKey: string = 'default'): Promise<OAuthTokens> {
216
+ const existing = await this.tokenStorage.load(storageKey);
217
+ if (!existing?.refreshToken) {
218
+ throw new Error('No refresh token available');
219
+ }
220
+
221
+ const params = new URLSearchParams({
222
+ grant_type: 'refresh_token',
223
+ refresh_token: existing.refreshToken,
224
+ client_id: this.config.clientId,
225
+ });
226
+
227
+ if (this.config.clientSecret) {
228
+ params.set('client_secret', this.config.clientSecret);
229
+ }
230
+
231
+ const response = await fetch(this.config.tokenEndpoint, {
232
+ method: 'POST',
233
+ headers: {
234
+ 'Content-Type': 'application/x-www-form-urlencoded',
235
+ },
236
+ body: params.toString(),
237
+ });
238
+
239
+ if (!response.ok) {
240
+ const error = await response.text();
241
+ this.logger.error('Token refresh failed', { status: response.status, error });
242
+ // Clear invalid tokens
243
+ await this.tokenStorage.delete(storageKey);
244
+ throw new Error(`Token refresh failed: ${response.status}`);
245
+ }
246
+
247
+ const data = (await response.json()) as TokenResponse;
248
+ const tokens = this.parseTokenResponse(data);
249
+
250
+ // Preserve refresh token if not returned in response
251
+ if (!tokens.refreshToken && existing.refreshToken) {
252
+ tokens.refreshToken = existing.refreshToken;
253
+ }
254
+
255
+ await this.tokenStorage.save(storageKey, tokens);
256
+ this.logger.info('Token refresh successful');
257
+ this.emit('tokens:refreshed', { expiresIn: tokens.expiresIn });
258
+
259
+ return tokens;
260
+ }
261
+
262
+ /**
263
+ * Get valid access token (auto-refresh if expired)
264
+ */
265
+ async getAccessToken(storageKey: string = 'default'): Promise<string | null> {
266
+ const tokens = await this.tokenStorage.load(storageKey);
267
+ if (!tokens) {
268
+ return null;
269
+ }
270
+
271
+ // Check if token is expired (with 60 second buffer)
272
+ if (tokens.expiresAt && Date.now() >= tokens.expiresAt - 60000) {
273
+ if (tokens.refreshToken) {
274
+ try {
275
+ const refreshed = await this.refreshTokens(storageKey);
276
+ return refreshed.accessToken;
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+ return null;
282
+ }
283
+
284
+ return tokens.accessToken;
285
+ }
286
+
287
+ /**
288
+ * Revoke tokens
289
+ */
290
+ async revokeTokens(storageKey: string = 'default'): Promise<void> {
291
+ await this.tokenStorage.delete(storageKey);
292
+ this.logger.info('Tokens revoked');
293
+ this.emit('tokens:revoked', { storageKey });
294
+ }
295
+
296
+ /**
297
+ * Check if authenticated
298
+ */
299
+ async isAuthenticated(storageKey: string = 'default'): Promise<boolean> {
300
+ const token = await this.getAccessToken(storageKey);
301
+ return token !== null;
302
+ }
303
+
304
+ /**
305
+ * Destroy manager and cleanup
306
+ */
307
+ destroy(): void {
308
+ if (this.cleanupTimer) {
309
+ clearInterval(this.cleanupTimer);
310
+ this.cleanupTimer = undefined;
311
+ }
312
+ this.pendingRequests.clear();
313
+ this.removeAllListeners();
314
+ }
315
+
316
+ /**
317
+ * Parse token response
318
+ */
319
+ private parseTokenResponse(data: TokenResponse): OAuthTokens {
320
+ return {
321
+ accessToken: data.access_token,
322
+ refreshToken: data.refresh_token,
323
+ tokenType: data.token_type,
324
+ expiresIn: data.expires_in,
325
+ expiresAt: data.expires_in ? Date.now() + data.expires_in * 1000 : undefined,
326
+ scope: data.scope,
327
+ };
328
+ }
329
+
330
+ /**
331
+ * Generate PKCE code verifier
332
+ */
333
+ private generateCodeVerifier(): string {
334
+ return this.generateRandomString(64);
335
+ }
336
+
337
+ /**
338
+ * Generate PKCE code challenge (S256)
339
+ */
340
+ private generateCodeChallenge(verifier: string): string {
341
+ const hash = crypto.createHash('sha256').update(verifier).digest();
342
+ return this.base64UrlEncode(hash);
343
+ }
344
+
345
+ /**
346
+ * Generate random string
347
+ */
348
+ private generateRandomString(length: number): string {
349
+ const bytes = crypto.randomBytes(length);
350
+ return this.base64UrlEncode(bytes).substring(0, length);
351
+ }
352
+
353
+ /**
354
+ * Base64 URL encode
355
+ */
356
+ private base64UrlEncode(buffer: Buffer): string {
357
+ return buffer.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
358
+ }
359
+
360
+ /**
361
+ * Start cleanup of expired pending requests
362
+ */
363
+ private startCleanup(): void {
364
+ this.cleanupTimer = setInterval(() => {
365
+ const now = Date.now();
366
+ const expireTime = 10 * 60 * 1000; // 10 minutes
367
+
368
+ for (const [state, request] of this.pendingRequests) {
369
+ if (now - request.timestamp > expireTime) {
370
+ this.pendingRequests.delete(state);
371
+ this.logger.debug('Expired pending OAuth request', { state });
372
+ }
373
+ }
374
+ }, 60000);
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Create OAuth manager
380
+ */
381
+ export function createOAuthManager(logger: ILogger, config: OAuthConfig): OAuthManager {
382
+ return new OAuthManager(logger, config);
383
+ }
384
+
385
+ /**
386
+ * OAuth middleware for Express/Connect
387
+ */
388
+ export function oauthMiddleware(oauthManager: OAuthManager, storageKey: string = 'default') {
389
+ return async (req: any, res: any, next: () => void) => {
390
+ const token = await oauthManager.getAccessToken(storageKey);
391
+
392
+ if (!token) {
393
+ res.status(401).json({
394
+ jsonrpc: '2.0',
395
+ id: null,
396
+ error: {
397
+ code: -32000,
398
+ message: 'Unauthorized - OAuth authentication required',
399
+ },
400
+ });
401
+ return;
402
+ }
403
+
404
+ req.oauthToken = token;
405
+ next();
406
+ };
407
+ }
408
+
409
+ /**
410
+ * Create GitHub OAuth provider config
411
+ */
412
+ export function createGitHubOAuthConfig(
413
+ clientId: string,
414
+ clientSecret: string,
415
+ redirectUri: string,
416
+ scopes: string[] = ['read:user']
417
+ ): OAuthConfig {
418
+ return {
419
+ clientId,
420
+ clientSecret,
421
+ redirectUri,
422
+ scopes,
423
+ authorizationEndpoint: 'https://github.com/login/oauth/authorize',
424
+ tokenEndpoint: 'https://github.com/login/oauth/access_token',
425
+ usePKCE: false, // GitHub doesn't support PKCE for OAuth apps
426
+ };
427
+ }
428
+
429
+ /**
430
+ * Create Google OAuth provider config
431
+ */
432
+ export function createGoogleOAuthConfig(
433
+ clientId: string,
434
+ clientSecret: string,
435
+ redirectUri: string,
436
+ scopes: string[] = ['openid', 'profile', 'email']
437
+ ): OAuthConfig {
438
+ return {
439
+ clientId,
440
+ clientSecret,
441
+ redirectUri,
442
+ scopes,
443
+ authorizationEndpoint: 'https://accounts.google.com/o/oauth2/v2/auth',
444
+ tokenEndpoint: 'https://oauth2.googleapis.com/token',
445
+ usePKCE: true,
446
+ };
447
+ }