{"_id":"@babamba2/mcp-abap-adt-auth-broker","name":"@babamba2/mcp-abap-adt-auth-broker","dist-tags":{"latest":"1.0.5"},"versions":{"1.0.5":{"name":"@babamba2/mcp-abap-adt-auth-broker","version":"1.0.5","description":"JWT authentication broker for MCP ABAP ADT - manages tokens based on destination headers (fork of @mcp-abap-adt/auth-broker by fr0ster)","main":"dist/index.js","types":"dist/index.d.ts","keywords":["abap","sap","adt","jwt","authentication","token","broker","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-broker#readme","bugs":{"url":"https://github.com/babamba2/mcp-abap-adt-auth-broker/issues"},"repository":{"type":"git","url":"git+https://github.com/babamba2/mcp-abap-adt-auth-broker.git"},"publishConfig":{"access":"public"},"scripts":{"chrono":"./tools/version-stats.sh","clean":"rm -rf dist tsconfig.tsbuildinfo","lint":"biome check --write src","lint:check":"biome check src","format":"biome format --write src","build":"npm run --silent clean && biome check src --diagnostic-level=error && tsc -p tsconfig.json && tsc -p tsconfig.cli.json","build:fast":"tsc -p tsconfig.json && tsc -p tsconfig.cli.json","test":"NODE_OPTIONS=--experimental-vm-modules jest","test:check":"tsc --noEmit && tsc --noEmit -p tsconfig.test.json","prepublishOnly":"npm run build:fast","prepack":"npm run build:fast","generate-env":"tsx bin/generate-env-from-service-key.ts"},"bin":{"mcp-auth":"dist/bin/mcp-auth.js","mcp-sso":"dist/bin/mcp-sso.js"},"engines":{"node":">=18.0.0"},"dependencies":{"@babamba2/mcp-abap-adt-auth-providers":"^1.0.5","@babamba2/mcp-abap-adt-auth-stores":"^1.0.4","@babamba2/mcp-abap-adt-interfaces":"^3.1.0","axios":"^1.13.5","tsx":"^4.21.0"},"devDependencies":{"@biomejs/biome":"^2.3.14","@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":"^30.2.0","jest-util":"^30.2.0","js-yaml":"^4.1.1","pino":"^10.1.0","pino-pretty":"^13.1.3","ts-jest":"^29.2.5","typescript":"^5.9.2"},"gitHead":"8d7c67603c29d84ebe58814eab87b36c77ca7b7f","_id":"@babamba2/mcp-abap-adt-auth-broker@1.0.5","_nodeVersion":"24.13.1","_npmVersion":"11.8.0","dist":{"integrity":"sha512-Q+Pjt2QNwRlUo+APEwSWduUU9poNlg7hfAb4r8rSbGXQoj4tqh0fSD518PtZEV8k9KboJAMx9J54VpWQPzQ2Mw==","shasum":"38d0f5c49e34322bb21b5a96bae687395b20edd2","tarball":"https://registry.npmjs.org/@babamba2/mcp-abap-adt-auth-broker/-/mcp-abap-adt-auth-broker-1.0.5.tgz","fileCount":40,"unpackedSize":227046,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCRxAu5L0msOLfI8hugopUYbSig3OFDpus3GuThRqBniQIhAKQLML7k1Wp+wZkB9Um5DV1dDxlNBkmp5UmXJ8lF4TSo"}]},"_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-broker_1.0.5_1776501605305_0.7436316287587572"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-18T08:40:05.238Z","1.0.5":"2026-04-18T08:40:05.454Z","modified":"2026-04-18T08:40:05.637Z"},"maintainers":[{"name":"psspss1122","email":"psspss1122@gmail.com"},{"name":"s2hoon326","email":"s2hoon326@gmail.com"}],"description":"JWT authentication broker for MCP ABAP ADT - manages tokens based on destination headers (fork of @mcp-abap-adt/auth-broker by fr0ster)","homepage":"https://github.com/babamba2/mcp-abap-adt-auth-broker#readme","keywords":["abap","sap","adt","jwt","authentication","token","broker","mcp","abap-adt"],"repository":{"type":"git","url":"git+https://github.com/babamba2/mcp-abap-adt-auth-broker.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-broker/issues"},"license":"MIT","readme":"# @mcp-abap-adt/auth-broker\r\n\r\nJWT authentication broker for MCP ABAP ADT server. Manages authentication tokens based on destination headers, automatically loading tokens from `.env` files and refreshing them using service keys when needed.\r\n\r\n## Features\r\n\r\n- 🔐 **Destination-based Authentication**: Load tokens based on `x-mcp-destination` header\r\n- 📁 **Environment File Support**: Automatically loads tokens from `{destination}.env` files\r\n- 🔄 **Automatic Token Refresh**: Refreshes expired tokens using service keys from `{destination}.json` files\r\n- ✅ **Token Validation**: Validates tokens via provider (if `validateToken` is implemented)\r\n- 💾 **Token Caching**: In-memory caching for improved performance\r\n- 🔧 **Configurable Base Path**: Customize where `.env` and `.json` files are stored\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install @mcp-abap-adt/auth-broker\r\n```\r\n\r\n## Usage\r\n\r\n### Basic Usage (Provider Required)\r\n\r\nAuthBroker requires a token provider configured for the destination:\r\n\r\n```typescript\r\nimport { AuthBroker, AbapSessionStore } from '@mcp-abap-adt/auth-broker';\r\nimport { AuthorizationCodeProvider } from '@mcp-abap-adt/auth-providers';\r\n\r\nconst tokenProvider = 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\nconst broker = new AuthBroker({\r\n  sessionStore: new AbapSessionStore('/path/to/destinations'),\r\n  tokenProvider,\r\n});\r\n\r\nconst token = await broker.getToken('TRIAL');\r\n```\r\n\r\n### Full Configuration (All Dependencies)\r\n\r\nFor maximum flexibility, provide all three dependencies:\r\n\r\n```typescript\r\nimport {\r\n  AuthBroker,\r\n  AbapServiceKeyStore,\r\n  AbapSessionStore,\r\n} from '@mcp-abap-adt/auth-broker';\r\nimport { AuthorizationCodeProvider } from '@mcp-abap-adt/auth-providers';\r\n\r\nconst broker = new AuthBroker({\r\n  sessionStore: new AbapSessionStore('/path/to/destinations'),\r\n  serviceKeyStore: new AbapServiceKeyStore('/path/to/destinations'), // optional\r\n  tokenProvider: new AuthorizationCodeProvider({\r\n    uaaUrl: 'https://...authentication...hana.ondemand.com',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n    browser: 'system',\r\n  }),\r\n}, 'chrome', logger);\r\n\r\n// Disable browser authentication for headless/stdio environments (e.g., MCP with Cline)\r\nconst brokerNoBrowser = new AuthBroker({\r\n  sessionStore: new AbapSessionStore('/path/to/destinations'),\r\n  serviceKeyStore: new AbapServiceKeyStore('/path/to/destinations'),\r\n  tokenProvider: new AuthorizationCodeProvider({\r\n    uaaUrl: 'https://...authentication...hana.ondemand.com',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n    browser: 'none',\r\n  }),\r\n  allowBrowserAuth: false, // Throws BROWSER_AUTH_REQUIRED if browser auth needed\r\n}, 'chrome', logger);\r\n```\r\n\r\n### Session + Service Key (For Initialization)\r\n\r\nIf you need to initialize sessions from service keys, create the provider from service key auth config:\r\n\r\n```typescript\r\nconst broker = new AuthBroker({\r\n  sessionStore: new AbapSessionStore('/path/to/destinations'),\r\n  serviceKeyStore: new AbapServiceKeyStore('/path/to/destinations'),\r\n  tokenProvider: 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```\r\n\r\n### In-Memory Session Store\r\n\r\nFor testing or temporary sessions:\r\n\r\n```typescript\r\nimport { AuthBroker, SafeAbapSessionStore } from '@mcp-abap-adt/auth-broker';\r\n\r\nconst broker = new AuthBroker({\r\n  sessionStore: new SafeAbapSessionStore(), // In-memory, data lost after restart\r\n});\r\n```\r\n\r\n### Custom Browser Auth Port\r\n\r\nTo avoid port conflicts with browser authentication:\r\n\r\n```typescript\r\nconst broker = new AuthBroker({\r\n  sessionStore: new AbapSessionStore('/path/to/destinations'),\r\n  serviceKeyStore: new AbapServiceKeyStore('/path/to/destinations'),\r\n  tokenProvider: new AuthorizationCodeProvider({\r\n    uaaUrl: 'https://...authentication...hana.ondemand.com',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n    browser: 'system',\r\n    redirectPort: 4001,\r\n  }),\r\n}, 'chrome');\r\n```\r\n\r\n### Getting Tokens\r\n\r\n```typescript\r\nconst token = await broker.getToken('TRIAL');\r\n\r\n// Force refresh token\r\nconst newToken = await broker.refreshToken('TRIAL');\r\n```\r\n\r\n### Creating Token Refresher for DI\r\n\r\nThe `createTokenRefresher()` method creates an `ITokenRefresher` implementation that can be injected into connections. This enables connections to handle token refresh transparently without knowing about authentication internals.\r\n\r\n```typescript\r\nimport { AuthBroker } from '@mcp-abap-adt/auth-broker';\r\nimport { JwtAbapConnection } from '@mcp-abap-adt/connection';\r\n\r\n// Create broker\r\nconst broker = new AuthBroker({\r\n  sessionStore: mySessionStore,\r\n  serviceKeyStore: myServiceKeyStore,\r\n  tokenProvider: myTokenProvider,\r\n});\r\n\r\n// Create token refresher for specific destination\r\nconst tokenRefresher = broker.createTokenRefresher('TRIAL');\r\n\r\n// Inject into connection (connection can handle 401/403 automatically)\r\nconst connection = new JwtAbapConnection(config, tokenRefresher);\r\n\r\n// Token refresher methods:\r\n// - getToken(): Returns cached token if valid, otherwise refreshes\r\n// - refreshToken(): Forces token refresh and saves to session store\r\n```\r\n\r\n**Benefits of Token Refresher:**\r\n- 🔄 **Transparent Refresh**: Connection handles 401/403 errors automatically\r\n- 🧩 **Dependency Injection**: Clean separation of concerns\r\n- 💾 **Automatic Persistence**: Tokens saved to session store after refresh\r\n- 🎯 **Destination-Scoped**: Each refresher is bound to specific destination\r\n\r\n## Configuration\r\n\r\n### Environment Variables\r\n\r\n#### Configuration Variables\r\n\r\n- `AUTH_BROKER_PATH` - Colon/semicolon-separated paths for searching `.env` and `.json` files (default: current working directory)\r\n\r\n#### Debugging Variables\r\n\r\n- `DEBUG_BROKER` - Enable debug logging for `auth-broker` package (short name)\r\n  - Set to `true` to enable logging (default: `false`)\r\n  - When enabled, logs authentication steps, token operations, and error details\r\n  - Can be explicitly disabled by setting to `false`\r\n  - Example: `DEBUG_BROKER=true npm test`\r\n  \r\n- `DEBUG_AUTH_BROKER` - Long name (backward compatibility)\r\n  - Same as `DEBUG_BROKER`, but longer name\r\n  - Example: `DEBUG_AUTH_BROKER=true npm test`\r\n  \r\n- `LOG_LEVEL` - Control log verbosity level\r\n  - Values: `debug`, `info`, `warn`, `error` (default: `info`)\r\n  - `debug` - All messages including detailed debug information\r\n  - `info` - Informational messages, warnings, and errors\r\n  - `warn` - Warnings and errors only\r\n  - `error` - Errors only\r\n  - Example: `LOG_LEVEL=debug DEBUG_BROKER=true npm test`\r\n\r\n- `DEBUG` - Alternative way to enable debugging\r\n  - Set to `true` to enable all debug logging\r\n  - Or set to a string containing `broker` or `auth-broker` to enable only this package\r\n  - Example: `DEBUG=true npm test` or `DEBUG=broker npm test` or `DEBUG=auth-broker npm test`\r\n\r\n**Note**: For debugging related packages:\r\n- `DEBUG_STORES` (short) or `DEBUG_AUTH_STORES` (long) - Enable logging for `@mcp-abap-adt/auth-stores` package\r\n- `DEBUG_PROVIDER` (short) or `DEBUG_AUTH_PROVIDERS` (long) - Enable logging for `@mcp-abap-adt/auth-providers` package\r\n\r\n**Legacy Support**: `DEBUG_AUTH_LOG` is still supported for backward compatibility (equivalent to `DEBUG_BROKER=true LOG_LEVEL=debug`)\r\n\r\n### Logging Features\r\n\r\nWhen logging is enabled (via `DEBUG_BROKER=true` or `DEBUG_AUTH_BROKER=true`), the broker provides detailed structured logging:\r\n\r\n**What is logged:**\r\n- **Broker initialization**: Configuration details, stores, token provider, browser settings\r\n- **Token retrieval**: Session state checks, token presence, refresh token availability\r\n- **Token operations**: Token requests via provider, received tokens with expiration information\r\n- **Token persistence**: Saving tokens to session with formatted token values and expiration dates\r\n- **Error context**: Detailed error information with file paths, error codes, missing fields\r\n\r\n**Logging Features:**\r\n- **Token Formatting**: Tokens are logged in truncated format (first 25 and last 25 characters, skipping middle) for security and readability\r\n- **Date Formatting**: Expiration dates are logged in readable format (e.g., \"2025-12-25 19:21:27 UTC\") instead of raw timestamps\r\n- **Structured Logging**: Uses `DefaultLogger` from `@mcp-abap-adt/logger` for proper formatting with icons and level prefixes\r\n- **Log Levels**: Controlled via `LOG_LEVEL` or `AUTH_LOG_LEVEL` environment variable (error, warn, info, debug)\r\n\r\nExample output with `DEBUG_BROKER=true LOG_LEVEL=info`:\r\n```\r\n[INFO] ℹ️ [AUTH-BROKER] Broker initialized: hasServiceKeyStore(true), hasSessionStore(true), hasTokenProvider(true), browser(system), allowBrowserAuth(true)\r\n[INFO] ℹ️ [AUTH-BROKER] Getting token for destination: TRIAL\r\n[INFO] ℹ️ [AUTH-BROKER] Session check for TRIAL: hasToken(true), hasAuthConfig(true), hasServiceUrl(true), serviceUrl(https://...abap...), authorizationToken(eyJ0eXAiOiJKV1QiLCJqaWQiO...Q5ti7aYmEzItIDuLp7axNYo6w), hasRefreshToken(true)\r\n[INFO] ℹ️ [AUTH-BROKER] Requesting tokens for TRIAL via session\r\n[INFO] ℹ️ [AUTH-BROKER] Tokens received for TRIAL: authorizationToken(eyJ0eXAiOiJKV1QiLCJqaWQiO...Q5ti7aYmEzItIDuLp7axNYo6w), hasRefreshToken(true), authType(authorization_code), expiresIn(43199), expiresAt(2025-12-26 20:15:30 UTC)\r\n[INFO] ℹ️ [AUTH-BROKER] Saving tokens to session for TRIAL: serviceUrl(https://...abap...), authorizationToken(eyJ0eXAiOiJKV1QiLCJqaWQiO...Q5ti7aYmEzItIDuLp7axNYo6w), hasRefreshToken(true), expiresAt(2025-12-26 20:15:30 UTC)\r\n[INFO] ℹ️ [AUTH-BROKER] Token retrieved for TRIAL (via session): authorizationToken(eyJ0eXAiOiJKV1QiLCJqaWQiO...Q5ti7aYmEzItIDuLp7axNYo6w)\r\n```\r\n\r\n**Note**: Logging only works when a logger is explicitly provided to the broker constructor. The broker will not output anything to console if no logger is passed.\r\n\r\n### File Structure\r\n\r\n#### Environment File for ABAP (`{destination}.env`)\r\n\r\nFor ABAP connections, use `SAP_*` environment variables:\r\n\r\n```env\r\nSAP_URL=https://your-system.abap.us10.hana.ondemand.com\r\nSAP_CLIENT=100\r\nSAP_JWT_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...\r\nSAP_REFRESH_TOKEN=refresh_token_string\r\nSAP_UAA_URL=https://your-account.authentication.us10.hana.ondemand.com\r\nSAP_UAA_CLIENT_ID=client_id\r\nSAP_UAA_CLIENT_SECRET=client_secret\r\n```\r\n\r\n#### Environment File for XSUAA (`{destination}.env`)\r\n\r\nFor XSUAA connections (reduced scope), use `XSUAA_*` environment variables:\r\n\r\n```env\r\nXSUAA_MCP_URL=https://your-mcp-server.cfapps.eu10.hana.ondemand.com\r\nXSUAA_JWT_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...\r\nXSUAA_REFRESH_TOKEN=refresh_token_string\r\nXSUAA_UAA_URL=https://your-account.authentication.eu10.hana.ondemand.com\r\nXSUAA_UAA_CLIENT_ID=client_id\r\nXSUAA_UAA_CLIENT_SECRET=client_secret\r\n```\r\n\r\n**Note**: `XSUAA_MCP_URL` is optional - it's not part of authentication, only needed for making requests. The token and UAA credentials are sufficient for authentication.\r\n\r\n#### Environment File for BTP (`{destination}.env`)\r\n\r\nFor BTP connections (full scope for ABAP systems), use `BTP_*` environment variables:\r\n\r\n```env\r\nBTP_ABAP_URL=https://your-system.abap.us10.hana.ondemand.com\r\nBTP_JWT_TOKEN=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...\r\nBTP_REFRESH_TOKEN=refresh_token_string\r\nBTP_UAA_URL=https://your-account.authentication.eu10.hana.ondemand.com\r\nBTP_UAA_CLIENT_ID=client_id\r\nBTP_UAA_CLIENT_SECRET=client_secret\r\nBTP_SAP_CLIENT=100\r\nBTP_LANGUAGE=EN\r\n```\r\n\r\n**Note**: `BTP_ABAP_URL` is required - it's the ABAP system URL. All parameters (except tokens) come from service key.\r\n\r\n#### Service Key File for ABAP (`{destination}.json`)\r\n\r\nStandard ABAP service key format:\r\n\r\n```json\r\n{\r\n  \"url\": \"https://your-system.abap.us10.hana.ondemand.com\",\r\n  \"uaa\": {\r\n    \"url\": \"https://your-account.authentication.us10.hana.ondemand.com\",\r\n    \"clientid\": \"your_client_id\",\r\n    \"clientsecret\": \"your_client_secret\"\r\n  }\r\n}\r\n```\r\n\r\n#### Service Key File for XSUAA (`{destination}.json`)\r\n\r\nDirect XSUAA service key format (from BTP):\r\n\r\n```json\r\n{\r\n  \"url\": \"https://your-account.authentication.eu10.hana.ondemand.com\",\r\n  \"apiurl\": \"https://api.authentication.eu10.hana.ondemand.com\",\r\n  \"clientid\": \"your_client_id\",\r\n  \"clientsecret\": \"your_client_secret\"\r\n}\r\n```\r\n\r\n**Note**: For XSUAA service keys, `apiurl` is prioritized over `url` for UAA authorization if present.\r\n\r\n## XSUAA vs BTP Authentication\r\n\r\nThis package supports two types of BTP authentication:\r\n\r\n### XSUAA (Reduced Scope)\r\n- **Purpose**: Access BTP services with limited scopes\r\n- **Service Key**: Contains only UAA credentials (no ABAP URL)\r\n- **Session Store**: `XsuaaSessionStore` (uses `XSUAA_*` environment variables)\r\n- **Authentication**: Client credentials grant type (no browser required)\r\n- **MCP URL**: Optional, provided separately (from YAML config `mcp_url`, parameter, or request header)\r\n- **Use Case**: Accessing BTP services like MCP servers with reduced permissions\r\n\r\n### BTP (Full Scope for ABAP)\r\n- **Purpose**: Access ABAP systems with full roles and scopes\r\n- **Service Key**: Contains UAA credentials and ABAP URL\r\n- **Session Store**: `BtpSessionStore` (uses `BTP_*` environment variables)\r\n- **Authentication**: Browser-based OAuth2 (like ABAP) or refresh token\r\n- **ABAP URL**: Required, from service key or YAML configuration\r\n- **Use Case**: Accessing ABAP systems in BTP with full permissions\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 (e.g., `AbapSessionStore`, `AuthorizationCodeProvider`)\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**: `AuthBroker` is decoupled from concrete implementations\r\n- **Flexibility**: New implementations can be added without modifying `AuthBroker`\r\n- **Testability**: Easy to mock dependencies for testing\r\n- **Maintainability**: Changes to implementations don't affect `AuthBroker`\r\n\r\n### Package Responsibilities\r\n\r\nThe `@mcp-abap-adt/auth-broker` package defines **interfaces** and provides **orchestration logic** for authentication. It does **not** implement concrete storage or token acquisition mechanisms - these are provided by separate packages (`@mcp-abap-adt/auth-stores`, `@mcp-abap-adt/auth-providers`).\r\n\r\n#### What AuthBroker Does\r\n\r\n- **Orchestrates authentication flows**: Coordinates token retrieval, validation, and refresh using provided stores and providers\r\n- **Manages token lifecycle**: Handles token caching, validation, and automatic refresh\r\n- **Works with interfaces only**: Uses `IServiceKeyStore`, `ISessionStore`, and `ITokenProvider` interfaces without knowing concrete implementations\r\n- **Delegates to providers**: Calls `tokenProvider.getTokens()` to obtain tokens\r\n- **Delegates to stores**: Saves tokens and connection configuration to `sessionStore`\r\n\r\n#### What AuthBroker Does NOT Do\r\n\r\n- **Does NOT implement storage**: File I/O, parsing, and storage logic are handled by concrete store implementations from `@mcp-abap-adt/auth-stores`\r\n- **Does NOT implement token acquisition**: OAuth2 flows, refresh token logic, and client credentials are handled by concrete provider implementations from `@mcp-abap-adt/auth-providers`\r\n\r\n### Consumer Responsibilities\r\n\r\nThe **consumer** (application using `AuthBroker`) is responsible for:\r\n\r\n1. **Selecting appropriate implementations**: Choose the correct `IServiceKeyStore`, `ISessionStore`, and `ITokenProvider` implementations based on the use case:\r\n   - **ABAP systems**: Use `AbapServiceKeyStore`, `AbapSessionStore` (or `SafeAbapSessionStore`), and `AuthorizationCodeProvider`\r\n   - **BTP systems**: Use `AbapServiceKeyStore`, `BtpSessionStore` (or `SafeBtpSessionStore`), and `AuthorizationCodeProvider`\r\n   - **XSUAA services**: Use `XsuaaServiceKeyStore`, `XsuaaSessionStore` (or `SafeXsuaaSessionStore`), and `ClientCredentialsProvider`\r\n\r\n2. **Ensuring complete configuration**: If a session store requires `serviceUrl` (e.g., `AbapSessionStore` requires `sapUrl`), the consumer must ensure that:\r\n   - The session is created with `serviceUrl` before calling `AuthBroker.getToken()`, OR\r\n   - The session store implementation handles `serviceUrl` retrieval internally (e.g., from `serviceKeyStore`)\r\n\r\n3. **Understanding store requirements**: Different session store implementations have different requirements:\r\n   - `AbapSessionStore`: Requires `sapUrl` (maps to `serviceUrl` in `IConnectionConfig`)\r\n   - `BtpSessionStore`: Does not require `serviceUrl` (uses `mcpUrl` instead)\r\n   - `XsuaaSessionStore`: Does not require `serviceUrl` (MCP URL is optional)\r\n\r\n### Store Responsibilities\r\n\r\nConcrete `ISessionStore` implementations are responsible for:\r\n\r\n- **Handling their own data format**: Each store knows its internal data format (e.g., `AbapSessionData`, `BtpBaseSessionData`)\r\n- **Converting between formats**: Converting between `IConfig`/`IConnectionConfig` and internal storage format\r\n- **Managing required fields**: If a store requires `serviceUrl` (e.g., `AbapSessionStore`), it should:\r\n  - Retrieve it from `serviceKeyStore` if not provided in `IConnectionConfig`, OR\r\n  - Use existing value from current session if available, OR\r\n  - Throw an error if neither is available (depending on implementation)\r\n\r\n### Provider Responsibilities\r\n\r\nConcrete `ITokenProvider` implementations are responsible for:\r\n\r\n- **Obtaining tokens**: Using OAuth2 flows, refresh tokens, or client credentials to obtain JWT tokens\r\n- **Managing token lifecycle**: Caching, validating, refreshing, and re-authenticating as needed\r\n\r\n### Design Principles\r\n\r\n1. **Interface-Only Communication** (Core Principle): All interactions with external dependencies happen **ONLY through interfaces**. The code knows **NOTHING beyond what is defined in the interfaces** (see [Core Development Principle](#core-development-principle) above)\r\n2. **Dependency Inversion Principle (DIP)**: `AuthBroker` depends on abstractions (`IServiceKeyStore`, `ISessionStore`, `ITokenProvider`), not concrete implementations\r\n3. **Single Responsibility**: Each component has a single, well-defined responsibility:\r\n   - `AuthBroker`: Orchestration and token lifecycle management\r\n   - `ISessionStore`: Session data storage and retrieval\r\n   - `ITokenProvider`: Token acquisition\r\n   - `IServiceKeyStore`: Service key storage and retrieval\r\n4. **Interface Segregation**: Interfaces are focused and minimal, containing only what's necessary for their specific purpose\r\n5. **Open/Closed Principle**: New store and provider implementations can be added without modifying `AuthBroker`\r\n\r\n## API\r\n\r\n### `AuthBroker`\r\n\r\n#### Constructor\r\n\r\n```typescript\r\nnew AuthBroker(\r\n  config: {\r\n    sessionStore: ISessionStore;        // required\r\n    serviceKeyStore?: IServiceKeyStore; // optional\r\n    tokenProvider: ITokenProvider;      // required\r\n    allowBrowserAuth?: boolean;         // optional\r\n  }, \r\n  browser?: string, \r\n  logger?: ILogger\r\n)\r\n```\r\n\r\n**Parameters:**\r\n- `config` - Configuration object:\r\n  - `sessionStore` - **Required** - Store for session data. Must contain initial session with `serviceUrl`\r\n  - `serviceKeyStore` - **Optional** - Store for service keys. Only needed for initializing sessions from service keys\r\n  - `tokenProvider` - **Required** - Token provider for token acquisition and refresh\r\n  - `allowBrowserAuth` - **Optional** - When `false`, throws `BROWSER_AUTH_REQUIRED` instead of launching browser auth\r\n- `browser` - Optional browser name for authentication (`chrome`, `edge`, `firefox`, `system`, `headless`, `none`). Default: `system`\r\n  - Use `'headless'` for SSH/remote sessions - logs URL and waits for manual callback\r\n  - Use `'none'` for automated tests - logs URL and rejects immediately\r\n  - For XSUAA, browser is not used (client_credentials grant type) - use `'none'`\r\n- `logger` - Optional logger instance. If not provided, uses no-op logger\r\n\r\n**When to Provide Each Dependency:**\r\n\r\n- **`sessionStore` (required)**: Always required. Must contain initial session with `serviceUrl`\r\n- **`serviceKeyStore` (optional)**: \r\n  - Required if you need to initialize sessions from service keys (Step 0)\r\n  - Not needed if session already contains authorization config and tokens\r\n- **`tokenProvider` (required)**:\r\n  - Used for all token acquisition and refresh flows\r\n  - Must be configured with the destination's auth parameters (e.g., UAA credentials)\r\n\r\n**Available Implementations:**\r\n- **ABAP**: `AbapServiceKeyStore(directory, defaultServiceUrl?, logger?)`, `AbapSessionStore(directory, defaultServiceUrl?, logger?)`, `SafeAbapSessionStore(defaultServiceUrl?, logger?)`, `AuthorizationCodeProvider(...)`\r\n- **XSUAA** (reduced scope): `XsuaaServiceKeyStore(directory, logger?)`, `XsuaaSessionStore(directory, defaultServiceUrl, logger?)`, `SafeXsuaaSessionStore(defaultServiceUrl, logger?)`, `ClientCredentialsProvider(...)`\r\n- **BTP** (full scope for ABAP): `AbapServiceKeyStore(directory, defaultServiceUrl?, logger?)`, `BtpSessionStore(directory, defaultServiceUrl, logger?)`, `SafeBtpSessionStore(defaultServiceUrl, logger?)`, `AuthorizationCodeProvider(...)`\r\n\r\n#### Methods\r\n\r\n##### `getToken(destination: string): Promise<string>`\r\n\r\nGets authentication token for destination. Implements a three-step flow:\r\n\r\n**Step 0: Initialize Session with Token (if needed)**\r\n- Checks if session has `authorizationToken` and authorization config\r\n- If both are missing and `serviceKeyStore` is available:\r\n  - Loads authorization config from service key\r\n  - Uses `tokenProvider.getTokens()` to obtain tokens\r\n  - Persists tokens to session\r\n- Otherwise → proceeds to Step 1\r\n\r\n**Step 1: Token Refresh / Re-Auth**\r\n- If session has authorization config:\r\n  - Uses `tokenProvider.getTokens()` to refresh or re-authenticate\r\n  - Persists tokens to session\r\n  - Returns new token\r\n- If that fails (or no session auth config) and `serviceKeyStore` is available:\r\n  - Loads authorization config from service key\r\n  - Uses `tokenProvider.getTokens()` to obtain tokens\r\n  - Persists tokens to session\r\n- If all failed → throws error\r\n\r\n**Important Notes:**\r\n- All authentication is handled by the injected provider (authorization_code or client_credentials).\r\n- `tokenProvider` is required for all token acquisition and refresh flows.\r\n- **Broker always calls `provider.getTokens()`** - provider handles token lifecycle internally (validation, refresh, login). Consumer doesn't need to know about token issues.\r\n- Provider decides whether to return cached token, refresh, or perform login based on token state.\r\n- **Store errors are handled gracefully**: If service key files are missing or malformed, the broker logs the error and continues with fallback mechanisms (session store data or provider-based auth)\r\n\r\n##### Error Handling\r\n\r\nThe broker implements comprehensive error handling for all external operations, treating all injected dependencies as untrusted:\r\n\r\n```typescript\r\nimport { STORE_ERROR_CODES } from '@mcp-abap-adt/interfaces';\r\n\r\ntry {\r\n  const token = await broker.getToken('TRIAL');\r\n} catch (error: any) {\r\n  // Broker handles errors internally where possible, but critical errors propagate\r\n  console.error('Failed to get token:', error.message);\r\n}\r\n```\r\n\r\n**Error Categories** (handled by broker with graceful degradation):\r\n\r\n**1. SessionStore Errors** (reading session files):\r\n- `STORE_ERROR_CODES.FILE_NOT_FOUND` - Session file missing (logged, tries serviceKeyStore fallback)\r\n- `STORE_ERROR_CODES.PARSE_ERROR` - Corrupted session file (logged with file path, tries fallback)\r\n- Write failures when saving tokens (logged and thrown - critical)\r\n\r\n**2. ServiceKeyStore Errors** (reading service key files):\r\n- `STORE_ERROR_CODES.FILE_NOT_FOUND` - Service key file missing (logged, continues with session data)\r\n- `STORE_ERROR_CODES.PARSE_ERROR` - Invalid JSON in service key (logged with file path and cause)\r\n- `STORE_ERROR_CODES.INVALID_CONFIG` - Missing required fields (logged with missing field names)\r\n- `STORE_ERROR_CODES.STORAGE_ERROR` - Permission/write errors (logged)\r\n\r\n**3. TokenProvider Errors** (network operations):\r\n- Network errors: `ECONNREFUSED`, `ETIMEDOUT`, `ENOTFOUND` (logged, throws with descriptive message)\r\n- `VALIDATION_ERROR` - Missing required auth fields (logged with field names, throws)\r\n- `BROWSER_AUTH_ERROR` - Browser authentication failed or cancelled (logged, throws)\r\n- `REFRESH_ERROR` - Token refresh failed at UAA server (logged, throws)\r\n\r\n**4. Browser Auth Disabled Errors** (when `allowBrowserAuth: false`):\r\n- `BROWSER_AUTH_REQUIRED` - Browser authentication is required but disabled. Thrown when:\r\n  - **Step 0**: No token and no UAA credentials in session, service key exists but browser auth needed\r\n  - **Step 2b**: Refresh token expired/invalid and browser auth needed for new token\r\n  - Error includes `destination` property for context\r\n  - Use case: Non-interactive environments (MCP stdio, Cline) where browser cannot open\r\n\r\n**Defensive Design Principles:**\r\n- **All external operations wrapped in try-catch**: Files may be missing/corrupted, network may fail\r\n- **Graceful degradation**: Store errors trigger fallback mechanisms (serviceKey → session → provider)\r\n- **Detailed error context**: Logs include file paths, error codes, missing fields for debugging\r\n- **Fail-fast for critical errors**: Write failures and provider errors throw immediately (cannot recover)\r\n- **No assumptions about injected dependencies**: All stores/providers treated as potentially unreliable\r\n\r\nExample error scenarios handled:\r\n- Session file deleted mid-operation → uses service key\r\n- Service key has invalid JSON → logs parse error, uses session data\r\n- Network timeout during token refresh → logs timeout, throws descriptive error\r\n- File permission denied → logs error with file path, throws\r\n\r\n##### `refreshToken(destination: string): Promise<string>`\r\n\r\nForce refresh token for destination. Calls `getToken()` to run the full refresh flow and persist updated tokens.\r\n\r\n##### `clearCache(destination: string): void`\r\n\r\nClear cached token for specific destination.\r\n\r\n##### `clearAllCache(): void`\r\n\r\nClear all cached tokens.\r\n\r\n### Token Providers\r\n\r\nThe package uses the `ITokenProvider` interface for token acquisition. Provider implementations live in `@mcp-abap-adt/auth-providers`:\r\n\r\n- **`ClientCredentialsProvider`** - For XSUAA authentication (reduced scope)\r\n  - Uses client_credentials grant type\r\n  - No browser interaction required\r\n  - No refresh token provided\r\n\r\n- **`AuthorizationCodeProvider`** - For BTP/ABAP authentication (full scope)\r\n  - Constructor accepts optional `browserAuthPort?: number` parameter (default: 3001)\r\n  - Automatically finds an available port if the requested port is in use (prevents `EADDRINUSE` errors)\r\n  - Server properly closes all connections and frees the port after authentication completes\r\n  - Use custom port to avoid conflicts when running alongside other services (e.g., proxy server)\r\n  - Uses browser-based OAuth2 flow (if no refresh token)\r\n  - Uses refresh token if available\r\n  - Provides refresh token for future use\r\n\r\n**Example Usage:**\r\n\r\n```typescript\r\nimport {\r\n  AuthBroker,\r\n  XsuaaServiceKeyStore,\r\n  XsuaaSessionStore,\r\n  AbapServiceKeyStore,\r\n  BtpSessionStore\r\n} from '@mcp-abap-adt/auth-broker';\r\nimport {\r\n  ClientCredentialsProvider,\r\n  AuthorizationCodeProvider,\r\n} from '@mcp-abap-adt/auth-providers';\r\n\r\n// XSUAA authentication\r\nconst xsuaaBroker = new AuthBroker({\r\n  sessionStore: new XsuaaSessionStore('/path/to/sessions', 'https://mcp.example.com'),\r\n  tokenProvider: new ClientCredentialsProvider({\r\n    uaaUrl: 'https://auth.example.com',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n  }),\r\n});\r\n\r\n// XSUAA authentication - with service key initialization\r\nconst xsuaaBrokerWithServiceKey = new AuthBroker({\r\n  sessionStore: new XsuaaSessionStore('/path/to/sessions', 'https://mcp.example.com'),\r\n  serviceKeyStore: new XsuaaServiceKeyStore('/path/to/keys'),\r\n  tokenProvider: new ClientCredentialsProvider({\r\n    uaaUrl: 'https://auth.example.com',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n  }),\r\n}, 'none');\r\n\r\n// BTP authentication\r\nconst btpBroker = new AuthBroker({\r\n  sessionStore: new BtpSessionStore('/path/to/sessions', 'https://abap.example.com'),\r\n  tokenProvider: new AuthorizationCodeProvider({\r\n    uaaUrl: 'https://auth.example.com',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n    browser: 'system',\r\n  }),\r\n});\r\n\r\n// BTP authentication - with service key and provider (for browser auth)\r\nconst btpBrokerFull = new AuthBroker({\r\n  sessionStore: new BtpSessionStore('/path/to/sessions', 'https://abap.example.com'),\r\n  serviceKeyStore: new AbapServiceKeyStore('/path/to/keys'),\r\n  tokenProvider: new AuthorizationCodeProvider({\r\n    uaaUrl: 'https://auth.example.com',\r\n    clientId: '...',\r\n    clientSecret: '...',\r\n    browser: 'system',\r\n  }),\r\n});\r\n```\r\n\r\n### CLI: mcp-auth\r\n\r\nGenerate or refresh `.env`/JSON output using AuthBroker + stores:\r\n\r\n```bash\r\nmcp-auth <auth-code|oidc|saml2-pure|saml2-bearer> [options]\r\nmcp-auth --service-key <path> --output <path> [--env <path>] [--type abap|xsuaa] [--credential] [--browser auto|none|system|chrome|edge|firefox] [--format json|env]\r\n```\r\n\r\n**Note**: The published CLI is compiled to `dist/bin` and does not require `tsx` at runtime. For repo usage, run `npm install` and `npm run build`.\r\n\r\n**Authentication Flow:**\r\n- Default: `authorization_code` (browser-based OAuth2)\r\n- `--credential`: `client_credentials` (clientId/clientSecret, no browser)\r\n\r\n**Browser Options (for authorization_code):**\r\n- `auto` (default): Try to open browser, fallback to showing URL\r\n- `none`: Show URL in console and wait for callback (no browser)\r\n- `system/chrome/edge/firefox`: Open specific browser\r\n\r\n**Examples:**\r\n```bash\r\n# Auth code (default via service key)\r\nmcp-auth auth-code --service-key ./abap.json --output ./abap.env --type abap\r\n\r\n# OIDC SSO (device flow example)\r\nmcp-auth oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa\r\n\r\n# SAML2 pure (cookie)\r\nmcp-auth saml2-pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --output ./saml.env --type abap\r\n\r\n# SAML2 bearer (in progress, requires --dev)\r\nmcp-auth saml2-bearer --dev --service-key ./mcp.json --assertion <base64> --output ./sso.env --type xsuaa\r\n\r\n# ABAP: authorization_code (default, opens browser)\r\nmcp-auth --service-key ./abap.json --output ./abap.env --type abap\r\n\r\n# ABAP: authorization_code (show URL in console, no browser)\r\nmcp-auth --service-key ./abap.json --output ./abap.env --type abap --browser none\r\n\r\n# XSUAA: authorization_code (default)\r\nmcp-auth --service-key ./mcp.json --output ./mcp.env --type xsuaa\r\n\r\n# XSUAA: client_credentials (special cases)\r\nmcp-auth --service-key ./mcp.json --output ./mcp.env --type xsuaa --credential\r\n\r\n# Using existing .env for refresh token\r\nmcp-auth --env ./mcp.env --service-key ./mcp.json --output ./mcp.env --type xsuaa\r\n```\r\n\r\n### CLI: mcp-sso\r\n\r\nGet tokens via SSO providers (OIDC/SAML) and generate `.env`/JSON output:\r\n\r\n```bash\r\nmcp-sso <oidc|saml2|bearer> [options]\r\nmcp-sso --protocol <oidc|saml2> --flow <flow> --output <path> [--type abap|xsuaa] [--format env|json] [--env <path>] [--config <path>]\r\n```\r\n\r\n**Supported flows:**\r\n- OIDC: `browser`, `device`, `password`, `token_exchange`\r\n- SAML2: `bearer`, `pure`\r\n\r\n**Examples:**\r\n```bash\r\n# OIDC browser flow\r\nmcp-sso oidc --flow browser --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa\r\n\r\n# OIDC browser flow (manual code / OOB)\r\nmcp-sso oidc --flow browser --token-endpoint https://issuer/token --client-id my-client --code <auth_code> --redirect-uri urn:ietf:wg:oauth:2.0:oob --output ./sso.env --type xsuaa\r\n\r\n# OIDC device flow\r\nmcp-sso oidc --flow device --issuer https://issuer --client-id my-client --output ./sso.env --type xsuaa\r\n\r\n# OIDC password flow\r\nmcp-sso oidc --flow password --token-endpoint https://issuer/oauth/token --client-id my-client --username user --password pass --output ./sso.env --type xsuaa\r\n\r\n# OIDC token exchange\r\nmcp-sso oidc --flow token_exchange --issuer https://issuer --client-id my-client --subject-token <token> --output ./sso.env --type xsuaa\r\n\r\n# SAML bearer flow (assertion -> token)\r\nmcp-sso bearer --idp-sso-url https://idp/sso --sp-entity-id my-sp --token-endpoint https://uaa.example/oauth/token --assertion <base64> --output ./sso.env --type xsuaa\r\n\r\n# SAML pure flow (cookie)\r\nmcp-sso saml2 --flow pure --idp-sso-url https://idp/sso --sp-entity-id my-sp --assertion <base64> --cookie \"SAP_SESSION=...\" --output ./sso.env --type abap\r\n```\r\n\r\n**SAML token alias (XSUAA):**\r\nIf your IdP requires the token alias endpoint, pass SAML metadata XML:\r\n\r\n```bash\r\nmcp-sso bearer --saml-metadata ./saml-sp.xml --assertion <base64> --service-key ./service-key.json --output ./sso.env --type xsuaa\r\n```\r\n\r\n### Local Keycloak (OIDC + SAML Tests)\r\n\r\nFor local testing of `mcp-sso`, a ready-to-run Keycloak setup is included\r\n(OIDC browser/password/device + SAML assertion capture).\r\n\r\n```bash\r\ncd tests/keycloak\r\ndocker compose up -d\r\n```\r\n\r\nThen use:\r\n```bash\r\nnode dist/bin/mcp-sso.js \\\r\n  oidc \\\r\n  --flow browser \\\r\n  --issuer http://localhost:8080/realms/mcp-sso \\\r\n  --client-id mcp-sso-cli \\\r\n  --scopes openid,profile,email \\\r\n  --output /tmp/keycloak.env \\\r\n  --type xsuaa\r\n```\r\n\r\nSee `tests/keycloak/README.md` for device flow and SAML examples.\r\n\r\n### XSUAA Demo (CAP)\r\n\r\nA minimal CAP app for testing XSUAA flows is included at `tests/sso-demo`.\r\nIt enables `authorization_code` and `saml2-bearer` grant types and provides a\r\nsimple `CatalogService`. See `tests/sso-demo/readme.md` for deploy steps.\r\n\r\n**Config file:**\r\nYou can pass a JSON file with provider config:\r\n\r\n```json\r\n{\r\n  \"protocol\": \"oidc\",\r\n  \"flow\": \"device\",\r\n  \"issuerUrl\": \"https://issuer\",\r\n  \"clientId\": \"my-client\",\r\n  \"scopes\": [\"openid\", \"profile\"]\r\n}\r\n```\r\n\r\n### Utility Script\r\n\r\nGenerate `.env` files from service keys:\r\n\r\n```bash\r\nnpm run generate-env <destination> [service-key-path] [session-path]\r\n```\r\n\r\n## Testing\r\n\r\nTests are located in `src/__tests__/` and use Jest as the test runner.\r\n\r\n### Running Tests\r\n\r\n```bash\r\n# Run all tests\r\nnpm test\r\n\r\n# Run specific test file (all tests in that file)\r\nnpm test -- getToken.test.ts\r\nnpm test -- refreshToken.test.ts\r\n\r\n# Run specific test by name/pattern\r\nnpm test -- getToken.test.ts -t \"Test 1\"\r\nnpm test -- getToken.test.ts -t \"Test 2\"\r\nnpm test -- getToken.test.ts -t \"Test 3\"\r\n\r\n# Run test group (e.g., all getToken tests)\r\nnpm test -- getToken.test.ts\r\n\r\n# Note: Test 2 requires Test 1 to pass first (test1Passed flag)\r\n# To run Test 2 alone, you may need to run all tests in the file:\r\nnpm test -- getToken.test.ts\r\n```\r\n\r\n### Test Structure\r\n\r\nTests are designed to run sequentially (guaranteed by `maxWorkers: 1` and `maxConcurrency: 1` in `jest.config.js`):\r\n\r\n1. **Test 1**: Verifies error handling for non-existent destination (`NO_EXISTS`)\r\n   - Requires: `NO_EXISTS.json` should NOT exist\r\n   \r\n2. **Test 2**: Tests browser authentication when service key exists but `.env` file doesn't\r\n   - Requires: `TRIAL.json` must exist, `TRIAL.env` should NOT exist\r\n   - Will open browser for OAuth authentication\r\n   \r\n3. **Test 3**: Tests token refresh using existing `.env` file\r\n   - Requires: `TRIAL.json` and `TRIAL.env` must exist\r\n   - Can run independently if `.env` file exists (created manually or by Test 2)\r\n\r\n### Test Setup\r\n\r\n1. Copy `tests/test-config.yaml.template` to `tests/test-config.yaml`\r\n2. Fill in configuration values (paths, destinations, MCP URL for XSUAA)\r\n3. Place service key files in configured `service_keys_dir`:\r\n   - `{destination}.json` for ABAP tests (e.g., `trial.json`)\r\n   - `{btp_destination}.json` for XSUAA tests (e.g., `btp.json`)\r\n\r\nTests will automatically skip if required files are missing or configuration contains placeholders.\r\n\r\n## Documentation\r\n\r\nComplete documentation is available in the [`docs/`](docs/) directory:\r\n\r\n- **[Architecture](docs/architecture/ARCHITECTURE.md)** - System architecture and design decisions\r\n- **[Development](docs/development/)** - Testing methodology and development roadmap\r\n- **[Development Roadmap](docs/development/DEVELOPMENT_ROADMAP.md)** - Development roadmap and future plans\r\n- **[Installation](docs/installing/INSTALLATION.md)** - Installation and setup guide\r\n- **[Usage](docs/using/USAGE.md)** - API reference and usage examples\r\n\r\nSee [docs/README.md](docs/README.md) for the complete documentation index.\r\n\r\n## Contributors\r\n\r\nThank you to all contributors! See [CONTRIBUTORS.md](CONTRIBUTORS.md) for the complete list.\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md","_rev":"1-52b8778cb64a99c8000ff89bf5c0c1d2"}