@egain/egain-mcp-server 1.0.24 → 1.0.26
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/bin/mcp-server.js +72 -32
- package/bin/mcp-server.js.map +3 -3
- package/esm/src/hooks/auth-hook.d.ts +5 -0
- package/esm/src/hooks/auth-hook.d.ts.map +1 -1
- package/esm/src/hooks/auth-hook.js +96 -46
- package/esm/src/hooks/auth-hook.js.map +1 -1
- package/package.json +1 -1
- package/src/hooks/auth-hook.ts +104 -47
package/src/hooks/auth-hook.ts
CHANGED
|
@@ -346,6 +346,26 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
346
346
|
}
|
|
347
347
|
|
|
348
348
|
|
|
349
|
+
/**
|
|
350
|
+
* Clean environment URL by removing protocol (https://, http://) and trailing slashes
|
|
351
|
+
* This ensures the domain_hint parameter works correctly
|
|
352
|
+
*/
|
|
353
|
+
private cleanEnvironmentUrl(url: string | undefined): string | undefined {
|
|
354
|
+
if (!url) {
|
|
355
|
+
return url;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
let cleaned = url.trim();
|
|
359
|
+
|
|
360
|
+
// Remove protocol (https:// or http://)
|
|
361
|
+
cleaned = cleaned.replace(/^https?:\/\//i, '');
|
|
362
|
+
|
|
363
|
+
// Remove trailing slashes
|
|
364
|
+
cleaned = cleaned.replace(/\/+$/, '');
|
|
365
|
+
|
|
366
|
+
return cleaned;
|
|
367
|
+
}
|
|
368
|
+
|
|
349
369
|
/**
|
|
350
370
|
* Save OAuth config to user's home directory (secure file storage)
|
|
351
371
|
*/
|
|
@@ -376,6 +396,15 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
376
396
|
if (fs.existsSync(configPath)) {
|
|
377
397
|
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
378
398
|
const config = JSON.parse(configContent) as AuthConfig;
|
|
399
|
+
// Clean environmentUrl if present
|
|
400
|
+
if (config.environmentUrl) {
|
|
401
|
+
const cleaned = this.cleanEnvironmentUrl(config.environmentUrl);
|
|
402
|
+
if (cleaned) {
|
|
403
|
+
config.environmentUrl = cleaned;
|
|
404
|
+
} else {
|
|
405
|
+
delete config.environmentUrl;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
379
408
|
console.error(`✅ Loaded config from: ${configPath}`);
|
|
380
409
|
return config;
|
|
381
410
|
}
|
|
@@ -421,9 +450,13 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
421
450
|
|
|
422
451
|
switch (cleanKey) {
|
|
423
452
|
// New variable names (preferred)
|
|
424
|
-
case 'EGAIN_URL':
|
|
425
|
-
|
|
453
|
+
case 'EGAIN_URL': {
|
|
454
|
+
const cleaned = this.cleanEnvironmentUrl(cleanValue);
|
|
455
|
+
if (cleaned) {
|
|
456
|
+
config.environmentUrl = cleaned;
|
|
457
|
+
}
|
|
426
458
|
break;
|
|
459
|
+
}
|
|
427
460
|
case 'CLIENT_ID':
|
|
428
461
|
config.clientId = cleanValue;
|
|
429
462
|
break;
|
|
@@ -441,9 +474,13 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
441
474
|
break;
|
|
442
475
|
|
|
443
476
|
// Backward compatibility with old names
|
|
444
|
-
case 'EGAIN_ENVIRONMENT_URL':
|
|
445
|
-
|
|
477
|
+
case 'EGAIN_ENVIRONMENT_URL': {
|
|
478
|
+
const cleaned = this.cleanEnvironmentUrl(cleanValue);
|
|
479
|
+
if (cleaned) {
|
|
480
|
+
config.environmentUrl = cleaned;
|
|
481
|
+
}
|
|
446
482
|
break;
|
|
483
|
+
}
|
|
447
484
|
case 'EGAIN_CLIENT_ID':
|
|
448
485
|
config.clientId = cleanValue;
|
|
449
486
|
break;
|
|
@@ -578,7 +615,7 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
578
615
|
const queryString = urlParts.slice(1).join('?'); // Handle multiple ? characters
|
|
579
616
|
existingParams = new URLSearchParams(queryString);
|
|
580
617
|
|
|
581
|
-
// Remove domain_hint if it exists (we'll add our own)
|
|
618
|
+
// Remove domain_hint if it exists (we'll add our own cleaned version)
|
|
582
619
|
if (existingParams.has('domain_hint')) {
|
|
583
620
|
existingParams.delete('domain_hint');
|
|
584
621
|
console.error('🔧 Removed existing domain_hint from authorization URL');
|
|
@@ -588,8 +625,14 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
588
625
|
const prefix = scopePrefix || '';
|
|
589
626
|
const scope = `${prefix}knowledge.portalmgr.manage ${prefix}knowledge.portalmgr.read ${prefix}core.aiservices.read`;
|
|
590
627
|
|
|
591
|
-
//
|
|
592
|
-
|
|
628
|
+
// Clean and validate environmentUrl before using it as domain_hint
|
|
629
|
+
const cleanedEnvironmentUrl = this.cleanEnvironmentUrl(environmentUrl);
|
|
630
|
+
if (!cleanedEnvironmentUrl) {
|
|
631
|
+
throw new Error('Environment URL is required');
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Add our parameters (ensure domain_hint is clean and not duplicated)
|
|
635
|
+
existingParams.set('domain_hint', cleanedEnvironmentUrl);
|
|
593
636
|
existingParams.set('client_id', clientId!.trim());
|
|
594
637
|
existingParams.set('response_type', 'code');
|
|
595
638
|
existingParams.set('redirect_uri', cleanRedirectUri);
|
|
@@ -1027,14 +1070,17 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
1027
1070
|
}
|
|
1028
1071
|
|
|
1029
1072
|
// Update authConfig from browser form data
|
|
1073
|
+
const cleanedEnvUrl = this.cleanEnvironmentUrl(config.egainUrl);
|
|
1030
1074
|
this.authConfig = {
|
|
1031
|
-
environmentUrl: config.egainUrl,
|
|
1032
1075
|
authUrl: config.authUrl,
|
|
1033
1076
|
accessUrl: config.accessTokenUrl,
|
|
1034
1077
|
clientId: config.clientId,
|
|
1035
1078
|
redirectUri: config.redirectUrl,
|
|
1036
1079
|
scopePrefix: config.scopePrefix || undefined
|
|
1037
1080
|
};
|
|
1081
|
+
if (cleanedEnvUrl) {
|
|
1082
|
+
this.authConfig.environmentUrl = cleanedEnvUrl;
|
|
1083
|
+
}
|
|
1038
1084
|
|
|
1039
1085
|
// Save config to secure file storage (home directory)
|
|
1040
1086
|
try {
|
|
@@ -1874,50 +1920,61 @@ export class AuthenticationHook implements SDKInitHook, BeforeRequestHook {
|
|
|
1874
1920
|
}
|
|
1875
1921
|
}
|
|
1876
1922
|
|
|
1877
|
-
// Check for error parameters
|
|
1923
|
+
// Check for error parameters
|
|
1878
1924
|
if (currentUrl && currentUrl.includes('error=')) {
|
|
1879
|
-
|
|
1925
|
+
const errorMatch = currentUrl.match(/[?&]error=([^&]+)/);
|
|
1926
|
+
const errorDescMatch = currentUrl.match(/error_description=([^&]+)/);
|
|
1927
|
+
const error = errorMatch && errorMatch[1] ? decodeURIComponent(errorMatch[1]) : 'unknown_error';
|
|
1928
|
+
const errorDesc = errorDescMatch && errorDescMatch[1] ? decodeURIComponent(errorDescMatch[1]) : 'No description';
|
|
1929
|
+
|
|
1930
|
+
// Scope errors are configuration issues - stop monitoring and let user see error
|
|
1931
|
+
// Username/password errors can be retried - continue monitoring
|
|
1932
|
+
const errorLower = error.toLowerCase();
|
|
1933
|
+
const errorDescLower = errorDesc.toLowerCase();
|
|
1934
|
+
const isScopeError = error === 'invalid_scope' || errorDescLower.includes('scope') || errorLower.includes('scope');
|
|
1935
|
+
const isRetryableError = error === 'access_denied' || error === 'invalid_grant' ||
|
|
1936
|
+
errorDescLower.includes('password') || errorLower.includes('password') ||
|
|
1937
|
+
errorDescLower.includes('username') || errorLower.includes('username') ||
|
|
1938
|
+
errorDescLower.includes('credential') || errorLower.includes('credential') ||
|
|
1939
|
+
errorLower.includes('user not found') || errorDescLower.includes('user not found');
|
|
1940
|
+
|
|
1941
|
+
// Check if this is a new error URL (different error message)
|
|
1942
|
+
const isNewError = currentUrl !== lastErrorUrl;
|
|
1943
|
+
if (isNewError) {
|
|
1880
1944
|
lastErrorUrl = currentUrl;
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
// Username/password errors can be retried - continue monitoring
|
|
1888
|
-
const isScopeError = error === 'invalid_scope' || errorDesc.toLowerCase().includes('scope');
|
|
1889
|
-
const isRetryableError = error === 'access_denied' || error === 'invalid_grant' ||
|
|
1890
|
-
errorDesc.toLowerCase().includes('password') ||
|
|
1891
|
-
errorDesc.toLowerCase().includes('username') ||
|
|
1892
|
-
errorDesc.toLowerCase().includes('credential');
|
|
1893
|
-
|
|
1894
|
-
// Log error only once
|
|
1895
|
-
if (!oAuthErrorLogged) {
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// Always check error type - stop immediately if non-retryable, continue if retryable
|
|
1948
|
+
if (isScopeError) {
|
|
1949
|
+
// Scope errors are configuration issues - stop monitoring
|
|
1950
|
+
if (!oAuthErrorLogged || isNewError) {
|
|
1896
1951
|
console.error('❌ OAuth authentication error:', `${error} - ${errorDesc}`);
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
// Unknown error type - be conservative and stop
|
|
1911
|
-
console.error('💡 Please check the error message displayed in your browser and close the window.');
|
|
1912
|
-
console.error('🛑 Stopping monitoring.');
|
|
1913
|
-
oAuthErrorLogged = true;
|
|
1914
|
-
this.stopConfigServer();
|
|
1915
|
-
return; // Exit the monitoring loop
|
|
1916
|
-
}
|
|
1952
|
+
console.error('💡 This is a configuration error. Please check your scope settings and close this window.');
|
|
1953
|
+
console.error('🛑 Stopping monitoring - please fix the configuration and try again.');
|
|
1954
|
+
oAuthErrorLogged = true;
|
|
1955
|
+
}
|
|
1956
|
+
this.stopConfigServer();
|
|
1957
|
+
return; // Exit the monitoring loop
|
|
1958
|
+
} else if (!isRetryableError) {
|
|
1959
|
+
// Unknown/non-retryable error type - stop monitoring
|
|
1960
|
+
if (!oAuthErrorLogged || isNewError) {
|
|
1961
|
+
console.error('❌ OAuth authentication error:', `${error} - ${errorDesc}`);
|
|
1962
|
+
console.error('💡 Please check the error message displayed in your browser and close the window.');
|
|
1963
|
+
console.error('🛑 Stopping monitoring.');
|
|
1964
|
+
oAuthErrorLogged = true;
|
|
1917
1965
|
}
|
|
1918
|
-
|
|
1966
|
+
this.stopConfigServer();
|
|
1967
|
+
return; // Exit the monitoring loop
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
// Retryable error - log each new error attempt so user knows what happened
|
|
1971
|
+
if (isNewError) {
|
|
1972
|
+
console.error('❌ OAuth authentication error:', `${error} - ${errorDesc}`);
|
|
1973
|
+
console.error('💡 The configuration server will remain running. Please try again with correct credentials.');
|
|
1974
|
+
console.error('🔍 Continuing to monitor browser for authorization code...');
|
|
1975
|
+
oAuthErrorLogged = true;
|
|
1919
1976
|
}
|
|
1920
|
-
//
|
|
1977
|
+
// Continue monitoring silently for retryable errors - don't throw, just keep checking
|
|
1921
1978
|
} else {
|
|
1922
1979
|
// Reset error tracking if URL no longer contains error
|
|
1923
1980
|
if (lastErrorUrl !== null) {
|