@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.
@@ -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
- config.environmentUrl = cleanValue;
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
- config.environmentUrl = cleanValue;
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
- // Add our parameters
592
- existingParams.set('domain_hint', environmentUrl!.trim());
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 - only throw if this is a new error URL
1923
+ // Check for error parameters
1878
1924
  if (currentUrl && currentUrl.includes('error=')) {
1879
- if (currentUrl !== lastErrorUrl) {
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
- const errorMatch = currentUrl.match(/[?&]error=([^&]+)/);
1882
- const errorDescMatch = currentUrl.match(/error_description=([^&]+)/);
1883
- const error = errorMatch && errorMatch[1] ? decodeURIComponent(errorMatch[1]) : 'unknown_error';
1884
- const errorDesc = errorDescMatch && errorDescMatch[1] ? decodeURIComponent(errorDescMatch[1]) : 'No description';
1885
-
1886
- // Scope errors are configuration issues - stop monitoring and let user see error
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
- if (isScopeError) {
1899
- console.error('💡 This is a configuration error. Please check your scope settings and close this window.');
1900
- console.error('🛑 Stopping monitoring - please fix the configuration and try again.');
1901
- oAuthErrorLogged = true;
1902
- // Stop monitoring for scope errors - user needs to fix config
1903
- this.stopConfigServer();
1904
- return; // Exit the monitoring loop
1905
- } else if (isRetryableError) {
1906
- console.error('💡 The configuration server will remain running. Please try again with correct credentials.');
1907
- console.error('🔍 Continuing to monitor browser for authorization code...');
1908
- oAuthErrorLogged = true;
1909
- } else {
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
- // Continue monitoring silently for retryable errors - don't throw, just keep checking
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
- // If it's the same error URL, continue monitoring silently
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) {