{"_id":"@babamba2/mcp-abap-adt-auth-providers","name":"@babamba2/mcp-abap-adt-auth-providers","dist-tags":{"latest":"1.0.5"},"versions":{"1.0.5":{"name":"@babamba2/mcp-abap-adt-auth-providers","version":"1.0.5","description":"Token providers for MCP ABAP ADT auth-broker (fork of @mcp-abap-adt/auth-providers by fr0ster)","main":"dist/index.js","types":"dist/index.d.ts","keywords":["abap","sap","adt","jwt","authentication","token","provider","authorization_code","client_credentials","mcp","abap-adt"],"author":{"name":"babamba2","email":"psspss1122@gmail.com"},"contributors":[{"name":"Oleksii Kyslytsia","email":"oleksij.kyslytsja@gmail.com","url":"original author"}],"license":"MIT","homepage":"https://github.com/babamba2/mcp-abap-adt-auth-providers#readme","bugs":{"url":"https://github.com/babamba2/mcp-abap-adt-auth-providers/issues"},"repository":{"type":"git","url":"git+https://github.com/babamba2/mcp-abap-adt-auth-providers.git"},"publishConfig":{"access":"public"},"bin":{"auth-authorization-code":"bin/auth-authorization-code.ts","auth-client-credentials":"bin/auth-client-credentials.ts","auth-device-flow":"bin/auth-device-flow.ts"},"scripts":{"chrono":"./tools/version-stats.sh","clean":"rm -rf dist tsconfig.tsbuildinfo","lint":"npx biome check --write src","lint:check":"npx biome check src","format":"npx biome format --write src","build":"npm run --silent clean && npx biome check src --diagnostic-level=error && npx tsc -p tsconfig.json","build:fast":"npx tsc -p tsconfig.json","test":"NODE_OPTIONS=--experimental-vm-modules jest","test:check":"npx tsc --noEmit","prepublishOnly":"npm run build:fast"},"engines":{"node":">=18.0.0"},"dependencies":{"@babamba2/mcp-abap-adt-interfaces":"^3.1.0","axios":"^1.13.5","express":"^5.1.0","open":"^11.0.0"},"devDependencies":{"@biomejs/biome":"^2.3.14","@babamba2/mcp-abap-adt-auth-stores":"^1.0.4","@babamba2/mcp-abap-adt-logger":"^0.1.4","@types/express":"^5.0.5","@types/jest":"^30.0.0","@types/js-yaml":"^4.0.9","@types/node":"^25.2.3","@jest/globals":"^30.2.0","jest":"^30.2.0","jest-util":"^30.2.0","js-yaml":"^4.1.1","pino":"^10.3.1","pino-pretty":"^13.1.3","ts-jest":"^29.2.5","tsx":"^4.19.2","typescript":"^5.9.2"},"gitHead":"b92aefbcb9863ea9342bdd8a4a8f63f8ff6fe30d","_id":"@babamba2/mcp-abap-adt-auth-providers@1.0.5","_nodeVersion":"24.13.1","_npmVersion":"11.8.0","dist":{"integrity":"sha512-YnQ/vvWzPKrWXT28NVBVfuko+JE7yrB195b+l5SdEnnJFhLU14sey/alV2yhKaU9wlFZV9k2E7iuc/JQsMXoXA==","shasum":"f3b699b55c89553c4fbf69436c6f0d315532948f","tarball":"https://registry.npmjs.org/@babamba2/mcp-abap-adt-auth-providers/-/mcp-abap-adt-auth-providers-1.0.5.tgz","fileCount":98,"unpackedSize":252982,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD0nQf95UD+ILQlYUDn3j63vWcwmPwZupQfeuK+rZbm4QIhAN43i0ZEMaRir+J7D82DHAbU+KV4rAreffkIvtQqjJjY"}]},"_npmUser":{"name":"psspss1122","email":"psspss1122@gmail.com"},"directories":{},"maintainers":[{"name":"psspss1122","email":"psspss1122@gmail.com"},{"name":"s2hoon326","email":"s2hoon326@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mcp-abap-adt-auth-providers_1.0.5_1776500881647_0.3085491339849207"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-18T08:28:01.579Z","1.0.5":"2026-04-18T08:28:01.793Z","modified":"2026-04-18T08:28:02.000Z"},"maintainers":[{"name":"psspss1122","email":"psspss1122@gmail.com"},{"name":"s2hoon326","email":"s2hoon326@gmail.com"}],"description":"Token providers for MCP ABAP ADT auth-broker (fork of @mcp-abap-adt/auth-providers by fr0ster)","homepage":"https://github.com/babamba2/mcp-abap-adt-auth-providers#readme","keywords":["abap","sap","adt","jwt","authentication","token","provider","authorization_code","client_credentials","mcp","abap-adt"],"repository":{"type":"git","url":"git+https://github.com/babamba2/mcp-abap-adt-auth-providers.git"},"contributors":[{"name":"Oleksii Kyslytsia","email":"oleksij.kyslytsja@gmail.com","url":"original author"}],"author":{"name":"babamba2","email":"psspss1122@gmail.com"},"bugs":{"url":"https://github.com/babamba2/mcp-abap-adt-auth-providers/issues"},"license":"MIT","readme":"# @mcp-abap-adt/auth-providers\r\n\r\nToken providers for MCP ABAP ADT auth-broker.\r\n\r\nThis package provides token provider implementations for the `@mcp-abap-adt/auth-broker` package.\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install @mcp-abap-adt/auth-providers\r\n```\r\n\r\n## Overview\r\n\r\nThis package implements the `ITokenProvider` interface from `@mcp-abap-adt/interfaces`:\r\n\r\n- **AuthorizationCodeProvider** - Uses browser-based OAuth2 authorization code flow (user token)\r\n- **ClientCredentialsProvider** - Uses `client_credentials` grant type (no browser required)\r\n\r\nProviders are configured via constructor; `getTokens()` takes no parameters and handles refresh/login internally.\r\n\r\n## Responsibilities and Design Principles\r\n\r\n### Core Development Principle\r\n\r\n**Interface-Only Communication**: This package follows a fundamental development principle: **all interactions with external dependencies happen ONLY through interfaces**. The code knows **NOTHING beyond what is defined in the interfaces**.\r\n\r\nThis means:\r\n- Does not know about concrete implementation classes from other packages\r\n- Does not know about internal data structures or methods not defined in interfaces\r\n- Does not make assumptions about implementation behavior beyond interface contracts\r\n- Does not access properties or methods not explicitly defined in interfaces\r\n\r\nThis principle ensures:\r\n- **Loose coupling**: Providers are decoupled from concrete implementations in other packages\r\n- **Flexibility**: New implementations can be added without modifying providers\r\n- **Testability**: Easy to mock dependencies for testing\r\n- **Maintainability**: Changes to implementations don't affect providers\r\n\r\n### Package Responsibilities\r\n\r\nThis package is responsible for:\r\n\r\n1. **Implementing token provider interface**: Provides concrete implementations of `ITokenProvider` interface defined in `@mcp-abap-adt/interfaces`\r\n2. **Token acquisition**: Handles OAuth2 flows (browser-based, refresh token, client credentials) to obtain JWT tokens\r\n3. **Token validation**: Validates JWT locally by checking exp claim (no HTTP requests)\r\n4. **OAuth2 flows**: Manages browser-based OAuth2 authorization code flow and refresh token flow\r\n\r\n#### What This Package Does\r\n\r\n- **Implements ITokenProvider**: Provides concrete implementations (`AuthorizationCodeProvider`, `ClientCredentialsProvider`)\r\n- **Handles OAuth2 flows**: Browser-based OAuth2, refresh token, and client credentials grant types\r\n- **Obtains tokens**: Makes HTTP requests to UAA endpoints to obtain JWT tokens\r\n- **Validates tokens**: Validates JWT locally by checking exp claim (no HTTP requests)\r\n- **Returns tokens**: Returns `ITokenResult` with `authorizationToken` and optional `refreshToken`\r\n\r\n#### What This Package Does NOT Do\r\n\r\n- **Does NOT store tokens**: Token storage is handled by `@mcp-abap-adt/auth-stores`\r\n- **Does NOT orchestrate authentication**: Token lifecycle management is handled by `@mcp-abap-adt/auth-broker`\r\n- **Does NOT know about service keys**: Service key loading is handled by stores\r\n- **Does NOT manage sessions**: Session management is handled by stores\r\n- **Does NOT return `serviceUrl` if unknown**: Providers may not return `serviceUrl` because they only handle token acquisition, not connection configuration\r\n\r\n### External Dependencies\r\n\r\nThis package interacts with external packages **ONLY through interfaces**:\r\n\r\n- **`@mcp-abap-adt/auth-broker`**: Uses interfaces (`ITokenProvider`, `IAuthorizationConfig`) - does not know about `AuthBroker` implementation\r\n- **`@mcp-abap-adt/logger`**: Uses `Logger` interface for logging - does not know about concrete logger implementation\r\n- **`@mcp-abap-adt/connection`**: Uses connection utilities for token validation - interacts through well-defined functions\r\n- **No direct dependencies on stores**: All interactions with stores happen through interfaces passed by consumers\r\n\r\n## Usage\r\n\r\n### Basic Usage\r\n\r\n```typescript\r\nimport { AuthBroker } from '@mcp-abap-adt/auth-broker';\r\nimport { AuthorizationCodeProvider, ClientCredentialsProvider } from '@mcp-abap-adt/auth-providers';\r\n\r\n// User token via authorization_code (browser flow)\r\nconst authCodeBroker = new AuthBroker({\r\n  tokenProvider: new AuthorizationCodeProvider({\r\n    uaaUrl: 'https://...',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n    browser: 'system',\r\n  }),\r\n});\r\n\r\n// Service token via client_credentials (no browser)\r\nconst clientCredsBroker = new AuthBroker({\r\n  tokenProvider: new ClientCredentialsProvider({\r\n    uaaUrl: 'https://...',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n  }),\r\n}, 'none');\r\n```\r\n\r\n### SSO Providers\r\n\r\nThis package also includes SSO providers for OIDC and SAML2, plus a small factory for DI-friendly creation.\r\n\r\nAvailable providers:\r\n- `OidcBrowserProvider` (authorization code + PKCE)\r\n- `OidcDeviceFlowProvider`\r\n- `OidcPasswordProvider`\r\n- `OidcTokenExchangeProvider`\r\n- `Saml2BearerProvider` (SAML assertion exchange)\r\n- `Saml2PureProvider` (returns SAMLResponse as token)\r\n\r\nFactory example:\r\n\r\n```typescript\r\nimport { AuthBroker } from '@mcp-abap-adt/auth-broker';\r\nimport { SsoProviderFactory } from '@mcp-abap-adt/auth-providers';\r\n\r\nconst tokenProvider = SsoProviderFactory.create({\r\n  protocol: 'oidc',\r\n  flow: 'browser',\r\n  config: {\r\n    issuerUrl: 'https://example-idp/.well-known/openid-configuration',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n    scopes: ['openid', 'profile', 'email'],\r\n    browser: 'system',\r\n  },\r\n});\r\n\r\nconst broker = new AuthBroker({ tokenProvider }, 'none');\r\n```\r\n\r\nOIDC browser example (manual code + explicit endpoints):\r\n\r\n```typescript\r\nimport { OidcBrowserProvider } from '@mcp-abap-adt/auth-providers';\r\n\r\nconst provider = new OidcBrowserProvider({\r\n  clientId: '...',\r\n  tokenEndpoint: 'https://issuer/oauth/token',\r\n  authorizationEndpoint: 'https://issuer/oauth/authorize',\r\n  authorizationCode: '<paste-code-here>',\r\n  redirectUri: 'urn:ietf:wg:oauth:2.0:oob',\r\n});\r\n```\r\n\r\nSAML bearer example (manual flow):\r\n\r\n```typescript\r\nimport { AuthBroker } from '@mcp-abap-adt/auth-broker';\r\nimport { Saml2BearerProvider } from '@mcp-abap-adt/auth-providers';\r\n\r\nconst provider = new Saml2BearerProvider({\r\n  assertionFlow: 'manual',\r\n  idpSsoUrl: 'https://idp.example.com/sso',\r\n  spEntityId: 'my-sp-entity',\r\n  uaaUrl: 'https://uaa.example.com',\r\n  clientId: '...',\r\n  clientSecret: '...',\r\n});\r\n\r\nconst broker = new AuthBroker({ tokenProvider: provider }, 'none');\r\n```\r\n\r\nSAML bearer example (headless, assertion provider):\r\n\r\n```typescript\r\nimport { AuthBroker } from '@mcp-abap-adt/auth-broker';\r\nimport { Saml2BearerProvider } from '@mcp-abap-adt/auth-providers';\r\n\r\nconst provider = new Saml2BearerProvider({\r\n  assertionFlow: 'assertion',\r\n  assertionProvider: async () => {\r\n    return getSamlResponseFromSsoProxy();\r\n  },\r\n  uaaUrl: 'https://uaa.example.com',\r\n  clientId: '...',\r\n  clientSecret: '...',\r\n});\r\n\r\nconst broker = new AuthBroker({ tokenProvider: provider }, 'none');\r\n```\r\n\r\nPure SAML example (cookie-based):\r\n\r\n```typescript\r\nimport { AuthBroker } from '@mcp-abap-adt/auth-broker';\r\nimport { Saml2PureProvider } from '@mcp-abap-adt/auth-providers';\r\n\r\nconst provider = new Saml2PureProvider({\r\n  assertionFlow: 'manual',\r\n  idpSsoUrl: 'https://idp.example.com/sso',\r\n  spEntityId: 'my-sp-entity',\r\n  // Convert SAMLResponse to session cookies for SAP (implementation-specific)\r\n  cookieProvider: async (samlResponse) => {\r\n    return exchangeSamlForCookies(samlResponse);\r\n  },\r\n});\r\n\r\nconst broker = new AuthBroker({ tokenProvider: provider }, 'none');\r\n```\r\n\r\n### With Stores\r\n\r\n**Important**: BTP and ABAP are different entities:\r\n- **BTP** (base BTP) - uses `BtpServiceKeyStore` and `BtpSessionStore` (without `sapUrl`)\r\n- **ABAP** - uses `AbapServiceKeyStore` and `AbapSessionStore` (with `sapUrl`)\r\n\r\n```typescript\r\nimport { AuthBroker } from '@mcp-abap-adt/auth-broker';\r\nimport { AuthorizationCodeProvider, ClientCredentialsProvider } from '@mcp-abap-adt/auth-providers';\r\nimport { \r\n  XsuaaServiceKeyStore, \r\n  XsuaaSessionStore,\r\n  BtpServiceKeyStore,\r\n  BtpSessionStore,\r\n  AbapServiceKeyStore,\r\n  AbapSessionStore \r\n} from '@mcp-abap-adt/auth-stores';\r\n\r\n// XSUAA provider with stores (client_credentials or auth code)\r\nconst xsuaaServiceKeyStore = new XsuaaServiceKeyStore('/path/to/service-keys');\r\nconst xsuaaSessionStore = new XsuaaSessionStore('/path/to/sessions');\r\n\r\nconst xsuaaBroker = new AuthBroker({\r\n  serviceKeyStore: xsuaaServiceKeyStore,\r\n  sessionStore: xsuaaSessionStore,\r\n  tokenProvider: new ClientCredentialsProvider({\r\n    uaaUrl: 'https://...',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n  }),\r\n}, 'none');\r\n\r\n// BTP provider with stores (base BTP, without sapUrl)\r\nconst btpServiceKeyStore = new BtpServiceKeyStore('/path/to/service-keys');\r\nconst btpSessionStore = new BtpSessionStore('/path/to/sessions');\r\n\r\nconst btpBroker = new AuthBroker({\r\n  serviceKeyStore: btpServiceKeyStore,\r\n  sessionStore: btpSessionStore,\r\n  tokenProvider: new AuthorizationCodeProvider({\r\n    uaaUrl: 'https://...',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n    browser: 'system',\r\n  }),\r\n});\r\n\r\n// ABAP provider with stores (with sapUrl)\r\nconst abapServiceKeyStore = new AbapServiceKeyStore('/path/to/service-keys');\r\nconst abapSessionStore = new AbapSessionStore('/path/to/sessions');\r\n\r\n// Use custom port if running alongside other services (e.g., proxy on port 3001)\r\nconst abapBroker = new AuthBroker({\r\n  serviceKeyStore: abapServiceKeyStore,\r\n  sessionStore: abapSessionStore,\r\n  tokenProvider: new AuthorizationCodeProvider({\r\n    uaaUrl: 'https://...',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n    browser: 'system',\r\n    redirectPort: 4001,\r\n  }), // Custom port to avoid conflicts\r\n});\r\n```\r\n\r\n### Token Providers\r\n\r\n#### AuthorizationCodeProvider\r\n\r\nUses browser-based OAuth2 flow or refresh token:\r\n\r\n```typescript\r\nimport { AuthorizationCodeProvider } from '@mcp-abap-adt/auth-providers';\r\n\r\nconst provider = new AuthorizationCodeProvider({\r\n  uaaUrl: 'https://...authentication...hana.ondemand.com',\r\n  clientId: '...',\r\n  clientSecret: '...',\r\n  browser: 'system',\r\n});\r\n\r\n// If refreshToken is provided here, uses refresh flow (no browser)\r\n// Otherwise, opens browser for OAuth2 authorization\r\nconst result = await provider.getTokens();\r\n\r\n// result.authorizationToken contains the JWT token\r\n// result.refreshToken contains refresh token (if browser flow was used)\r\n```\r\n\r\n#### ClientCredentialsProvider\r\n\r\nUses `client_credentials` grant type - no browser interaction required:\r\n\r\n```typescript\r\nimport { ClientCredentialsProvider } from '@mcp-abap-adt/auth-providers';\r\n\r\nconst provider = new ClientCredentialsProvider({\r\n  uaaUrl: 'https://...authentication...hana.ondemand.com',\r\n  clientId: '...',\r\n  clientSecret: '...',\r\n});\r\n\r\nconst result = await provider.getTokens();\r\n\r\n// result.authorizationToken contains the JWT token\r\n// result.refreshToken is undefined (client_credentials doesn't provide refresh tokens)\r\n```\r\n\r\n**Note**: The `browserAuthPort` parameter (default: 3001) configures the OAuth callback server port. If the requested port is already in use, an error will be thrown. You must specify a different port or free the port before starting authentication. The server properly closes all connections and frees the port after authentication completes, ensuring no lingering port occupation.\r\n\r\n**Timeout**: Browser authentication has a 30-second timeout to prevent blocking the consumer. If authentication is not completed within 30 seconds, the operation will fail with a timeout error. This prevents the provider from hanging indefinitely when the user doesn't complete authentication. \r\n\r\n**Process Termination Handling**: The OAuth callback server registers cleanup handlers for `SIGTERM`, `SIGINT`, `SIGHUP`, and `exit` signals. This ensures ports are properly freed even when MCP clients (like Cline) terminate the process before authentication completes. This is especially important for stdio servers where the client may kill the process at any time. On Windows, the `SIGBREAK` signal (Ctrl+Break) is also handled.\r\n\r\n**Cross-Platform Browser Support**: The browser authentication works across Linux, macOS, and Windows:\r\n- **Linux**: Automatically sets `DISPLAY=:0` if neither `DISPLAY` nor `WAYLAND_DISPLAY` environment variables are set. Supports multiple browser executable names (`google-chrome`, `google-chrome-stable`, `chromium`, `chromium-browser` for Chrome; `firefox`, `firefox-esr` for Firefox).\r\n- **Windows**: Uses proper `cmd /c start \"\"` syntax for reliable browser opening.\r\n- **macOS**: Uses native `open -a` command.\r\n\r\n**Headless Mode (SSH/Remote)**: For environments without a display (SSH sessions, Docker, CI/CD), use `browser: 'headless'`:\r\n\r\n```typescript\r\nconst result = await provider.getTokens();\r\n```\r\n\r\nIn headless mode, the authentication URL is logged and the server waits for the user to complete authentication manually. The user can open the URL on any machine and the callback will be received by the server.\r\n\r\n**Browser Options**:\r\n- `'system'` (default): Opens system default browser\r\n- `'headless'`: Logs URL, waits for manual callback (SSH/remote)\r\n- `'none'`: Logs URL, immediately rejects (automated tests)\r\n- `'chrome'`, `'edge'`, `'firefox'`: Opens specific browser\r\n\r\n### Token Validation\r\n\r\nProviders can perform **local JWT validation** by checking the `exp` (expiration) claim:\r\n\r\n```typescript\r\nconst isValid = await provider.validateToken(token, serviceUrl);\r\n```\r\n\r\n- No HTTP requests are made to the SAP server\r\n- Returns `true` if token has valid JWT format and `exp` is in the future (with 60s buffer)\r\n- Returns `false` if token is expired, invalid format, or will expire within 60 seconds\r\n- Network issues (ECONNREFUSED, timeout) do NOT trigger token refresh\r\n- HTTP errors (401/403) are handled by retry mechanism in `makeAdtRequest` wrapper\r\n\r\n```typescript\r\n// Local validation (no HTTP)\r\nconst provider = new AuthorizationCodeProvider({\r\n  uaaUrl: 'https://...authentication...hana.ondemand.com',\r\n  clientId: '...',\r\n  clientSecret: '...',\r\n});\r\nconst isValid = await provider.validateToken(token);  // serviceUrl optional\r\n// Checks JWT exp claim locally, no network request\r\n```\r\n\r\nThis approach prevents unnecessary token refresh and browser authentication when:\r\n- Server is unreachable (ECONNREFUSED, timeout)\r\n- Network is slow or unstable\r\n- Running in offline/disconnected mode\r\n\r\n### Token Refresh\r\n\r\nProviders handle refresh automatically inside `getTokens()`. No separate refresh methods are needed.\r\n\r\n```typescript\r\ntry {\r\n  const result = await provider.getTokens();\r\n  // Returns new access token and refresh token (if available)\r\n} catch (error) {\r\n  if (error instanceof ValidationError) {\r\n    console.error('Missing fields:', error.missingFields);\r\n  } else if (error instanceof RefreshError) {\r\n    console.error('Browser auth failed:', error.cause);\r\n  }\r\n}\r\n```\r\n\r\n### Error Handling\r\n\r\nThe package provides typed error classes for better error handling:\r\n\r\n```typescript\r\nimport {\r\n  TokenProviderError,\r\n  ValidationError,\r\n  RefreshError,\r\n  SessionDataError,\r\n  ServiceKeyError,\r\n  BrowserAuthError,\r\n} from '@mcp-abap-adt/auth-providers';\r\n\r\ntry {\r\n  const result = await provider.getTokens();\r\n} catch (error) {\r\n  if (error instanceof ValidationError) {\r\n    // provider config validation failed\r\n    console.error('Missing required fields:', error.missingFields);\r\n    console.error('Error code:', error.code); // 'VALIDATION_ERROR'\r\n  } else if (error instanceof RefreshError) {\r\n    // Token refresh operation failed\r\n    console.error('Refresh failed:', error.message);\r\n    console.error('Original error:', error.cause);\r\n    console.error('Error code:', error.code); // 'REFRESH_ERROR'\r\n  } else if (error instanceof BrowserAuthError) {\r\n    // Browser authentication failed\r\n    console.error('Browser auth failed:', error.cause);\r\n  }\r\n}\r\n```\r\n\r\n**Error Types**:\r\n- `TokenProviderError` - Base class with `code: string` property\r\n- `ValidationError` - provider config validation failed, includes `missingFields: string[]`\r\n- `RefreshError` - Token refresh failed, includes `cause?: Error`\r\n- `SessionDataError` - Session data invalid, includes `missingFields: string[]`\r\n- `ServiceKeyError` - Service key data invalid, includes `missingFields: string[]`\r\n- `BrowserAuthError` - Browser auth failed, includes `cause?: Error`\r\n\r\nAll error codes are defined in `@mcp-abap-adt/interfaces` package as `TOKEN_PROVIDER_ERROR_CODES`.\r\n\r\n## Testing\r\n\r\nThe package includes both unit tests (with mocks) and integration tests (with real files and services).\r\n\r\n### Unit Tests\r\n\r\n```bash\r\nnpm test\r\n```\r\n\r\n### Integration Tests\r\n\r\nIntegration tests work with real files from `tests/test-config.yaml`:\r\n\r\n1. Copy `tests/test-config.yaml.template` to `tests/test-config.yaml`\r\n2. Fill in real destination name\r\n3. Run tests - integration tests will use real services if configured\r\n\r\n```yaml\r\n# Destination name (used for service key file: <destination>.json and session file: <destination>.env)\r\ndestination: \"trial\"  # Example: \"trial\" -> looks for trial.json and trial.env\r\n\r\n# Optional: Destination directory (base directory for service keys and sessions)\r\n# If not specified, uses default platform paths:\r\n#   Unix: ~/.config/mcp-abap-adt\r\n#   Windows: %USERPROFILE%\\Documents\\mcp-abap-adt\r\n# Uncomment and set if you need a custom path:\r\n# destination_dir: ~/.config/mcp-abap-adt\r\n```\r\n\r\nIntegration tests will skip if `test-config.yaml` is not configured or contains placeholder values.\r\n\r\n**Test Scenarios**:\r\n- **Scenario 1 & 2**: Token lifecycle - login via browser and reuse token from previous scenario\r\n- **Scenario 3**: Expired session + expired refresh token - provider should re-authenticate via browser\r\n- **Token validation**: Explicit validation of token expiration in all scenarios\r\n\r\n**Note**: \r\n- Integration tests use `AbapServiceKeyStore` and `AbapSessionStore` for loading service keys and sessions\r\n- Tests may open a browser for authentication if no refresh token is available. This is expected behavior.\r\n- Each test scenario uses a unique port (3101, 3102, 3103) to avoid port conflicts\r\n- Tests use `browser: 'system'` for interactive authentication (not `'none'`)\r\n\r\n### Debug Logging\r\n\r\nTo enable detailed logging during tests or runtime, set environment variables:\r\n\r\n```bash\r\n# Enable logging for auth providers (short name)\r\nDEBUG_PROVIDER=true npm test\r\n\r\n# Or use long name (backward compatibility)\r\nDEBUG_AUTH_PROVIDERS=true npm test\r\n\r\n# Or enable via general DEBUG variable\r\nDEBUG=true npm test\r\n\r\n# Or include in DEBUG list\r\nDEBUG=provider npm test\r\n# Or\r\nDEBUG=auth-providers npm test\r\n\r\n# Set log level (debug, info, warn, error)\r\nLOG_LEVEL=debug npm test\r\n```\r\n\r\nLogging uses `@mcp-abap-adt/logger` package with structured logging:\r\n- Token exchange stages (what we send, what we receive)\r\n- Token information (lengths, previews, expiration)\r\n- Token validation checks (expiration, validity)\r\n- Errors with details\r\n\r\nExample output:\r\n```\r\n[INFO] ℹ️ [browserAuth] Exchanging code for token...\r\n[INFO] ℹ️ Tokens received: accessToken(2263 chars), refreshToken(34 chars)\r\n[DEBUG] 🐛 [BaseTokenProvider] Token validation check {\"expiresAt\":\"2025-12-25 11:08:15 UTC\",\"isValid\":true}\r\n[INFO] ℹ️ [browserAuth] Authorization URL: https://.../oauth/authorize?...\r\n[INFO] ℹ️ [browserAuth] Browser: system\r\n```\r\n\r\n**Logging Features**:\r\n- **Token Formatting**: Tokens are logged in truncated format (start...end) for security\r\n- **Date Formatting**: Expiration dates are displayed in readable format (YYYY-MM-DD HH:MM:SS UTC) instead of ISO format\r\n- **Browser Information**: Logs browser type and authorization URL for debugging\r\n- **Token Lifecycle**: Detailed logging of token acquisition, validation, and refresh operations\r\n\r\n## Dependencies\r\n\r\n- `@mcp-abap-adt/interfaces` (^0.2.2) - Interface definitions and error code constants\r\n- `axios` - HTTP client\r\n- `express` - OAuth2 callback server\r\n- `open` - Browser opening utility\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md","_rev":"1-462d6164b996d8647921bd9d299c22d3"}