@lanonasis/oauth-client 1.0.2 → 1.2.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/README.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # @lanonasis/oauth-client
2
2
 
3
- Drop-in OAuth + MCP connectivity client for the Lanonasis ecosystem. Handles browser/desktop/terminal flows, token lifecycle, secure (hashed) API key storage, and MCP WebSocket/SSE connections so other projects can integrate without re-implementing auth.
3
+ Drop-in OAuth + API Key authentication client for the Lanonasis ecosystem. Supports dual authentication modes: OAuth2 PKCE flow for pre-registered clients and direct API key authentication for dashboard users. Handles browser/desktop/terminal flows, token lifecycle, secure storage, and MCP WebSocket/SSE connections.
4
4
 
5
5
  ## Features
6
+ - **Dual Authentication**: OAuth2 PKCE flow OR direct API key authentication
6
7
  - OAuth flows for terminal and desktop (Electron-friendly) environments
8
+ - API key authentication for new users with dashboard-generated keys
7
9
  - Token storage with secure backends (Keytar, encrypted files, Electron secure store, mobile secure storage, WebCrypto in browsers)
8
10
  - API key storage that normalizes to SHA-256 digests before persisting
9
11
  - MCP client that connects over WebSocket (`/ws`) or SSE (`/sse`) with auto-refreshing tokens
12
+ - Automatic auth mode detection based on configuration
10
13
  - ESM + CJS bundles with TypeScript types
11
14
 
12
15
  ## Installation
@@ -17,6 +20,24 @@ bun add @lanonasis/oauth-client
17
20
  ```
18
21
 
19
22
  ## Quick Start
23
+
24
+ ### Option 1: API Key Authentication (Recommended for New Users)
25
+ ```ts
26
+ import { MCPClient } from '@lanonasis/oauth-client';
27
+
28
+ // Simple API key mode - perfect for dashboard users
29
+ const client = new MCPClient({
30
+ apiKey: 'lano_abc123xyz', // Get from dashboard
31
+ mcpEndpoint: 'wss://mcp.lanonasis.com'
32
+ });
33
+
34
+ await client.connect(); // Automatically uses API key auth
35
+
36
+ // Make requests
37
+ const memories = await client.searchMemories('test query');
38
+ ```
39
+
40
+ ### Option 2: OAuth Authentication (For Pre-registered Clients)
20
41
  ```ts
21
42
  import { MCPClient } from '@lanonasis/oauth-client';
22
43
 
@@ -27,7 +48,7 @@ const client = new MCPClient({
27
48
  scope: 'mcp:read mcp:write api_keys:manage'
28
49
  });
29
50
 
30
- await client.connect(); // handles auth, refresh, and MCP connect
51
+ await client.connect(); // Triggers OAuth flow, handles refresh
31
52
  ```
32
53
 
33
54
  ### Terminal OAuth flow
@@ -74,12 +95,20 @@ const hashed = await apiKeys.getApiKey(); // returns sha256 hex digest
74
95
  ```
75
96
 
76
97
  ## Configuration
98
+
99
+ ### API Key Mode
100
+ - `apiKey` (required): Your dashboard-generated API key (starts with `lano_`).
101
+ - `mcpEndpoint` (optional): defaults to `wss://mcp.lanonasis.com` and can also be `https://...` for SSE.
102
+
103
+ ### OAuth Mode
77
104
  - `clientId` (required): OAuth client id issued by Lanonasis Auth.
78
105
  - `authBaseUrl` (optional): defaults to `https://auth.lanonasis.com`.
79
106
  - `mcpEndpoint` (optional): defaults to `wss://mcp.lanonasis.com` and can also be `https://...` for SSE.
80
107
  - `scope` (optional): defaults to `mcp:read mcp:write api_keys:manage`.
81
108
  - `autoRefresh` (MCPClient): refresh tokens 5 minutes before expiry (default `true`).
82
109
 
110
+ **Note**: Auth mode is automatically detected - if you provide `apiKey`, it uses API key authentication. If you provide `clientId`, it uses OAuth.
111
+
83
112
  ## Publishing (maintainers)
84
113
  1) Build artifacts: `npm install && npm run build`
85
114
  2) Verify contents: ensure `dist`, `README.md`, `LICENSE` are present.
package/dist/index.cjs CHANGED
@@ -440,6 +440,9 @@ var TokenStorage = class {
440
440
  }
441
441
  }
442
442
  isTokenExpired(tokens) {
443
+ if (tokens.token_type === "api-key" || tokens.expires_in === 0) {
444
+ return false;
445
+ }
443
446
  if (!tokens.expires_in) return false;
444
447
  if (!tokens.issued_at) {
445
448
  console.warn("Token missing issued_at timestamp, treating as expired");
@@ -1071,9 +1074,66 @@ var ApiKeyStorage = class {
1071
1074
  }
1072
1075
  };
1073
1076
 
1077
+ // src/flows/apikey-flow.ts
1078
+ var APIKeyFlow = class extends BaseOAuthFlow {
1079
+ constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
1080
+ super({
1081
+ clientId: "api-key-client",
1082
+ authBaseUrl
1083
+ });
1084
+ this.apiKey = apiKey;
1085
+ }
1086
+ /**
1087
+ * "Authenticate" by returning the API key as a virtual token
1088
+ * The API key will be used directly in request headers
1089
+ */
1090
+ async authenticate() {
1091
+ if (!this.apiKey || !this.apiKey.startsWith("lano_") && !this.apiKey.startsWith("vx_")) {
1092
+ throw new Error(
1093
+ 'Invalid API key format. Must start with "lano_" or "vx_". Please regenerate your API key from the dashboard.'
1094
+ );
1095
+ }
1096
+ if (this.apiKey.startsWith("vx_")) {
1097
+ console.warn(
1098
+ '\u26A0\uFE0F DEPRECATION WARNING: API keys with "vx_" prefix are deprecated and will stop working soon. Please regenerate your API key from the dashboard to get a "lano_" prefixed key. Support for "vx_" keys will be removed in a future version.'
1099
+ );
1100
+ }
1101
+ return {
1102
+ access_token: this.apiKey,
1103
+ token_type: "api-key",
1104
+ expires_in: 0,
1105
+ // API keys don't expire
1106
+ issued_at: Date.now()
1107
+ };
1108
+ }
1109
+ /**
1110
+ * API keys don't need refresh
1111
+ */
1112
+ async refreshToken(refreshToken) {
1113
+ throw new Error("API keys do not support token refresh");
1114
+ }
1115
+ /**
1116
+ * Optional: Validate API key by making a test request
1117
+ */
1118
+ async validateAPIKey() {
1119
+ try {
1120
+ const response = await fetch(`${this.config.authBaseUrl}/api/v1/health`, {
1121
+ headers: {
1122
+ "x-api-key": this.apiKey
1123
+ }
1124
+ });
1125
+ return response.ok;
1126
+ } catch (error) {
1127
+ console.error("API key validation failed:", error);
1128
+ return false;
1129
+ }
1130
+ }
1131
+ };
1132
+
1074
1133
  // src/client/mcp-client.ts
1075
1134
  var MCPClient = class {
1076
1135
  constructor(config = {}) {
1136
+ // ← NEW: Track auth mode
1077
1137
  this.ws = null;
1078
1138
  this.eventSource = null;
1079
1139
  this.accessToken = null;
@@ -1084,30 +1144,45 @@ var MCPClient = class {
1084
1144
  ...config
1085
1145
  };
1086
1146
  this.tokenStorage = new TokenStorage();
1087
- if (this.isTerminal()) {
1088
- this.authFlow = new TerminalOAuthFlow(config);
1147
+ this.authMode = config.apiKey ? "apikey" : "oauth";
1148
+ if (this.authMode === "apikey") {
1149
+ this.authFlow = new APIKeyFlow(
1150
+ config.apiKey,
1151
+ config.authBaseUrl || "https://mcp.lanonasis.com"
1152
+ );
1089
1153
  } else {
1090
- this.authFlow = new DesktopOAuthFlow(config);
1154
+ if (this.isTerminal()) {
1155
+ this.authFlow = new TerminalOAuthFlow(config);
1156
+ } else {
1157
+ this.authFlow = new DesktopOAuthFlow(config);
1158
+ }
1091
1159
  }
1092
1160
  }
1093
1161
  async connect() {
1094
1162
  try {
1095
1163
  let tokens = await this.tokenStorage.retrieve();
1096
- if (!tokens || this.tokenStorage.isTokenExpired(tokens)) {
1097
- if (tokens?.refresh_token) {
1098
- try {
1099
- tokens = await this.authFlow.refreshToken(tokens.refresh_token);
1100
- await this.tokenStorage.store(tokens);
1101
- } catch (error) {
1164
+ if (this.authMode === "apikey") {
1165
+ if (!tokens) {
1166
+ tokens = await this.authenticate();
1167
+ }
1168
+ this.accessToken = tokens.access_token;
1169
+ } else {
1170
+ if (!tokens || this.tokenStorage.isTokenExpired(tokens)) {
1171
+ if (tokens?.refresh_token) {
1172
+ try {
1173
+ tokens = await this.authFlow.refreshToken(tokens.refresh_token);
1174
+ await this.tokenStorage.store(tokens);
1175
+ } catch (error) {
1176
+ tokens = await this.authenticate();
1177
+ }
1178
+ } else {
1102
1179
  tokens = await this.authenticate();
1103
1180
  }
1104
- } else {
1105
- tokens = await this.authenticate();
1106
1181
  }
1107
- }
1108
- this.accessToken = tokens.access_token;
1109
- if (this.config.autoRefresh && tokens.expires_in) {
1110
- this.scheduleTokenRefresh(tokens);
1182
+ this.accessToken = tokens.access_token;
1183
+ if (this.config.autoRefresh && tokens.expires_in) {
1184
+ this.scheduleTokenRefresh(tokens);
1185
+ }
1111
1186
  }
1112
1187
  await this.establishConnection();
1113
1188
  } catch (error) {
@@ -1127,6 +1202,10 @@ var MCPClient = class {
1127
1202
  if (!tokens) {
1128
1203
  throw new Error("Not authenticated");
1129
1204
  }
1205
+ if (this.authMode === "apikey") {
1206
+ this.accessToken = tokens.access_token;
1207
+ return;
1208
+ }
1130
1209
  if (this.tokenStorage.isTokenExpired(tokens)) {
1131
1210
  if (tokens.refresh_token) {
1132
1211
  try {
@@ -1183,11 +1262,19 @@ var MCPClient = class {
1183
1262
  this.ws = new WebSocket(wsUrl.toString());
1184
1263
  } else {
1185
1264
  const { default: WS } = await import("ws");
1186
- this.ws = new WS(wsUrl.toString(), {
1187
- headers: {
1188
- "Authorization": `Bearer ${this.accessToken}`
1189
- }
1190
- });
1265
+ if (this.authMode === "apikey") {
1266
+ this.ws = new WS(wsUrl.toString(), {
1267
+ headers: {
1268
+ "x-api-key": this.accessToken
1269
+ }
1270
+ });
1271
+ } else {
1272
+ this.ws = new WS(wsUrl.toString(), {
1273
+ headers: {
1274
+ "Authorization": `Bearer ${this.accessToken}`
1275
+ }
1276
+ });
1277
+ }
1191
1278
  }
1192
1279
  return new Promise((resolve, reject) => {
1193
1280
  if (!this.ws) {
@@ -1221,11 +1308,19 @@ var MCPClient = class {
1221
1308
  } else {
1222
1309
  const EventSourceModule = await import("eventsource");
1223
1310
  const ES = EventSourceModule.default || EventSourceModule;
1224
- this.eventSource = new ES(sseUrl.toString(), {
1225
- headers: {
1226
- "Authorization": `Bearer ${this.accessToken}`
1227
- }
1228
- });
1311
+ if (this.authMode === "apikey") {
1312
+ this.eventSource = new ES(sseUrl.toString(), {
1313
+ headers: {
1314
+ "x-api-key": this.accessToken
1315
+ }
1316
+ });
1317
+ } else {
1318
+ this.eventSource = new ES(sseUrl.toString(), {
1319
+ headers: {
1320
+ "Authorization": `Bearer ${this.accessToken}`
1321
+ }
1322
+ });
1323
+ }
1229
1324
  }
1230
1325
  this.eventSource.onopen = () => {
1231
1326
  console.log("MCP SSE connected");
@@ -1255,12 +1350,17 @@ var MCPClient = class {
1255
1350
  if (!this.accessToken) {
1256
1351
  throw new Error("Not authenticated");
1257
1352
  }
1353
+ const headers = {
1354
+ "Content-Type": "application/json"
1355
+ };
1356
+ if (this.authMode === "apikey") {
1357
+ headers["x-api-key"] = this.accessToken;
1358
+ } else {
1359
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
1360
+ }
1258
1361
  const response = await fetch(`${this.config.mcpEndpoint}/api`, {
1259
1362
  method: "POST",
1260
- headers: {
1261
- "Authorization": `Bearer ${this.accessToken}`,
1262
- "Content-Type": "application/json"
1263
- },
1363
+ headers,
1264
1364
  body: JSON.stringify({
1265
1365
  jsonrpc: "2.0",
1266
1366
  id: this.generateId(),
@@ -1269,6 +1369,9 @@ var MCPClient = class {
1269
1369
  })
1270
1370
  });
1271
1371
  if (response.status === 401) {
1372
+ if (this.authMode === "apikey") {
1373
+ throw new Error("Invalid API key - please check your credentials");
1374
+ }
1272
1375
  const tokens = await this.tokenStorage.retrieve();
1273
1376
  if (tokens?.refresh_token) {
1274
1377
  const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
package/dist/index.d.cts CHANGED
@@ -172,11 +172,13 @@ declare class ApiKeyStorage {
172
172
  interface MCPClientConfig extends Partial<OAuthConfig> {
173
173
  mcpEndpoint?: string;
174
174
  autoRefresh?: boolean;
175
+ apiKey?: string;
175
176
  }
176
177
  declare class MCPClient {
177
178
  private tokenStorage;
178
179
  private authFlow;
179
180
  private config;
181
+ private authMode;
180
182
  private ws;
181
183
  private eventSource;
182
184
  private accessToken;
package/dist/index.d.ts CHANGED
@@ -172,11 +172,13 @@ declare class ApiKeyStorage {
172
172
  interface MCPClientConfig extends Partial<OAuthConfig> {
173
173
  mcpEndpoint?: string;
174
174
  autoRefresh?: boolean;
175
+ apiKey?: string;
175
176
  }
176
177
  declare class MCPClient {
177
178
  private tokenStorage;
178
179
  private authFlow;
179
180
  private config;
181
+ private authMode;
180
182
  private ws;
181
183
  private eventSource;
182
184
  private accessToken;
package/dist/index.js CHANGED
@@ -406,6 +406,9 @@ var TokenStorage = class {
406
406
  }
407
407
  }
408
408
  isTokenExpired(tokens) {
409
+ if (tokens.token_type === "api-key" || tokens.expires_in === 0) {
410
+ return false;
411
+ }
409
412
  if (!tokens.expires_in) return false;
410
413
  if (!tokens.issued_at) {
411
414
  console.warn("Token missing issued_at timestamp, treating as expired");
@@ -1037,9 +1040,66 @@ var ApiKeyStorage = class {
1037
1040
  }
1038
1041
  };
1039
1042
 
1043
+ // src/flows/apikey-flow.ts
1044
+ var APIKeyFlow = class extends BaseOAuthFlow {
1045
+ constructor(apiKey, authBaseUrl = "https://mcp.lanonasis.com") {
1046
+ super({
1047
+ clientId: "api-key-client",
1048
+ authBaseUrl
1049
+ });
1050
+ this.apiKey = apiKey;
1051
+ }
1052
+ /**
1053
+ * "Authenticate" by returning the API key as a virtual token
1054
+ * The API key will be used directly in request headers
1055
+ */
1056
+ async authenticate() {
1057
+ if (!this.apiKey || !this.apiKey.startsWith("lano_") && !this.apiKey.startsWith("vx_")) {
1058
+ throw new Error(
1059
+ 'Invalid API key format. Must start with "lano_" or "vx_". Please regenerate your API key from the dashboard.'
1060
+ );
1061
+ }
1062
+ if (this.apiKey.startsWith("vx_")) {
1063
+ console.warn(
1064
+ '\u26A0\uFE0F DEPRECATION WARNING: API keys with "vx_" prefix are deprecated and will stop working soon. Please regenerate your API key from the dashboard to get a "lano_" prefixed key. Support for "vx_" keys will be removed in a future version.'
1065
+ );
1066
+ }
1067
+ return {
1068
+ access_token: this.apiKey,
1069
+ token_type: "api-key",
1070
+ expires_in: 0,
1071
+ // API keys don't expire
1072
+ issued_at: Date.now()
1073
+ };
1074
+ }
1075
+ /**
1076
+ * API keys don't need refresh
1077
+ */
1078
+ async refreshToken(refreshToken) {
1079
+ throw new Error("API keys do not support token refresh");
1080
+ }
1081
+ /**
1082
+ * Optional: Validate API key by making a test request
1083
+ */
1084
+ async validateAPIKey() {
1085
+ try {
1086
+ const response = await fetch(`${this.config.authBaseUrl}/api/v1/health`, {
1087
+ headers: {
1088
+ "x-api-key": this.apiKey
1089
+ }
1090
+ });
1091
+ return response.ok;
1092
+ } catch (error) {
1093
+ console.error("API key validation failed:", error);
1094
+ return false;
1095
+ }
1096
+ }
1097
+ };
1098
+
1040
1099
  // src/client/mcp-client.ts
1041
1100
  var MCPClient = class {
1042
1101
  constructor(config = {}) {
1102
+ // ← NEW: Track auth mode
1043
1103
  this.ws = null;
1044
1104
  this.eventSource = null;
1045
1105
  this.accessToken = null;
@@ -1050,30 +1110,45 @@ var MCPClient = class {
1050
1110
  ...config
1051
1111
  };
1052
1112
  this.tokenStorage = new TokenStorage();
1053
- if (this.isTerminal()) {
1054
- this.authFlow = new TerminalOAuthFlow(config);
1113
+ this.authMode = config.apiKey ? "apikey" : "oauth";
1114
+ if (this.authMode === "apikey") {
1115
+ this.authFlow = new APIKeyFlow(
1116
+ config.apiKey,
1117
+ config.authBaseUrl || "https://mcp.lanonasis.com"
1118
+ );
1055
1119
  } else {
1056
- this.authFlow = new DesktopOAuthFlow(config);
1120
+ if (this.isTerminal()) {
1121
+ this.authFlow = new TerminalOAuthFlow(config);
1122
+ } else {
1123
+ this.authFlow = new DesktopOAuthFlow(config);
1124
+ }
1057
1125
  }
1058
1126
  }
1059
1127
  async connect() {
1060
1128
  try {
1061
1129
  let tokens = await this.tokenStorage.retrieve();
1062
- if (!tokens || this.tokenStorage.isTokenExpired(tokens)) {
1063
- if (tokens?.refresh_token) {
1064
- try {
1065
- tokens = await this.authFlow.refreshToken(tokens.refresh_token);
1066
- await this.tokenStorage.store(tokens);
1067
- } catch (error) {
1130
+ if (this.authMode === "apikey") {
1131
+ if (!tokens) {
1132
+ tokens = await this.authenticate();
1133
+ }
1134
+ this.accessToken = tokens.access_token;
1135
+ } else {
1136
+ if (!tokens || this.tokenStorage.isTokenExpired(tokens)) {
1137
+ if (tokens?.refresh_token) {
1138
+ try {
1139
+ tokens = await this.authFlow.refreshToken(tokens.refresh_token);
1140
+ await this.tokenStorage.store(tokens);
1141
+ } catch (error) {
1142
+ tokens = await this.authenticate();
1143
+ }
1144
+ } else {
1068
1145
  tokens = await this.authenticate();
1069
1146
  }
1070
- } else {
1071
- tokens = await this.authenticate();
1072
1147
  }
1073
- }
1074
- this.accessToken = tokens.access_token;
1075
- if (this.config.autoRefresh && tokens.expires_in) {
1076
- this.scheduleTokenRefresh(tokens);
1148
+ this.accessToken = tokens.access_token;
1149
+ if (this.config.autoRefresh && tokens.expires_in) {
1150
+ this.scheduleTokenRefresh(tokens);
1151
+ }
1077
1152
  }
1078
1153
  await this.establishConnection();
1079
1154
  } catch (error) {
@@ -1093,6 +1168,10 @@ var MCPClient = class {
1093
1168
  if (!tokens) {
1094
1169
  throw new Error("Not authenticated");
1095
1170
  }
1171
+ if (this.authMode === "apikey") {
1172
+ this.accessToken = tokens.access_token;
1173
+ return;
1174
+ }
1096
1175
  if (this.tokenStorage.isTokenExpired(tokens)) {
1097
1176
  if (tokens.refresh_token) {
1098
1177
  try {
@@ -1149,11 +1228,19 @@ var MCPClient = class {
1149
1228
  this.ws = new WebSocket(wsUrl.toString());
1150
1229
  } else {
1151
1230
  const { default: WS } = await import("ws");
1152
- this.ws = new WS(wsUrl.toString(), {
1153
- headers: {
1154
- "Authorization": `Bearer ${this.accessToken}`
1155
- }
1156
- });
1231
+ if (this.authMode === "apikey") {
1232
+ this.ws = new WS(wsUrl.toString(), {
1233
+ headers: {
1234
+ "x-api-key": this.accessToken
1235
+ }
1236
+ });
1237
+ } else {
1238
+ this.ws = new WS(wsUrl.toString(), {
1239
+ headers: {
1240
+ "Authorization": `Bearer ${this.accessToken}`
1241
+ }
1242
+ });
1243
+ }
1157
1244
  }
1158
1245
  return new Promise((resolve, reject) => {
1159
1246
  if (!this.ws) {
@@ -1187,11 +1274,19 @@ var MCPClient = class {
1187
1274
  } else {
1188
1275
  const EventSourceModule = await import("eventsource");
1189
1276
  const ES = EventSourceModule.default || EventSourceModule;
1190
- this.eventSource = new ES(sseUrl.toString(), {
1191
- headers: {
1192
- "Authorization": `Bearer ${this.accessToken}`
1193
- }
1194
- });
1277
+ if (this.authMode === "apikey") {
1278
+ this.eventSource = new ES(sseUrl.toString(), {
1279
+ headers: {
1280
+ "x-api-key": this.accessToken
1281
+ }
1282
+ });
1283
+ } else {
1284
+ this.eventSource = new ES(sseUrl.toString(), {
1285
+ headers: {
1286
+ "Authorization": `Bearer ${this.accessToken}`
1287
+ }
1288
+ });
1289
+ }
1195
1290
  }
1196
1291
  this.eventSource.onopen = () => {
1197
1292
  console.log("MCP SSE connected");
@@ -1221,12 +1316,17 @@ var MCPClient = class {
1221
1316
  if (!this.accessToken) {
1222
1317
  throw new Error("Not authenticated");
1223
1318
  }
1319
+ const headers = {
1320
+ "Content-Type": "application/json"
1321
+ };
1322
+ if (this.authMode === "apikey") {
1323
+ headers["x-api-key"] = this.accessToken;
1324
+ } else {
1325
+ headers["Authorization"] = `Bearer ${this.accessToken}`;
1326
+ }
1224
1327
  const response = await fetch(`${this.config.mcpEndpoint}/api`, {
1225
1328
  method: "POST",
1226
- headers: {
1227
- "Authorization": `Bearer ${this.accessToken}`,
1228
- "Content-Type": "application/json"
1229
- },
1329
+ headers,
1230
1330
  body: JSON.stringify({
1231
1331
  jsonrpc: "2.0",
1232
1332
  id: this.generateId(),
@@ -1235,6 +1335,9 @@ var MCPClient = class {
1235
1335
  })
1236
1336
  });
1237
1337
  if (response.status === 401) {
1338
+ if (this.authMode === "apikey") {
1339
+ throw new Error("Invalid API key - please check your credentials");
1340
+ }
1238
1341
  const tokens = await this.tokenStorage.retrieve();
1239
1342
  if (tokens?.refresh_token) {
1240
1343
  const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@lanonasis/oauth-client",
3
- "version": "1.0.2",
3
+ "version": "1.2.0",
4
4
  "type": "module",
5
- "description": "OAuth client for Lan Onasis MCP integration",
5
+ "description": "OAuth and API Key authentication client for Lan Onasis MCP integration",
6
6
  "license": "MIT",
7
7
  "author": "Lan Onasis",
8
8
  "main": "./dist/index.js",