@juspay/neurolink 8.28.0 → 8.30.0
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/CHANGELOG.md +12 -0
- package/README.md +23 -2
- package/dist/adapters/video/vertexVideoHandler.d.ts +12 -2
- package/dist/adapters/video/vertexVideoHandler.js +12 -2
- package/dist/core/baseProvider.d.ts +19 -0
- package/dist/core/baseProvider.js +174 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +7 -1
- package/dist/lib/adapters/video/vertexVideoHandler.d.ts +12 -2
- package/dist/lib/adapters/video/vertexVideoHandler.js +12 -2
- package/dist/lib/core/baseProvider.d.ts +19 -0
- package/dist/lib/core/baseProvider.js +174 -0
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.js +7 -1
- package/dist/lib/mcp/auth/index.d.ts +6 -0
- package/dist/lib/mcp/auth/index.js +12 -0
- package/dist/lib/mcp/auth/oauthClientProvider.d.ts +93 -0
- package/dist/lib/mcp/auth/oauthClientProvider.js +326 -0
- package/dist/lib/mcp/auth/tokenStorage.d.ts +56 -0
- package/dist/lib/mcp/auth/tokenStorage.js +135 -0
- package/dist/lib/mcp/externalServerManager.d.ts +5 -1
- package/dist/lib/mcp/externalServerManager.js +84 -22
- package/dist/lib/mcp/httpRateLimiter.d.ts +152 -0
- package/dist/lib/mcp/httpRateLimiter.js +365 -0
- package/dist/lib/mcp/httpRetryHandler.d.ts +62 -0
- package/dist/lib/mcp/httpRetryHandler.js +154 -0
- package/dist/lib/mcp/index.d.ts +5 -0
- package/dist/lib/mcp/index.js +8 -0
- package/dist/lib/mcp/mcpClientFactory.d.ts +25 -2
- package/dist/lib/mcp/mcpClientFactory.js +206 -10
- package/dist/lib/mcp/toolRegistry.d.ts +1 -2
- package/dist/lib/mcp/toolRegistry.js +1 -5
- package/dist/lib/neurolink.js +3 -0
- package/dist/lib/providers/amazonBedrock.js +4 -1
- package/dist/lib/providers/ollama.js +4 -1
- package/dist/lib/sdk/toolRegistration.d.ts +3 -25
- package/dist/lib/types/cli.d.ts +42 -42
- package/dist/lib/types/externalMcp.d.ts +55 -3
- package/dist/lib/types/externalMcp.js +0 -1
- package/dist/lib/types/generateTypes.d.ts +37 -0
- package/dist/lib/types/hitlTypes.d.ts +38 -0
- package/dist/lib/types/index.d.ts +6 -8
- package/dist/lib/types/index.js +4 -4
- package/dist/lib/types/mcpTypes.d.ts +235 -27
- package/dist/lib/types/providers.d.ts +16 -16
- package/dist/lib/types/sdkTypes.d.ts +2 -2
- package/dist/lib/types/tools.d.ts +42 -3
- package/dist/lib/types/utilities.d.ts +19 -0
- package/dist/mcp/auth/index.d.ts +6 -0
- package/dist/mcp/auth/index.js +11 -0
- package/dist/mcp/auth/oauthClientProvider.d.ts +93 -0
- package/dist/mcp/auth/oauthClientProvider.js +325 -0
- package/dist/mcp/auth/tokenStorage.d.ts +56 -0
- package/dist/mcp/auth/tokenStorage.js +134 -0
- package/dist/mcp/externalServerManager.d.ts +5 -1
- package/dist/mcp/externalServerManager.js +84 -22
- package/dist/mcp/httpRateLimiter.d.ts +152 -0
- package/dist/mcp/httpRateLimiter.js +364 -0
- package/dist/mcp/httpRetryHandler.d.ts +62 -0
- package/dist/mcp/httpRetryHandler.js +153 -0
- package/dist/mcp/index.d.ts +5 -0
- package/dist/mcp/index.js +8 -0
- package/dist/mcp/mcpClientFactory.d.ts +25 -2
- package/dist/mcp/mcpClientFactory.js +206 -10
- package/dist/mcp/toolRegistry.d.ts +1 -2
- package/dist/mcp/toolRegistry.js +1 -5
- package/dist/neurolink.js +3 -0
- package/dist/providers/amazonBedrock.js +4 -1
- package/dist/providers/ollama.js +4 -1
- package/dist/sdk/toolRegistration.d.ts +3 -25
- package/dist/types/cli.d.ts +42 -42
- package/dist/types/externalMcp.d.ts +55 -3
- package/dist/types/externalMcp.js +0 -1
- package/dist/types/generateTypes.d.ts +37 -0
- package/dist/types/hitlTypes.d.ts +38 -0
- package/dist/types/index.d.ts +6 -8
- package/dist/types/index.js +4 -4
- package/dist/types/mcpTypes.d.ts +235 -27
- package/dist/types/providers.d.ts +16 -16
- package/dist/types/sdkTypes.d.ts +2 -2
- package/dist/types/tools.d.ts +42 -3
- package/dist/types/utilities.d.ts +19 -0
- package/package.json +2 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 Client Provider for MCP HTTP Transport
|
|
3
|
+
* Implements OAuth 2.1 authentication with PKCE support
|
|
4
|
+
*/
|
|
5
|
+
import type { OAuthTokens, TokenStorage, MCPOAuthConfig, OAuthClientInformation, AuthorizationUrlResult, TokenExchangeRequest } from "../../types/mcpTypes.js";
|
|
6
|
+
/**
|
|
7
|
+
* NeuroLink OAuth Provider for MCP HTTP Transport
|
|
8
|
+
* Handles OAuth 2.1 authentication flow with optional PKCE support
|
|
9
|
+
*/
|
|
10
|
+
export declare class NeuroLinkOAuthProvider {
|
|
11
|
+
private config;
|
|
12
|
+
private storage;
|
|
13
|
+
private pendingChallenges;
|
|
14
|
+
private pendingStates;
|
|
15
|
+
constructor(config: MCPOAuthConfig, storage?: TokenStorage);
|
|
16
|
+
/**
|
|
17
|
+
* Get stored tokens for a server
|
|
18
|
+
* Returns null if tokens are not available or expired (without refresh token)
|
|
19
|
+
*/
|
|
20
|
+
tokens(serverId: string): Promise<OAuthTokens | null>;
|
|
21
|
+
/**
|
|
22
|
+
* Save tokens for a server
|
|
23
|
+
*/
|
|
24
|
+
saveTokens(serverId: string, tokens: OAuthTokens): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* Delete tokens for a server
|
|
27
|
+
*/
|
|
28
|
+
deleteTokens(serverId: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Get client information for MCP SDK
|
|
31
|
+
*/
|
|
32
|
+
clientInformation(): OAuthClientInformation;
|
|
33
|
+
/**
|
|
34
|
+
* Generate authorization URL for OAuth flow
|
|
35
|
+
* Returns the URL to redirect the user to for authorization
|
|
36
|
+
* @param _serverId - Server ID (reserved for future use in state management)
|
|
37
|
+
*/
|
|
38
|
+
redirectToAuthorization(_serverId: string): AuthorizationUrlResult;
|
|
39
|
+
/**
|
|
40
|
+
* Exchange authorization code for tokens
|
|
41
|
+
*/
|
|
42
|
+
exchangeCode(serverId: string, request: TokenExchangeRequest): Promise<OAuthTokens>;
|
|
43
|
+
/**
|
|
44
|
+
* Refresh tokens using refresh token
|
|
45
|
+
*/
|
|
46
|
+
refreshTokens(serverId: string, refreshToken: string): Promise<OAuthTokens>;
|
|
47
|
+
/**
|
|
48
|
+
* Revoke tokens (if supported by the OAuth server)
|
|
49
|
+
*/
|
|
50
|
+
revokeTokens(serverId: string, revocationUrl: string): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Get authorization header value for API requests
|
|
53
|
+
*/
|
|
54
|
+
getAuthorizationHeader(serverId: string): Promise<string | null>;
|
|
55
|
+
/**
|
|
56
|
+
* Check if a server has valid (non-expired) tokens
|
|
57
|
+
*/
|
|
58
|
+
hasValidTokens(serverId: string): Promise<boolean>;
|
|
59
|
+
/**
|
|
60
|
+
* Generate a cryptographically secure state parameter
|
|
61
|
+
*/
|
|
62
|
+
private generateState;
|
|
63
|
+
/**
|
|
64
|
+
* Generate PKCE code verifier and challenge
|
|
65
|
+
* Uses SHA-256 for code challenge method (required by OAuth 2.1)
|
|
66
|
+
*/
|
|
67
|
+
private generatePKCE;
|
|
68
|
+
/**
|
|
69
|
+
* Get the OAuth configuration
|
|
70
|
+
*/
|
|
71
|
+
getConfig(): Readonly<MCPOAuthConfig>;
|
|
72
|
+
/**
|
|
73
|
+
* Get the token storage instance
|
|
74
|
+
*/
|
|
75
|
+
getStorage(): TokenStorage;
|
|
76
|
+
/**
|
|
77
|
+
* Clean up expired pending states and challenges
|
|
78
|
+
* Should be called periodically to prevent memory leaks
|
|
79
|
+
*/
|
|
80
|
+
cleanupPendingRequests(): void;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Create an OAuth provider from MCP server auth configuration
|
|
84
|
+
*/
|
|
85
|
+
export declare function createOAuthProviderFromConfig(authConfig: {
|
|
86
|
+
clientId: string;
|
|
87
|
+
clientSecret?: string;
|
|
88
|
+
authorizationUrl: string;
|
|
89
|
+
tokenUrl: string;
|
|
90
|
+
redirectUrl: string;
|
|
91
|
+
scope?: string;
|
|
92
|
+
usePKCE?: boolean;
|
|
93
|
+
}, storage?: TokenStorage): NeuroLinkOAuthProvider;
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.1 Client Provider for MCP HTTP Transport
|
|
3
|
+
* Implements OAuth 2.1 authentication with PKCE support
|
|
4
|
+
*/
|
|
5
|
+
import { randomBytes, createHash } from "crypto";
|
|
6
|
+
import { InMemoryTokenStorage, isTokenExpired, calculateExpiresAt, } from "./tokenStorage.js";
|
|
7
|
+
import { logger } from "../../utils/logger.js";
|
|
8
|
+
/**
|
|
9
|
+
* NeuroLink OAuth Provider for MCP HTTP Transport
|
|
10
|
+
* Handles OAuth 2.1 authentication flow with optional PKCE support
|
|
11
|
+
*/
|
|
12
|
+
export class NeuroLinkOAuthProvider {
|
|
13
|
+
config;
|
|
14
|
+
storage;
|
|
15
|
+
pendingChallenges = new Map();
|
|
16
|
+
pendingStates = new Set();
|
|
17
|
+
constructor(config, storage) {
|
|
18
|
+
this.config = {
|
|
19
|
+
...config,
|
|
20
|
+
usePKCE: config.usePKCE ?? true, // PKCE enabled by default for OAuth 2.1
|
|
21
|
+
};
|
|
22
|
+
this.storage = storage ?? new InMemoryTokenStorage();
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Get stored tokens for a server
|
|
26
|
+
* Returns null if tokens are not available or expired (without refresh token)
|
|
27
|
+
*/
|
|
28
|
+
async tokens(serverId) {
|
|
29
|
+
const tokens = await this.storage.getTokens(serverId);
|
|
30
|
+
if (!tokens) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
// Check if tokens are expired
|
|
34
|
+
if (isTokenExpired(tokens)) {
|
|
35
|
+
// Try to refresh if refresh token is available
|
|
36
|
+
if (tokens.refreshToken) {
|
|
37
|
+
try {
|
|
38
|
+
const refreshedTokens = await this.refreshTokens(serverId, tokens.refreshToken);
|
|
39
|
+
return refreshedTokens;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
logger.warn(`[NeuroLinkOAuthProvider] Token refresh failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
43
|
+
// Delete expired tokens if refresh fails
|
|
44
|
+
await this.storage.deleteTokens(serverId);
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// No refresh token, delete expired tokens
|
|
49
|
+
await this.storage.deleteTokens(serverId);
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return tokens;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Save tokens for a server
|
|
56
|
+
*/
|
|
57
|
+
async saveTokens(serverId, tokens) {
|
|
58
|
+
await this.storage.saveTokens(serverId, tokens);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Delete tokens for a server
|
|
62
|
+
*/
|
|
63
|
+
async deleteTokens(serverId) {
|
|
64
|
+
await this.storage.deleteTokens(serverId);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Get client information for MCP SDK
|
|
68
|
+
*/
|
|
69
|
+
clientInformation() {
|
|
70
|
+
return {
|
|
71
|
+
clientId: this.config.clientId,
|
|
72
|
+
clientSecret: this.config.clientSecret,
|
|
73
|
+
redirectUri: this.config.redirectUrl,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Generate authorization URL for OAuth flow
|
|
78
|
+
* Returns the URL to redirect the user to for authorization
|
|
79
|
+
* @param _serverId - Server ID (reserved for future use in state management)
|
|
80
|
+
*/
|
|
81
|
+
redirectToAuthorization(_serverId) {
|
|
82
|
+
// Generate state parameter for CSRF protection
|
|
83
|
+
const state = this.generateState();
|
|
84
|
+
this.pendingStates.add(state);
|
|
85
|
+
// Build authorization URL
|
|
86
|
+
const url = new URL(this.config.authorizationUrl);
|
|
87
|
+
// Required OAuth 2.1 parameters
|
|
88
|
+
url.searchParams.set("response_type", "code");
|
|
89
|
+
url.searchParams.set("client_id", this.config.clientId);
|
|
90
|
+
url.searchParams.set("redirect_uri", this.config.redirectUrl);
|
|
91
|
+
url.searchParams.set("state", state);
|
|
92
|
+
// Optional scope
|
|
93
|
+
if (this.config.scope) {
|
|
94
|
+
url.searchParams.set("scope", this.config.scope);
|
|
95
|
+
}
|
|
96
|
+
// PKCE support
|
|
97
|
+
let codeVerifier;
|
|
98
|
+
if (this.config.usePKCE) {
|
|
99
|
+
const pkce = this.generatePKCE();
|
|
100
|
+
codeVerifier = pkce.codeVerifier;
|
|
101
|
+
// Store PKCE challenge for later verification
|
|
102
|
+
this.pendingChallenges.set(state, pkce);
|
|
103
|
+
url.searchParams.set("code_challenge", pkce.codeChallenge);
|
|
104
|
+
url.searchParams.set("code_challenge_method", pkce.codeChallengeMethod);
|
|
105
|
+
}
|
|
106
|
+
// Additional custom parameters
|
|
107
|
+
if (this.config.additionalParams) {
|
|
108
|
+
for (const [key, value] of Object.entries(this.config.additionalParams)) {
|
|
109
|
+
url.searchParams.set(key, value);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
url: url.toString(),
|
|
114
|
+
state,
|
|
115
|
+
codeVerifier,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Exchange authorization code for tokens
|
|
120
|
+
*/
|
|
121
|
+
async exchangeCode(serverId, request) {
|
|
122
|
+
// Validate state
|
|
123
|
+
if (!this.pendingStates.has(request.state)) {
|
|
124
|
+
throw new Error("Invalid or expired state parameter");
|
|
125
|
+
}
|
|
126
|
+
this.pendingStates.delete(request.state);
|
|
127
|
+
// Get PKCE verifier if applicable
|
|
128
|
+
let codeVerifier = request.codeVerifier;
|
|
129
|
+
if (this.config.usePKCE && !codeVerifier) {
|
|
130
|
+
const pkce = this.pendingChallenges.get(request.state);
|
|
131
|
+
if (pkce) {
|
|
132
|
+
codeVerifier = pkce.codeVerifier;
|
|
133
|
+
this.pendingChallenges.delete(request.state);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Build token request
|
|
137
|
+
const body = new URLSearchParams();
|
|
138
|
+
body.set("grant_type", "authorization_code");
|
|
139
|
+
body.set("code", request.code);
|
|
140
|
+
body.set("redirect_uri", this.config.redirectUrl);
|
|
141
|
+
body.set("client_id", this.config.clientId);
|
|
142
|
+
// Include client secret if available (confidential clients)
|
|
143
|
+
if (this.config.clientSecret) {
|
|
144
|
+
body.set("client_secret", this.config.clientSecret);
|
|
145
|
+
}
|
|
146
|
+
// Include PKCE verifier if applicable
|
|
147
|
+
if (codeVerifier) {
|
|
148
|
+
body.set("code_verifier", codeVerifier);
|
|
149
|
+
}
|
|
150
|
+
// Request tokens
|
|
151
|
+
const response = await fetch(this.config.tokenUrl, {
|
|
152
|
+
method: "POST",
|
|
153
|
+
headers: {
|
|
154
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
155
|
+
Accept: "application/json",
|
|
156
|
+
},
|
|
157
|
+
body: body.toString(),
|
|
158
|
+
});
|
|
159
|
+
if (!response.ok) {
|
|
160
|
+
const errorText = await response.text();
|
|
161
|
+
throw new Error(`Token exchange failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
162
|
+
}
|
|
163
|
+
const tokenResponse = (await response.json());
|
|
164
|
+
// Convert to OAuthTokens format
|
|
165
|
+
const tokens = {
|
|
166
|
+
accessToken: tokenResponse.access_token,
|
|
167
|
+
refreshToken: tokenResponse.refresh_token,
|
|
168
|
+
expiresAt: tokenResponse.expires_in
|
|
169
|
+
? calculateExpiresAt(tokenResponse.expires_in)
|
|
170
|
+
: undefined,
|
|
171
|
+
tokenType: tokenResponse.token_type ?? "Bearer",
|
|
172
|
+
scope: tokenResponse.scope,
|
|
173
|
+
};
|
|
174
|
+
// Save tokens
|
|
175
|
+
await this.saveTokens(serverId, tokens);
|
|
176
|
+
return tokens;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Refresh tokens using refresh token
|
|
180
|
+
*/
|
|
181
|
+
async refreshTokens(serverId, refreshToken) {
|
|
182
|
+
const body = new URLSearchParams();
|
|
183
|
+
body.set("grant_type", "refresh_token");
|
|
184
|
+
body.set("refresh_token", refreshToken);
|
|
185
|
+
body.set("client_id", this.config.clientId);
|
|
186
|
+
if (this.config.clientSecret) {
|
|
187
|
+
body.set("client_secret", this.config.clientSecret);
|
|
188
|
+
}
|
|
189
|
+
const response = await fetch(this.config.tokenUrl, {
|
|
190
|
+
method: "POST",
|
|
191
|
+
headers: {
|
|
192
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
193
|
+
Accept: "application/json",
|
|
194
|
+
},
|
|
195
|
+
body: body.toString(),
|
|
196
|
+
});
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
const errorText = await response.text();
|
|
199
|
+
throw new Error(`Token refresh failed: ${response.status} ${response.statusText} - ${errorText}`);
|
|
200
|
+
}
|
|
201
|
+
const tokenResponse = (await response.json());
|
|
202
|
+
const tokens = {
|
|
203
|
+
accessToken: tokenResponse.access_token,
|
|
204
|
+
// Keep old refresh token if new one not provided
|
|
205
|
+
refreshToken: tokenResponse.refresh_token ?? refreshToken,
|
|
206
|
+
expiresAt: tokenResponse.expires_in
|
|
207
|
+
? calculateExpiresAt(tokenResponse.expires_in)
|
|
208
|
+
: undefined,
|
|
209
|
+
tokenType: tokenResponse.token_type ?? "Bearer",
|
|
210
|
+
scope: tokenResponse.scope,
|
|
211
|
+
};
|
|
212
|
+
await this.saveTokens(serverId, tokens);
|
|
213
|
+
return tokens;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Revoke tokens (if supported by the OAuth server)
|
|
217
|
+
*/
|
|
218
|
+
async revokeTokens(serverId, revocationUrl) {
|
|
219
|
+
const tokens = await this.storage.getTokens(serverId);
|
|
220
|
+
if (!tokens) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const body = new URLSearchParams();
|
|
224
|
+
body.set("token", tokens.accessToken);
|
|
225
|
+
body.set("client_id", this.config.clientId);
|
|
226
|
+
if (this.config.clientSecret) {
|
|
227
|
+
body.set("client_secret", this.config.clientSecret);
|
|
228
|
+
}
|
|
229
|
+
try {
|
|
230
|
+
await fetch(revocationUrl, {
|
|
231
|
+
method: "POST",
|
|
232
|
+
headers: {
|
|
233
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
234
|
+
},
|
|
235
|
+
body: body.toString(),
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
logger.warn(`[NeuroLinkOAuthProvider] Token revocation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
240
|
+
}
|
|
241
|
+
// Always delete local tokens
|
|
242
|
+
await this.storage.deleteTokens(serverId);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Get authorization header value for API requests
|
|
246
|
+
*/
|
|
247
|
+
async getAuthorizationHeader(serverId) {
|
|
248
|
+
const tokens = await this.tokens(serverId);
|
|
249
|
+
if (!tokens) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
return `${tokens.tokenType} ${tokens.accessToken}`;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Check if a server has valid (non-expired) tokens
|
|
256
|
+
*/
|
|
257
|
+
async hasValidTokens(serverId) {
|
|
258
|
+
const tokens = await this.tokens(serverId);
|
|
259
|
+
return tokens !== null;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Generate a cryptographically secure state parameter
|
|
263
|
+
*/
|
|
264
|
+
generateState() {
|
|
265
|
+
return randomBytes(32).toString("base64url");
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Generate PKCE code verifier and challenge
|
|
269
|
+
* Uses SHA-256 for code challenge method (required by OAuth 2.1)
|
|
270
|
+
*/
|
|
271
|
+
generatePKCE() {
|
|
272
|
+
// Generate code verifier (43-128 characters, URL-safe)
|
|
273
|
+
const codeVerifier = randomBytes(32).toString("base64url");
|
|
274
|
+
// Generate code challenge using SHA-256
|
|
275
|
+
const codeChallenge = createHash("sha256")
|
|
276
|
+
.update(codeVerifier)
|
|
277
|
+
.digest("base64url");
|
|
278
|
+
return {
|
|
279
|
+
codeVerifier,
|
|
280
|
+
codeChallenge,
|
|
281
|
+
codeChallengeMethod: "S256",
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Get the OAuth configuration
|
|
286
|
+
*/
|
|
287
|
+
getConfig() {
|
|
288
|
+
return { ...this.config };
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Get the token storage instance
|
|
292
|
+
*/
|
|
293
|
+
getStorage() {
|
|
294
|
+
return this.storage;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Clean up expired pending states and challenges
|
|
298
|
+
* Should be called periodically to prevent memory leaks
|
|
299
|
+
*/
|
|
300
|
+
cleanupPendingRequests() {
|
|
301
|
+
// Clear old pending states (older than 10 minutes)
|
|
302
|
+
// Note: In a production system, you'd want to track timestamps
|
|
303
|
+
// For now, we just clear all if there are too many
|
|
304
|
+
if (this.pendingStates.size > 100) {
|
|
305
|
+
this.pendingStates.clear();
|
|
306
|
+
}
|
|
307
|
+
if (this.pendingChallenges.size > 100) {
|
|
308
|
+
this.pendingChallenges.clear();
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Create an OAuth provider from MCP server auth configuration
|
|
314
|
+
*/
|
|
315
|
+
export function createOAuthProviderFromConfig(authConfig, storage) {
|
|
316
|
+
return new NeuroLinkOAuthProvider({
|
|
317
|
+
clientId: authConfig.clientId,
|
|
318
|
+
clientSecret: authConfig.clientSecret,
|
|
319
|
+
authorizationUrl: authConfig.authorizationUrl,
|
|
320
|
+
tokenUrl: authConfig.tokenUrl,
|
|
321
|
+
redirectUrl: authConfig.redirectUrl,
|
|
322
|
+
scope: authConfig.scope,
|
|
323
|
+
usePKCE: authConfig.usePKCE ?? true,
|
|
324
|
+
}, storage);
|
|
325
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Storage for OAuth 2.1 authentication
|
|
3
|
+
* Provides implementations for storing OAuth tokens
|
|
4
|
+
*/
|
|
5
|
+
import type { OAuthTokens, TokenStorage } from "../../types/mcpTypes.js";
|
|
6
|
+
/**
|
|
7
|
+
* In-memory token storage implementation
|
|
8
|
+
* Suitable for development and single-session use
|
|
9
|
+
* Tokens are lost when the process terminates
|
|
10
|
+
*/
|
|
11
|
+
export declare class InMemoryTokenStorage implements TokenStorage {
|
|
12
|
+
private tokens;
|
|
13
|
+
getTokens(serverId: string): Promise<OAuthTokens | null>;
|
|
14
|
+
saveTokens(serverId: string, tokens: OAuthTokens): Promise<void>;
|
|
15
|
+
deleteTokens(serverId: string): Promise<void>;
|
|
16
|
+
hasTokens(serverId: string): Promise<boolean>;
|
|
17
|
+
clearAll(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Get the number of stored token sets
|
|
20
|
+
*/
|
|
21
|
+
get size(): number;
|
|
22
|
+
/**
|
|
23
|
+
* Get all server IDs with stored tokens
|
|
24
|
+
*/
|
|
25
|
+
getServerIds(): string[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* File-based token storage implementation
|
|
29
|
+
* Persists tokens to disk for cross-session use
|
|
30
|
+
*/
|
|
31
|
+
export declare class FileTokenStorage implements TokenStorage {
|
|
32
|
+
private filePath;
|
|
33
|
+
private tokens;
|
|
34
|
+
private loaded;
|
|
35
|
+
constructor(filePath: string);
|
|
36
|
+
private loadTokens;
|
|
37
|
+
private saveToFile;
|
|
38
|
+
getTokens(serverId: string): Promise<OAuthTokens | null>;
|
|
39
|
+
saveTokens(serverId: string, tokens: OAuthTokens): Promise<void>;
|
|
40
|
+
deleteTokens(serverId: string): Promise<void>;
|
|
41
|
+
hasTokens(serverId: string): Promise<boolean>;
|
|
42
|
+
clearAll(): Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if tokens are expired or about to expire
|
|
46
|
+
* @param tokens - OAuth tokens to check
|
|
47
|
+
* @param bufferSeconds - Buffer time in seconds before expiration (default: 60)
|
|
48
|
+
* @returns True if tokens are expired or will expire within buffer time
|
|
49
|
+
*/
|
|
50
|
+
export declare function isTokenExpired(tokens: OAuthTokens, bufferSeconds?: number): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Calculate token expiration timestamp from expires_in value
|
|
53
|
+
* @param expiresIn - Token lifetime in seconds
|
|
54
|
+
* @returns Expiration timestamp (Unix epoch in milliseconds)
|
|
55
|
+
*/
|
|
56
|
+
export declare function calculateExpiresAt(expiresIn: number): number;
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Storage for OAuth 2.1 authentication
|
|
3
|
+
* Provides implementations for storing OAuth tokens
|
|
4
|
+
*/
|
|
5
|
+
import { logger } from "../../utils/logger.js";
|
|
6
|
+
/**
|
|
7
|
+
* In-memory token storage implementation
|
|
8
|
+
* Suitable for development and single-session use
|
|
9
|
+
* Tokens are lost when the process terminates
|
|
10
|
+
*/
|
|
11
|
+
export class InMemoryTokenStorage {
|
|
12
|
+
tokens = new Map();
|
|
13
|
+
async getTokens(serverId) {
|
|
14
|
+
return this.tokens.get(serverId) ?? null;
|
|
15
|
+
}
|
|
16
|
+
async saveTokens(serverId, tokens) {
|
|
17
|
+
this.tokens.set(serverId, tokens);
|
|
18
|
+
}
|
|
19
|
+
async deleteTokens(serverId) {
|
|
20
|
+
this.tokens.delete(serverId);
|
|
21
|
+
}
|
|
22
|
+
async hasTokens(serverId) {
|
|
23
|
+
return this.tokens.has(serverId);
|
|
24
|
+
}
|
|
25
|
+
async clearAll() {
|
|
26
|
+
this.tokens.clear();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the number of stored token sets
|
|
30
|
+
*/
|
|
31
|
+
get size() {
|
|
32
|
+
return this.tokens.size;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get all server IDs with stored tokens
|
|
36
|
+
*/
|
|
37
|
+
getServerIds() {
|
|
38
|
+
return Array.from(this.tokens.keys());
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* File-based token storage implementation
|
|
43
|
+
* Persists tokens to disk for cross-session use
|
|
44
|
+
*/
|
|
45
|
+
export class FileTokenStorage {
|
|
46
|
+
filePath;
|
|
47
|
+
tokens = new Map();
|
|
48
|
+
loaded = false;
|
|
49
|
+
constructor(filePath) {
|
|
50
|
+
this.filePath = filePath;
|
|
51
|
+
}
|
|
52
|
+
async loadTokens() {
|
|
53
|
+
if (this.loaded) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const fs = await import("fs/promises");
|
|
58
|
+
const data = await fs.readFile(this.filePath, "utf-8");
|
|
59
|
+
const parsed = JSON.parse(data);
|
|
60
|
+
this.tokens = new Map(Object.entries(parsed));
|
|
61
|
+
this.loaded = true;
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
// File doesn't exist or is invalid, start with empty tokens
|
|
65
|
+
if (error instanceof Error &&
|
|
66
|
+
"code" in error &&
|
|
67
|
+
error.code !== "ENOENT") {
|
|
68
|
+
logger.warn(`[FileTokenStorage] Error loading tokens: ${error.message}`);
|
|
69
|
+
}
|
|
70
|
+
this.tokens = new Map();
|
|
71
|
+
this.loaded = true;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async saveToFile() {
|
|
75
|
+
try {
|
|
76
|
+
const fs = await import("fs/promises");
|
|
77
|
+
const path = await import("path");
|
|
78
|
+
// Ensure directory exists
|
|
79
|
+
const dir = path.dirname(this.filePath);
|
|
80
|
+
await fs.mkdir(dir, { recursive: true });
|
|
81
|
+
// Write tokens to file
|
|
82
|
+
const data = Object.fromEntries(this.tokens.entries());
|
|
83
|
+
await fs.writeFile(this.filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
logger.error(`[FileTokenStorage] Error saving tokens: ${error instanceof Error ? error.message : String(error)}`);
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async getTokens(serverId) {
|
|
91
|
+
await this.loadTokens();
|
|
92
|
+
return this.tokens.get(serverId) ?? null;
|
|
93
|
+
}
|
|
94
|
+
async saveTokens(serverId, tokens) {
|
|
95
|
+
await this.loadTokens();
|
|
96
|
+
this.tokens.set(serverId, tokens);
|
|
97
|
+
await this.saveToFile();
|
|
98
|
+
}
|
|
99
|
+
async deleteTokens(serverId) {
|
|
100
|
+
await this.loadTokens();
|
|
101
|
+
this.tokens.delete(serverId);
|
|
102
|
+
await this.saveToFile();
|
|
103
|
+
}
|
|
104
|
+
async hasTokens(serverId) {
|
|
105
|
+
await this.loadTokens();
|
|
106
|
+
return this.tokens.has(serverId);
|
|
107
|
+
}
|
|
108
|
+
async clearAll() {
|
|
109
|
+
this.tokens.clear();
|
|
110
|
+
await this.saveToFile();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Check if tokens are expired or about to expire
|
|
115
|
+
* @param tokens - OAuth tokens to check
|
|
116
|
+
* @param bufferSeconds - Buffer time in seconds before expiration (default: 60)
|
|
117
|
+
* @returns True if tokens are expired or will expire within buffer time
|
|
118
|
+
*/
|
|
119
|
+
export function isTokenExpired(tokens, bufferSeconds = 60) {
|
|
120
|
+
if (!tokens.expiresAt) {
|
|
121
|
+
return false; // No expiration set, assume valid
|
|
122
|
+
}
|
|
123
|
+
const bufferMs = bufferSeconds * 1000;
|
|
124
|
+
const now = Date.now();
|
|
125
|
+
return tokens.expiresAt - bufferMs <= now;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Calculate token expiration timestamp from expires_in value
|
|
129
|
+
* @param expiresIn - Token lifetime in seconds
|
|
130
|
+
* @returns Expiration timestamp (Unix epoch in milliseconds)
|
|
131
|
+
*/
|
|
132
|
+
export function calculateExpiresAt(expiresIn) {
|
|
133
|
+
return Date.now() + expiresIn * 1000;
|
|
134
|
+
}
|
|
@@ -8,11 +8,15 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { EventEmitter } from "events";
|
|
10
10
|
import { ToolDiscoveryService } from "./toolDiscoveryService.js";
|
|
11
|
-
import type { HITLManager } from "../
|
|
11
|
+
import type { HITLManager } from "../types/hitlTypes.js";
|
|
12
12
|
import type { ExternalMCPServerInstance, ExternalMCPServerHealth, ExternalMCPConfigValidation, ExternalMCPOperationResult, ExternalMCPManagerConfig, ExternalMCPToolInfo } from "../types/externalMcp.js";
|
|
13
13
|
import type { MCPServerInfo } from "../types/mcpTypes.js";
|
|
14
14
|
import type { JsonObject } from "../types/common.js";
|
|
15
15
|
import type { ServerLoadResult } from "../types/typeAliases.js";
|
|
16
|
+
/**
|
|
17
|
+
* ExternalServerManager
|
|
18
|
+
* Core class for managing external MCP servers
|
|
19
|
+
*/
|
|
16
20
|
export declare class ExternalServerManager extends EventEmitter {
|
|
17
21
|
private servers;
|
|
18
22
|
private config;
|