@lanonasis/oauth-client 1.2.0 → 1.2.1
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 +2 -31
- package/dist/index.cjs +29 -132
- package/dist/index.d.cts +0 -2
- package/dist/index.d.ts +0 -2
- package/dist/{index.js → index.mjs} +29 -132
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -1,15 +1,12 @@
|
|
|
1
1
|
# @lanonasis/oauth-client
|
|
2
2
|
|
|
3
|
-
Drop-in OAuth +
|
|
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.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
|
-
- **Dual Authentication**: OAuth2 PKCE flow OR direct API key authentication
|
|
7
6
|
- OAuth flows for terminal and desktop (Electron-friendly) environments
|
|
8
|
-
- API key authentication for new users with dashboard-generated keys
|
|
9
7
|
- Token storage with secure backends (Keytar, encrypted files, Electron secure store, mobile secure storage, WebCrypto in browsers)
|
|
10
8
|
- API key storage that normalizes to SHA-256 digests before persisting
|
|
11
9
|
- MCP client that connects over WebSocket (`/ws`) or SSE (`/sse`) with auto-refreshing tokens
|
|
12
|
-
- Automatic auth mode detection based on configuration
|
|
13
10
|
- ESM + CJS bundles with TypeScript types
|
|
14
11
|
|
|
15
12
|
## Installation
|
|
@@ -20,24 +17,6 @@ bun add @lanonasis/oauth-client
|
|
|
20
17
|
```
|
|
21
18
|
|
|
22
19
|
## 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)
|
|
41
20
|
```ts
|
|
42
21
|
import { MCPClient } from '@lanonasis/oauth-client';
|
|
43
22
|
|
|
@@ -48,7 +27,7 @@ const client = new MCPClient({
|
|
|
48
27
|
scope: 'mcp:read mcp:write api_keys:manage'
|
|
49
28
|
});
|
|
50
29
|
|
|
51
|
-
await client.connect(); //
|
|
30
|
+
await client.connect(); // handles auth, refresh, and MCP connect
|
|
52
31
|
```
|
|
53
32
|
|
|
54
33
|
### Terminal OAuth flow
|
|
@@ -95,20 +74,12 @@ const hashed = await apiKeys.getApiKey(); // returns sha256 hex digest
|
|
|
95
74
|
```
|
|
96
75
|
|
|
97
76
|
## 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
|
|
104
77
|
- `clientId` (required): OAuth client id issued by Lanonasis Auth.
|
|
105
78
|
- `authBaseUrl` (optional): defaults to `https://auth.lanonasis.com`.
|
|
106
79
|
- `mcpEndpoint` (optional): defaults to `wss://mcp.lanonasis.com` and can also be `https://...` for SSE.
|
|
107
80
|
- `scope` (optional): defaults to `mcp:read mcp:write api_keys:manage`.
|
|
108
81
|
- `autoRefresh` (MCPClient): refresh tokens 5 minutes before expiry (default `true`).
|
|
109
82
|
|
|
110
|
-
**Note**: Auth mode is automatically detected - if you provide `apiKey`, it uses API key authentication. If you provide `clientId`, it uses OAuth.
|
|
111
|
-
|
|
112
83
|
## Publishing (maintainers)
|
|
113
84
|
1) Build artifacts: `npm install && npm run build`
|
|
114
85
|
2) Verify contents: ensure `dist`, `README.md`, `LICENSE` are present.
|
package/dist/index.cjs
CHANGED
|
@@ -440,9 +440,6 @@ 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
|
-
}
|
|
446
443
|
if (!tokens.expires_in) return false;
|
|
447
444
|
if (!tokens.issued_at) {
|
|
448
445
|
console.warn("Token missing issued_at timestamp, treating as expired");
|
|
@@ -1074,66 +1071,9 @@ var ApiKeyStorage = class {
|
|
|
1074
1071
|
}
|
|
1075
1072
|
};
|
|
1076
1073
|
|
|
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
|
-
|
|
1133
1074
|
// src/client/mcp-client.ts
|
|
1134
1075
|
var MCPClient = class {
|
|
1135
1076
|
constructor(config = {}) {
|
|
1136
|
-
// ← NEW: Track auth mode
|
|
1137
1077
|
this.ws = null;
|
|
1138
1078
|
this.eventSource = null;
|
|
1139
1079
|
this.accessToken = null;
|
|
@@ -1144,45 +1084,30 @@ var MCPClient = class {
|
|
|
1144
1084
|
...config
|
|
1145
1085
|
};
|
|
1146
1086
|
this.tokenStorage = new TokenStorage();
|
|
1147
|
-
this.
|
|
1148
|
-
|
|
1149
|
-
this.authFlow = new APIKeyFlow(
|
|
1150
|
-
config.apiKey,
|
|
1151
|
-
config.authBaseUrl || "https://mcp.lanonasis.com"
|
|
1152
|
-
);
|
|
1087
|
+
if (this.isTerminal()) {
|
|
1088
|
+
this.authFlow = new TerminalOAuthFlow(config);
|
|
1153
1089
|
} else {
|
|
1154
|
-
|
|
1155
|
-
this.authFlow = new TerminalOAuthFlow(config);
|
|
1156
|
-
} else {
|
|
1157
|
-
this.authFlow = new DesktopOAuthFlow(config);
|
|
1158
|
-
}
|
|
1090
|
+
this.authFlow = new DesktopOAuthFlow(config);
|
|
1159
1091
|
}
|
|
1160
1092
|
}
|
|
1161
1093
|
async connect() {
|
|
1162
1094
|
try {
|
|
1163
1095
|
let tokens = await this.tokenStorage.retrieve();
|
|
1164
|
-
if (this.
|
|
1165
|
-
if (
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
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 {
|
|
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) {
|
|
1179
1102
|
tokens = await this.authenticate();
|
|
1180
1103
|
}
|
|
1104
|
+
} else {
|
|
1105
|
+
tokens = await this.authenticate();
|
|
1181
1106
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1107
|
+
}
|
|
1108
|
+
this.accessToken = tokens.access_token;
|
|
1109
|
+
if (this.config.autoRefresh && tokens.expires_in) {
|
|
1110
|
+
this.scheduleTokenRefresh(tokens);
|
|
1186
1111
|
}
|
|
1187
1112
|
await this.establishConnection();
|
|
1188
1113
|
} catch (error) {
|
|
@@ -1202,10 +1127,6 @@ var MCPClient = class {
|
|
|
1202
1127
|
if (!tokens) {
|
|
1203
1128
|
throw new Error("Not authenticated");
|
|
1204
1129
|
}
|
|
1205
|
-
if (this.authMode === "apikey") {
|
|
1206
|
-
this.accessToken = tokens.access_token;
|
|
1207
|
-
return;
|
|
1208
|
-
}
|
|
1209
1130
|
if (this.tokenStorage.isTokenExpired(tokens)) {
|
|
1210
1131
|
if (tokens.refresh_token) {
|
|
1211
1132
|
try {
|
|
@@ -1262,19 +1183,11 @@ var MCPClient = class {
|
|
|
1262
1183
|
this.ws = new WebSocket(wsUrl.toString());
|
|
1263
1184
|
} else {
|
|
1264
1185
|
const { default: WS } = await import("ws");
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
});
|
|
1271
|
-
} else {
|
|
1272
|
-
this.ws = new WS(wsUrl.toString(), {
|
|
1273
|
-
headers: {
|
|
1274
|
-
"Authorization": `Bearer ${this.accessToken}`
|
|
1275
|
-
}
|
|
1276
|
-
});
|
|
1277
|
-
}
|
|
1186
|
+
this.ws = new WS(wsUrl.toString(), {
|
|
1187
|
+
headers: {
|
|
1188
|
+
"Authorization": `Bearer ${this.accessToken}`
|
|
1189
|
+
}
|
|
1190
|
+
});
|
|
1278
1191
|
}
|
|
1279
1192
|
return new Promise((resolve, reject) => {
|
|
1280
1193
|
if (!this.ws) {
|
|
@@ -1308,19 +1221,11 @@ var MCPClient = class {
|
|
|
1308
1221
|
} else {
|
|
1309
1222
|
const EventSourceModule = await import("eventsource");
|
|
1310
1223
|
const ES = EventSourceModule.default || EventSourceModule;
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
});
|
|
1317
|
-
} else {
|
|
1318
|
-
this.eventSource = new ES(sseUrl.toString(), {
|
|
1319
|
-
headers: {
|
|
1320
|
-
"Authorization": `Bearer ${this.accessToken}`
|
|
1321
|
-
}
|
|
1322
|
-
});
|
|
1323
|
-
}
|
|
1224
|
+
this.eventSource = new ES(sseUrl.toString(), {
|
|
1225
|
+
headers: {
|
|
1226
|
+
"Authorization": `Bearer ${this.accessToken}`
|
|
1227
|
+
}
|
|
1228
|
+
});
|
|
1324
1229
|
}
|
|
1325
1230
|
this.eventSource.onopen = () => {
|
|
1326
1231
|
console.log("MCP SSE connected");
|
|
@@ -1350,17 +1255,12 @@ var MCPClient = class {
|
|
|
1350
1255
|
if (!this.accessToken) {
|
|
1351
1256
|
throw new Error("Not authenticated");
|
|
1352
1257
|
}
|
|
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
|
-
}
|
|
1361
1258
|
const response = await fetch(`${this.config.mcpEndpoint}/api`, {
|
|
1362
1259
|
method: "POST",
|
|
1363
|
-
headers
|
|
1260
|
+
headers: {
|
|
1261
|
+
"Authorization": `Bearer ${this.accessToken}`,
|
|
1262
|
+
"Content-Type": "application/json"
|
|
1263
|
+
},
|
|
1364
1264
|
body: JSON.stringify({
|
|
1365
1265
|
jsonrpc: "2.0",
|
|
1366
1266
|
id: this.generateId(),
|
|
@@ -1369,9 +1269,6 @@ var MCPClient = class {
|
|
|
1369
1269
|
})
|
|
1370
1270
|
});
|
|
1371
1271
|
if (response.status === 401) {
|
|
1372
|
-
if (this.authMode === "apikey") {
|
|
1373
|
-
throw new Error("Invalid API key - please check your credentials");
|
|
1374
|
-
}
|
|
1375
1272
|
const tokens = await this.tokenStorage.retrieve();
|
|
1376
1273
|
if (tokens?.refresh_token) {
|
|
1377
1274
|
const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
|
package/dist/index.d.cts
CHANGED
|
@@ -172,13 +172,11 @@ declare class ApiKeyStorage {
|
|
|
172
172
|
interface MCPClientConfig extends Partial<OAuthConfig> {
|
|
173
173
|
mcpEndpoint?: string;
|
|
174
174
|
autoRefresh?: boolean;
|
|
175
|
-
apiKey?: string;
|
|
176
175
|
}
|
|
177
176
|
declare class MCPClient {
|
|
178
177
|
private tokenStorage;
|
|
179
178
|
private authFlow;
|
|
180
179
|
private config;
|
|
181
|
-
private authMode;
|
|
182
180
|
private ws;
|
|
183
181
|
private eventSource;
|
|
184
182
|
private accessToken;
|
package/dist/index.d.ts
CHANGED
|
@@ -172,13 +172,11 @@ declare class ApiKeyStorage {
|
|
|
172
172
|
interface MCPClientConfig extends Partial<OAuthConfig> {
|
|
173
173
|
mcpEndpoint?: string;
|
|
174
174
|
autoRefresh?: boolean;
|
|
175
|
-
apiKey?: string;
|
|
176
175
|
}
|
|
177
176
|
declare class MCPClient {
|
|
178
177
|
private tokenStorage;
|
|
179
178
|
private authFlow;
|
|
180
179
|
private config;
|
|
181
|
-
private authMode;
|
|
182
180
|
private ws;
|
|
183
181
|
private eventSource;
|
|
184
182
|
private accessToken;
|
|
@@ -406,9 +406,6 @@ 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
|
-
}
|
|
412
409
|
if (!tokens.expires_in) return false;
|
|
413
410
|
if (!tokens.issued_at) {
|
|
414
411
|
console.warn("Token missing issued_at timestamp, treating as expired");
|
|
@@ -1040,66 +1037,9 @@ var ApiKeyStorage = class {
|
|
|
1040
1037
|
}
|
|
1041
1038
|
};
|
|
1042
1039
|
|
|
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
|
-
|
|
1099
1040
|
// src/client/mcp-client.ts
|
|
1100
1041
|
var MCPClient = class {
|
|
1101
1042
|
constructor(config = {}) {
|
|
1102
|
-
// ← NEW: Track auth mode
|
|
1103
1043
|
this.ws = null;
|
|
1104
1044
|
this.eventSource = null;
|
|
1105
1045
|
this.accessToken = null;
|
|
@@ -1110,45 +1050,30 @@ var MCPClient = class {
|
|
|
1110
1050
|
...config
|
|
1111
1051
|
};
|
|
1112
1052
|
this.tokenStorage = new TokenStorage();
|
|
1113
|
-
this.
|
|
1114
|
-
|
|
1115
|
-
this.authFlow = new APIKeyFlow(
|
|
1116
|
-
config.apiKey,
|
|
1117
|
-
config.authBaseUrl || "https://mcp.lanonasis.com"
|
|
1118
|
-
);
|
|
1053
|
+
if (this.isTerminal()) {
|
|
1054
|
+
this.authFlow = new TerminalOAuthFlow(config);
|
|
1119
1055
|
} else {
|
|
1120
|
-
|
|
1121
|
-
this.authFlow = new TerminalOAuthFlow(config);
|
|
1122
|
-
} else {
|
|
1123
|
-
this.authFlow = new DesktopOAuthFlow(config);
|
|
1124
|
-
}
|
|
1056
|
+
this.authFlow = new DesktopOAuthFlow(config);
|
|
1125
1057
|
}
|
|
1126
1058
|
}
|
|
1127
1059
|
async connect() {
|
|
1128
1060
|
try {
|
|
1129
1061
|
let tokens = await this.tokenStorage.retrieve();
|
|
1130
|
-
if (this.
|
|
1131
|
-
if (
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
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 {
|
|
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) {
|
|
1145
1068
|
tokens = await this.authenticate();
|
|
1146
1069
|
}
|
|
1070
|
+
} else {
|
|
1071
|
+
tokens = await this.authenticate();
|
|
1147
1072
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1073
|
+
}
|
|
1074
|
+
this.accessToken = tokens.access_token;
|
|
1075
|
+
if (this.config.autoRefresh && tokens.expires_in) {
|
|
1076
|
+
this.scheduleTokenRefresh(tokens);
|
|
1152
1077
|
}
|
|
1153
1078
|
await this.establishConnection();
|
|
1154
1079
|
} catch (error) {
|
|
@@ -1168,10 +1093,6 @@ var MCPClient = class {
|
|
|
1168
1093
|
if (!tokens) {
|
|
1169
1094
|
throw new Error("Not authenticated");
|
|
1170
1095
|
}
|
|
1171
|
-
if (this.authMode === "apikey") {
|
|
1172
|
-
this.accessToken = tokens.access_token;
|
|
1173
|
-
return;
|
|
1174
|
-
}
|
|
1175
1096
|
if (this.tokenStorage.isTokenExpired(tokens)) {
|
|
1176
1097
|
if (tokens.refresh_token) {
|
|
1177
1098
|
try {
|
|
@@ -1228,19 +1149,11 @@ var MCPClient = class {
|
|
|
1228
1149
|
this.ws = new WebSocket(wsUrl.toString());
|
|
1229
1150
|
} else {
|
|
1230
1151
|
const { default: WS } = await import("ws");
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
});
|
|
1237
|
-
} else {
|
|
1238
|
-
this.ws = new WS(wsUrl.toString(), {
|
|
1239
|
-
headers: {
|
|
1240
|
-
"Authorization": `Bearer ${this.accessToken}`
|
|
1241
|
-
}
|
|
1242
|
-
});
|
|
1243
|
-
}
|
|
1152
|
+
this.ws = new WS(wsUrl.toString(), {
|
|
1153
|
+
headers: {
|
|
1154
|
+
"Authorization": `Bearer ${this.accessToken}`
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1244
1157
|
}
|
|
1245
1158
|
return new Promise((resolve, reject) => {
|
|
1246
1159
|
if (!this.ws) {
|
|
@@ -1274,19 +1187,11 @@ var MCPClient = class {
|
|
|
1274
1187
|
} else {
|
|
1275
1188
|
const EventSourceModule = await import("eventsource");
|
|
1276
1189
|
const ES = EventSourceModule.default || EventSourceModule;
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
});
|
|
1283
|
-
} else {
|
|
1284
|
-
this.eventSource = new ES(sseUrl.toString(), {
|
|
1285
|
-
headers: {
|
|
1286
|
-
"Authorization": `Bearer ${this.accessToken}`
|
|
1287
|
-
}
|
|
1288
|
-
});
|
|
1289
|
-
}
|
|
1190
|
+
this.eventSource = new ES(sseUrl.toString(), {
|
|
1191
|
+
headers: {
|
|
1192
|
+
"Authorization": `Bearer ${this.accessToken}`
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1290
1195
|
}
|
|
1291
1196
|
this.eventSource.onopen = () => {
|
|
1292
1197
|
console.log("MCP SSE connected");
|
|
@@ -1316,17 +1221,12 @@ var MCPClient = class {
|
|
|
1316
1221
|
if (!this.accessToken) {
|
|
1317
1222
|
throw new Error("Not authenticated");
|
|
1318
1223
|
}
|
|
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
|
-
}
|
|
1327
1224
|
const response = await fetch(`${this.config.mcpEndpoint}/api`, {
|
|
1328
1225
|
method: "POST",
|
|
1329
|
-
headers
|
|
1226
|
+
headers: {
|
|
1227
|
+
"Authorization": `Bearer ${this.accessToken}`,
|
|
1228
|
+
"Content-Type": "application/json"
|
|
1229
|
+
},
|
|
1330
1230
|
body: JSON.stringify({
|
|
1331
1231
|
jsonrpc: "2.0",
|
|
1332
1232
|
id: this.generateId(),
|
|
@@ -1335,9 +1235,6 @@ var MCPClient = class {
|
|
|
1335
1235
|
})
|
|
1336
1236
|
});
|
|
1337
1237
|
if (response.status === 401) {
|
|
1338
|
-
if (this.authMode === "apikey") {
|
|
1339
|
-
throw new Error("Invalid API key - please check your credentials");
|
|
1340
|
-
}
|
|
1341
1238
|
const tokens = await this.tokenStorage.retrieve();
|
|
1342
1239
|
if (tokens?.refresh_token) {
|
|
1343
1240
|
const newTokens = await this.authFlow.refreshToken(tokens.refresh_token);
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lanonasis/oauth-client",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "OAuth
|
|
5
|
+
"description": "OAuth client for Lan Onasis MCP integration",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Lan Onasis",
|
|
8
|
-
"main": "./dist/index.
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
9
|
"module": "./dist/index.mjs",
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"files": [
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
],
|
|
16
16
|
"repository": {
|
|
17
17
|
"type": "git",
|
|
18
|
-
"url": "
|
|
18
|
+
"url": "https://github.com/lanonasis/v-secure.git",
|
|
19
19
|
"directory": "oauth-client"
|
|
20
20
|
},
|
|
21
21
|
"bugs": {
|
|
@@ -26,12 +26,12 @@
|
|
|
26
26
|
".": {
|
|
27
27
|
"types": "./dist/index.d.ts",
|
|
28
28
|
"import": "./dist/index.mjs",
|
|
29
|
-
"require": "./dist/index.
|
|
29
|
+
"require": "./dist/index.cjs"
|
|
30
30
|
}
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"build": "tsup
|
|
34
|
-
"dev": "tsup
|
|
33
|
+
"build": "tsup --config tsup.config.ts",
|
|
34
|
+
"dev": "tsup --watch --config tsup.config.ts",
|
|
35
35
|
"test": "bun test",
|
|
36
36
|
"lint": "eslint src"
|
|
37
37
|
},
|
|
@@ -62,6 +62,7 @@
|
|
|
62
62
|
"@supabase/supabase-js": "^2.0.0"
|
|
63
63
|
},
|
|
64
64
|
"publishConfig": {
|
|
65
|
+
"registry": "https://registry.npmjs.org/",
|
|
65
66
|
"access": "public"
|
|
66
67
|
}
|
|
67
68
|
}
|