@leanmcp/auth 0.3.2 → 0.4.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 +148 -661
- package/dist/{auth0-DMNC3QWJ.mjs → auth0-UTD4QBG6.mjs} +4 -2
- package/dist/chunk-LPEX4YW6.mjs +13 -0
- package/dist/{chunk-ESHQ6BRM.mjs → chunk-P4HFKA5R.mjs} +7 -7
- package/dist/chunk-RGCCBQWG.mjs +113 -0
- package/dist/chunk-ZOPKMOPV.mjs +53 -0
- package/dist/{clerk-7PVVTTC7.mjs → clerk-3SDKGD6C.mjs} +4 -2
- package/dist/client/index.d.mts +499 -0
- package/dist/client/index.d.ts +499 -0
- package/dist/client/index.js +1039 -0
- package/dist/client/index.mjs +869 -0
- package/dist/{cognito-5Q5HGYMA.mjs → cognito-QQT7LK2Y.mjs} +4 -2
- package/dist/index.mjs +2 -1
- package/dist/{leanmcp-X6BD6HOJ.mjs → leanmcp-Y7TXNSTD.mjs} +4 -2
- package/dist/proxy/index.d.mts +376 -0
- package/dist/proxy/index.d.ts +376 -0
- package/dist/proxy/index.js +536 -0
- package/dist/proxy/index.mjs +480 -0
- package/dist/server/index.d.mts +496 -0
- package/dist/server/index.d.ts +496 -0
- package/dist/server/index.js +882 -0
- package/dist/server/index.mjs +847 -0
- package/dist/storage/index.d.mts +181 -0
- package/dist/storage/index.d.ts +181 -0
- package/dist/storage/index.js +499 -0
- package/dist/storage/index.mjs +372 -0
- package/dist/types-DMpGN530.d.mts +122 -0
- package/dist/types-DMpGN530.d.ts +122 -0
- package/package.json +40 -7
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MemoryStorage,
|
|
3
|
+
isTokenExpired,
|
|
4
|
+
withExpiresAt
|
|
5
|
+
} from "../chunk-RGCCBQWG.mjs";
|
|
6
|
+
import {
|
|
7
|
+
__name,
|
|
8
|
+
__require
|
|
9
|
+
} from "../chunk-LPEX4YW6.mjs";
|
|
10
|
+
|
|
11
|
+
// src/storage/file.ts
|
|
12
|
+
import { promises as fs } from "fs";
|
|
13
|
+
import { dirname } from "path";
|
|
14
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
15
|
+
var CURRENT_VERSION = 1;
|
|
16
|
+
var ALGORITHM = "aes-256-gcm";
|
|
17
|
+
var FileStorage = class {
|
|
18
|
+
static {
|
|
19
|
+
__name(this, "FileStorage");
|
|
20
|
+
}
|
|
21
|
+
filePath;
|
|
22
|
+
encryptionKey;
|
|
23
|
+
prettyPrint;
|
|
24
|
+
cache = null;
|
|
25
|
+
writePromise = null;
|
|
26
|
+
constructor(options) {
|
|
27
|
+
if (typeof options === "string") {
|
|
28
|
+
this.filePath = this.expandPath(options);
|
|
29
|
+
this.prettyPrint = false;
|
|
30
|
+
} else {
|
|
31
|
+
this.filePath = this.expandPath(options.filePath);
|
|
32
|
+
this.prettyPrint = options.prettyPrint ?? false;
|
|
33
|
+
if (options.encryptionKey) {
|
|
34
|
+
this.encryptionKey = scryptSync(options.encryptionKey, "leanmcp-salt", 32);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Expand ~ to home directory
|
|
40
|
+
*/
|
|
41
|
+
expandPath(filePath) {
|
|
42
|
+
if (filePath.startsWith("~")) {
|
|
43
|
+
const home = process.env.HOME || process.env.USERPROFILE || "";
|
|
44
|
+
return filePath.replace("~", home);
|
|
45
|
+
}
|
|
46
|
+
return filePath;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Normalize server URL for consistent key lookup
|
|
50
|
+
*/
|
|
51
|
+
normalizeUrl(serverUrl) {
|
|
52
|
+
return serverUrl.replace(/\/+$/, "").toLowerCase();
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Encrypt data
|
|
56
|
+
*/
|
|
57
|
+
encrypt(data) {
|
|
58
|
+
if (!this.encryptionKey) return data;
|
|
59
|
+
const iv = randomBytes(16);
|
|
60
|
+
const cipher = createCipheriv(ALGORITHM, this.encryptionKey, iv);
|
|
61
|
+
let encrypted = cipher.update(data, "utf8", "hex");
|
|
62
|
+
encrypted += cipher.final("hex");
|
|
63
|
+
const authTag = cipher.getAuthTag();
|
|
64
|
+
return `${iv.toString("hex")}:${authTag.toString("hex")}:${encrypted}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Decrypt data
|
|
68
|
+
*/
|
|
69
|
+
decrypt(data) {
|
|
70
|
+
if (!this.encryptionKey) return data;
|
|
71
|
+
const [ivHex, authTagHex, encrypted] = data.split(":");
|
|
72
|
+
const iv = Buffer.from(ivHex, "hex");
|
|
73
|
+
const authTag = Buffer.from(authTagHex, "hex");
|
|
74
|
+
const decipher = createDecipheriv(ALGORITHM, this.encryptionKey, iv);
|
|
75
|
+
decipher.setAuthTag(authTag);
|
|
76
|
+
let decrypted = decipher.update(encrypted, "hex", "utf8");
|
|
77
|
+
decrypted += decipher.final("utf8");
|
|
78
|
+
return decrypted;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Read data from file
|
|
82
|
+
*/
|
|
83
|
+
async readFile() {
|
|
84
|
+
if (this.cache) return this.cache;
|
|
85
|
+
try {
|
|
86
|
+
const raw = await fs.readFile(this.filePath, "utf8");
|
|
87
|
+
const decrypted = this.decrypt(raw);
|
|
88
|
+
this.cache = JSON.parse(decrypted);
|
|
89
|
+
return this.cache;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if (error.code === "ENOENT") {
|
|
92
|
+
this.cache = {
|
|
93
|
+
version: CURRENT_VERSION,
|
|
94
|
+
sessions: {}
|
|
95
|
+
};
|
|
96
|
+
return this.cache;
|
|
97
|
+
}
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Write data to file (coalesced to avoid race conditions)
|
|
103
|
+
*/
|
|
104
|
+
async writeFile(data) {
|
|
105
|
+
this.cache = data;
|
|
106
|
+
if (this.writePromise) {
|
|
107
|
+
return this.writePromise;
|
|
108
|
+
}
|
|
109
|
+
this.writePromise = (async () => {
|
|
110
|
+
try {
|
|
111
|
+
await fs.mkdir(dirname(this.filePath), {
|
|
112
|
+
recursive: true
|
|
113
|
+
});
|
|
114
|
+
const json = this.prettyPrint ? JSON.stringify(data, null, 2) : JSON.stringify(data);
|
|
115
|
+
const encrypted = this.encrypt(json);
|
|
116
|
+
await fs.writeFile(this.filePath, encrypted, "utf8");
|
|
117
|
+
} finally {
|
|
118
|
+
this.writePromise = null;
|
|
119
|
+
}
|
|
120
|
+
})();
|
|
121
|
+
return this.writePromise;
|
|
122
|
+
}
|
|
123
|
+
async getTokens(serverUrl) {
|
|
124
|
+
const key = this.normalizeUrl(serverUrl);
|
|
125
|
+
const data = await this.readFile();
|
|
126
|
+
const session = data.sessions[key];
|
|
127
|
+
if (!session) return null;
|
|
128
|
+
if (isTokenExpired(session.tokens)) {
|
|
129
|
+
return session.tokens;
|
|
130
|
+
}
|
|
131
|
+
return session.tokens;
|
|
132
|
+
}
|
|
133
|
+
async setTokens(serverUrl, tokens) {
|
|
134
|
+
const key = this.normalizeUrl(serverUrl);
|
|
135
|
+
const data = await this.readFile();
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
const existing = data.sessions[key];
|
|
138
|
+
data.sessions[key] = {
|
|
139
|
+
tokens: withExpiresAt(tokens),
|
|
140
|
+
clientInfo: existing?.clientInfo,
|
|
141
|
+
createdAt: existing?.createdAt ?? now,
|
|
142
|
+
updatedAt: now
|
|
143
|
+
};
|
|
144
|
+
await this.writeFile(data);
|
|
145
|
+
}
|
|
146
|
+
async clearTokens(serverUrl) {
|
|
147
|
+
const key = this.normalizeUrl(serverUrl);
|
|
148
|
+
const data = await this.readFile();
|
|
149
|
+
const session = data.sessions[key];
|
|
150
|
+
if (session) {
|
|
151
|
+
if (session.clientInfo) {
|
|
152
|
+
data.sessions[key] = {
|
|
153
|
+
tokens: {
|
|
154
|
+
access_token: "",
|
|
155
|
+
token_type: "bearer"
|
|
156
|
+
},
|
|
157
|
+
clientInfo: session.clientInfo,
|
|
158
|
+
createdAt: session.createdAt,
|
|
159
|
+
updatedAt: Date.now()
|
|
160
|
+
};
|
|
161
|
+
} else {
|
|
162
|
+
const { [key]: _, ...remaining } = data.sessions;
|
|
163
|
+
data.sessions = remaining;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
await this.writeFile(data);
|
|
167
|
+
}
|
|
168
|
+
async getClientInfo(serverUrl) {
|
|
169
|
+
const key = this.normalizeUrl(serverUrl);
|
|
170
|
+
const data = await this.readFile();
|
|
171
|
+
return data.sessions[key]?.clientInfo ?? null;
|
|
172
|
+
}
|
|
173
|
+
async setClientInfo(serverUrl, info) {
|
|
174
|
+
const key = this.normalizeUrl(serverUrl);
|
|
175
|
+
const data = await this.readFile();
|
|
176
|
+
const now = Date.now();
|
|
177
|
+
const existing = data.sessions[key];
|
|
178
|
+
data.sessions[key] = {
|
|
179
|
+
tokens: existing?.tokens ?? {
|
|
180
|
+
access_token: "",
|
|
181
|
+
token_type: "bearer"
|
|
182
|
+
},
|
|
183
|
+
clientInfo: info,
|
|
184
|
+
createdAt: existing?.createdAt ?? now,
|
|
185
|
+
updatedAt: now
|
|
186
|
+
};
|
|
187
|
+
await this.writeFile(data);
|
|
188
|
+
}
|
|
189
|
+
async clearClientInfo(serverUrl) {
|
|
190
|
+
const key = this.normalizeUrl(serverUrl);
|
|
191
|
+
const data = await this.readFile();
|
|
192
|
+
const session = data.sessions[key];
|
|
193
|
+
if (session) {
|
|
194
|
+
if (session.tokens?.access_token) {
|
|
195
|
+
data.sessions[key] = {
|
|
196
|
+
tokens: session.tokens,
|
|
197
|
+
createdAt: session.createdAt,
|
|
198
|
+
updatedAt: Date.now()
|
|
199
|
+
};
|
|
200
|
+
} else {
|
|
201
|
+
const { [key]: _, ...remaining } = data.sessions;
|
|
202
|
+
data.sessions = remaining;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
await this.writeFile(data);
|
|
206
|
+
}
|
|
207
|
+
async clearAll() {
|
|
208
|
+
await this.writeFile({
|
|
209
|
+
version: CURRENT_VERSION,
|
|
210
|
+
sessions: {}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
async getAllSessions() {
|
|
214
|
+
const data = await this.readFile();
|
|
215
|
+
return Object.entries(data.sessions).map(([url, session]) => ({
|
|
216
|
+
serverUrl: url,
|
|
217
|
+
tokens: session.tokens,
|
|
218
|
+
clientInfo: session.clientInfo,
|
|
219
|
+
createdAt: session.createdAt,
|
|
220
|
+
updatedAt: session.updatedAt
|
|
221
|
+
}));
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// src/storage/keychain.ts
|
|
226
|
+
var SERVICE_NAME = "leanmcp-auth";
|
|
227
|
+
var KeychainStorage = class {
|
|
228
|
+
static {
|
|
229
|
+
__name(this, "KeychainStorage");
|
|
230
|
+
}
|
|
231
|
+
serviceName;
|
|
232
|
+
keytar = null;
|
|
233
|
+
initPromise = null;
|
|
234
|
+
constructor(options = {}) {
|
|
235
|
+
this.serviceName = options.serviceName ?? SERVICE_NAME;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Initialize keytar (lazy load)
|
|
239
|
+
*/
|
|
240
|
+
async init() {
|
|
241
|
+
if (this.keytar) return;
|
|
242
|
+
if (this.initPromise) {
|
|
243
|
+
await this.initPromise;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
this.initPromise = (async () => {
|
|
247
|
+
try {
|
|
248
|
+
this.keytar = __require("keytar");
|
|
249
|
+
} catch (error) {
|
|
250
|
+
throw new Error('KeychainStorage requires the "keytar" package. Install it with: npm install keytar');
|
|
251
|
+
}
|
|
252
|
+
})();
|
|
253
|
+
await this.initPromise;
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Normalize server URL for consistent key lookup
|
|
257
|
+
*/
|
|
258
|
+
normalizeUrl(serverUrl) {
|
|
259
|
+
return serverUrl.replace(/\/+$/, "").toLowerCase();
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Get account key for tokens
|
|
263
|
+
*/
|
|
264
|
+
getTokensAccount(serverUrl) {
|
|
265
|
+
return `tokens:${this.normalizeUrl(serverUrl)}`;
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Get account key for client info
|
|
269
|
+
*/
|
|
270
|
+
getClientAccount(serverUrl) {
|
|
271
|
+
return `client:${this.normalizeUrl(serverUrl)}`;
|
|
272
|
+
}
|
|
273
|
+
async getTokens(serverUrl) {
|
|
274
|
+
await this.init();
|
|
275
|
+
const account = this.getTokensAccount(serverUrl);
|
|
276
|
+
const stored = await this.keytar.getPassword(this.serviceName, account);
|
|
277
|
+
if (!stored) return null;
|
|
278
|
+
try {
|
|
279
|
+
return JSON.parse(stored);
|
|
280
|
+
} catch {
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async setTokens(serverUrl, tokens) {
|
|
285
|
+
await this.init();
|
|
286
|
+
const account = this.getTokensAccount(serverUrl);
|
|
287
|
+
const enrichedTokens = withExpiresAt(tokens);
|
|
288
|
+
await this.keytar.setPassword(this.serviceName, account, JSON.stringify(enrichedTokens));
|
|
289
|
+
}
|
|
290
|
+
async clearTokens(serverUrl) {
|
|
291
|
+
await this.init();
|
|
292
|
+
const account = this.getTokensAccount(serverUrl);
|
|
293
|
+
await this.keytar.deletePassword(this.serviceName, account);
|
|
294
|
+
}
|
|
295
|
+
async getClientInfo(serverUrl) {
|
|
296
|
+
await this.init();
|
|
297
|
+
const account = this.getClientAccount(serverUrl);
|
|
298
|
+
const stored = await this.keytar.getPassword(this.serviceName, account);
|
|
299
|
+
if (!stored) return null;
|
|
300
|
+
try {
|
|
301
|
+
return JSON.parse(stored);
|
|
302
|
+
} catch {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
async setClientInfo(serverUrl, info) {
|
|
307
|
+
await this.init();
|
|
308
|
+
const account = this.getClientAccount(serverUrl);
|
|
309
|
+
await this.keytar.setPassword(this.serviceName, account, JSON.stringify(info));
|
|
310
|
+
}
|
|
311
|
+
async clearClientInfo(serverUrl) {
|
|
312
|
+
await this.init();
|
|
313
|
+
const account = this.getClientAccount(serverUrl);
|
|
314
|
+
await this.keytar.deletePassword(this.serviceName, account);
|
|
315
|
+
}
|
|
316
|
+
async clearAll() {
|
|
317
|
+
await this.init();
|
|
318
|
+
const credentials = await this.keytar.findCredentials(this.serviceName);
|
|
319
|
+
for (const cred of credentials) {
|
|
320
|
+
await this.keytar.deletePassword(this.serviceName, cred.account);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
async getAllSessions() {
|
|
324
|
+
await this.init();
|
|
325
|
+
const credentials = await this.keytar.findCredentials(this.serviceName);
|
|
326
|
+
const sessions = [];
|
|
327
|
+
const tokensMap = /* @__PURE__ */ new Map();
|
|
328
|
+
const clientMap = /* @__PURE__ */ new Map();
|
|
329
|
+
for (const cred of credentials) {
|
|
330
|
+
if (cred.account.startsWith("tokens:")) {
|
|
331
|
+
const url = cred.account.replace("tokens:", "");
|
|
332
|
+
try {
|
|
333
|
+
tokensMap.set(url, JSON.parse(cred.password));
|
|
334
|
+
} catch {
|
|
335
|
+
}
|
|
336
|
+
} else if (cred.account.startsWith("client:")) {
|
|
337
|
+
const url = cred.account.replace("client:", "");
|
|
338
|
+
try {
|
|
339
|
+
clientMap.set(url, JSON.parse(cred.password));
|
|
340
|
+
} catch {
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
for (const [url, tokens] of tokensMap) {
|
|
345
|
+
sessions.push({
|
|
346
|
+
serverUrl: url,
|
|
347
|
+
tokens,
|
|
348
|
+
clientInfo: clientMap.get(url),
|
|
349
|
+
createdAt: Date.now(),
|
|
350
|
+
updatedAt: Date.now()
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
return sessions;
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
async function isKeychainAvailable() {
|
|
357
|
+
try {
|
|
358
|
+
__require("keytar");
|
|
359
|
+
return true;
|
|
360
|
+
} catch {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
__name(isKeychainAvailable, "isKeychainAvailable");
|
|
365
|
+
export {
|
|
366
|
+
FileStorage,
|
|
367
|
+
KeychainStorage,
|
|
368
|
+
MemoryStorage,
|
|
369
|
+
isKeychainAvailable,
|
|
370
|
+
isTokenExpired,
|
|
371
|
+
withExpiresAt
|
|
372
|
+
};
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Storage Types
|
|
3
|
+
*
|
|
4
|
+
* Defines interfaces for storing OAuth tokens across different backends
|
|
5
|
+
* (memory, file, keychain, browser localStorage, etc.)
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* OAuth 2.0/2.1 token response
|
|
9
|
+
*/
|
|
10
|
+
interface OAuthTokens {
|
|
11
|
+
/** The access token issued by the authorization server */
|
|
12
|
+
access_token: string;
|
|
13
|
+
/** Token type (usually "Bearer") */
|
|
14
|
+
token_type: string;
|
|
15
|
+
/** Lifetime in seconds of the access token */
|
|
16
|
+
expires_in?: number;
|
|
17
|
+
/** Refresh token for obtaining new access tokens */
|
|
18
|
+
refresh_token?: string;
|
|
19
|
+
/** ID token (OpenID Connect) */
|
|
20
|
+
id_token?: string;
|
|
21
|
+
/** Scope granted by the authorization server */
|
|
22
|
+
scope?: string;
|
|
23
|
+
/** Computed: Unix timestamp when token expires */
|
|
24
|
+
expires_at?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* OAuth client registration information
|
|
28
|
+
* Used for Dynamic Client Registration (RFC 7591)
|
|
29
|
+
*/
|
|
30
|
+
interface ClientRegistration {
|
|
31
|
+
/** OAuth client identifier */
|
|
32
|
+
client_id: string;
|
|
33
|
+
/** OAuth client secret (for confidential clients) */
|
|
34
|
+
client_secret?: string;
|
|
35
|
+
/** Unix timestamp when client secret expires */
|
|
36
|
+
client_secret_expires_at?: number;
|
|
37
|
+
/** Token for accessing registration endpoint */
|
|
38
|
+
registration_access_token?: string;
|
|
39
|
+
/** Client metadata from registration */
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Stored session combining tokens and client info
|
|
44
|
+
*/
|
|
45
|
+
interface StoredSession {
|
|
46
|
+
/** Server URL this session is for */
|
|
47
|
+
serverUrl: string;
|
|
48
|
+
/** OAuth tokens */
|
|
49
|
+
tokens: OAuthTokens;
|
|
50
|
+
/** Client registration info (if dynamic registration used) */
|
|
51
|
+
clientInfo?: ClientRegistration;
|
|
52
|
+
/** Unix timestamp when session was created */
|
|
53
|
+
createdAt: number;
|
|
54
|
+
/** Unix timestamp when session was last updated */
|
|
55
|
+
updatedAt: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Token storage interface
|
|
59
|
+
*
|
|
60
|
+
* Implement this interface to create custom storage backends.
|
|
61
|
+
* All operations should be async to support various backends.
|
|
62
|
+
*/
|
|
63
|
+
interface TokenStorage {
|
|
64
|
+
/**
|
|
65
|
+
* Get stored tokens for a server
|
|
66
|
+
* @param serverUrl - The MCP server URL
|
|
67
|
+
* @returns Tokens if found, null otherwise
|
|
68
|
+
*/
|
|
69
|
+
getTokens(serverUrl: string): Promise<OAuthTokens | null>;
|
|
70
|
+
/**
|
|
71
|
+
* Store tokens for a server
|
|
72
|
+
* @param serverUrl - The MCP server URL
|
|
73
|
+
* @param tokens - OAuth tokens to store
|
|
74
|
+
*/
|
|
75
|
+
setTokens(serverUrl: string, tokens: OAuthTokens): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Clear tokens for a server
|
|
78
|
+
* @param serverUrl - The MCP server URL
|
|
79
|
+
*/
|
|
80
|
+
clearTokens(serverUrl: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Get stored client registration for a server
|
|
83
|
+
* @param serverUrl - The MCP server URL
|
|
84
|
+
* @returns Client info if found, null otherwise
|
|
85
|
+
*/
|
|
86
|
+
getClientInfo(serverUrl: string): Promise<ClientRegistration | null>;
|
|
87
|
+
/**
|
|
88
|
+
* Store client registration for a server
|
|
89
|
+
* @param serverUrl - The MCP server URL
|
|
90
|
+
* @param info - Client registration info
|
|
91
|
+
*/
|
|
92
|
+
setClientInfo(serverUrl: string, info: ClientRegistration): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Clear client registration for a server
|
|
95
|
+
* @param serverUrl - The MCP server URL
|
|
96
|
+
*/
|
|
97
|
+
clearClientInfo(serverUrl: string): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Clear all stored data
|
|
100
|
+
*/
|
|
101
|
+
clearAll(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Get all stored sessions (optional)
|
|
104
|
+
* @returns Array of stored sessions
|
|
105
|
+
*/
|
|
106
|
+
getAllSessions?(): Promise<StoredSession[]>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if tokens are expired or about to expire
|
|
110
|
+
* @param tokens - OAuth tokens to check
|
|
111
|
+
* @param bufferSeconds - Seconds before expiry to consider expired (default: 60)
|
|
112
|
+
* @returns True if tokens are expired or will expire within buffer
|
|
113
|
+
*/
|
|
114
|
+
declare function isTokenExpired(tokens: OAuthTokens, bufferSeconds?: number): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Compute expires_at from expires_in if not present
|
|
117
|
+
* @param tokens - OAuth tokens to enhance
|
|
118
|
+
* @returns Tokens with expires_at computed
|
|
119
|
+
*/
|
|
120
|
+
declare function withExpiresAt(tokens: OAuthTokens): OAuthTokens;
|
|
121
|
+
|
|
122
|
+
export { type ClientRegistration as C, type OAuthTokens as O, type StoredSession as S, type TokenStorage as T, isTokenExpired as i, withExpiresAt as w };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Storage Types
|
|
3
|
+
*
|
|
4
|
+
* Defines interfaces for storing OAuth tokens across different backends
|
|
5
|
+
* (memory, file, keychain, browser localStorage, etc.)
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* OAuth 2.0/2.1 token response
|
|
9
|
+
*/
|
|
10
|
+
interface OAuthTokens {
|
|
11
|
+
/** The access token issued by the authorization server */
|
|
12
|
+
access_token: string;
|
|
13
|
+
/** Token type (usually "Bearer") */
|
|
14
|
+
token_type: string;
|
|
15
|
+
/** Lifetime in seconds of the access token */
|
|
16
|
+
expires_in?: number;
|
|
17
|
+
/** Refresh token for obtaining new access tokens */
|
|
18
|
+
refresh_token?: string;
|
|
19
|
+
/** ID token (OpenID Connect) */
|
|
20
|
+
id_token?: string;
|
|
21
|
+
/** Scope granted by the authorization server */
|
|
22
|
+
scope?: string;
|
|
23
|
+
/** Computed: Unix timestamp when token expires */
|
|
24
|
+
expires_at?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* OAuth client registration information
|
|
28
|
+
* Used for Dynamic Client Registration (RFC 7591)
|
|
29
|
+
*/
|
|
30
|
+
interface ClientRegistration {
|
|
31
|
+
/** OAuth client identifier */
|
|
32
|
+
client_id: string;
|
|
33
|
+
/** OAuth client secret (for confidential clients) */
|
|
34
|
+
client_secret?: string;
|
|
35
|
+
/** Unix timestamp when client secret expires */
|
|
36
|
+
client_secret_expires_at?: number;
|
|
37
|
+
/** Token for accessing registration endpoint */
|
|
38
|
+
registration_access_token?: string;
|
|
39
|
+
/** Client metadata from registration */
|
|
40
|
+
metadata?: Record<string, unknown>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Stored session combining tokens and client info
|
|
44
|
+
*/
|
|
45
|
+
interface StoredSession {
|
|
46
|
+
/** Server URL this session is for */
|
|
47
|
+
serverUrl: string;
|
|
48
|
+
/** OAuth tokens */
|
|
49
|
+
tokens: OAuthTokens;
|
|
50
|
+
/** Client registration info (if dynamic registration used) */
|
|
51
|
+
clientInfo?: ClientRegistration;
|
|
52
|
+
/** Unix timestamp when session was created */
|
|
53
|
+
createdAt: number;
|
|
54
|
+
/** Unix timestamp when session was last updated */
|
|
55
|
+
updatedAt: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Token storage interface
|
|
59
|
+
*
|
|
60
|
+
* Implement this interface to create custom storage backends.
|
|
61
|
+
* All operations should be async to support various backends.
|
|
62
|
+
*/
|
|
63
|
+
interface TokenStorage {
|
|
64
|
+
/**
|
|
65
|
+
* Get stored tokens for a server
|
|
66
|
+
* @param serverUrl - The MCP server URL
|
|
67
|
+
* @returns Tokens if found, null otherwise
|
|
68
|
+
*/
|
|
69
|
+
getTokens(serverUrl: string): Promise<OAuthTokens | null>;
|
|
70
|
+
/**
|
|
71
|
+
* Store tokens for a server
|
|
72
|
+
* @param serverUrl - The MCP server URL
|
|
73
|
+
* @param tokens - OAuth tokens to store
|
|
74
|
+
*/
|
|
75
|
+
setTokens(serverUrl: string, tokens: OAuthTokens): Promise<void>;
|
|
76
|
+
/**
|
|
77
|
+
* Clear tokens for a server
|
|
78
|
+
* @param serverUrl - The MCP server URL
|
|
79
|
+
*/
|
|
80
|
+
clearTokens(serverUrl: string): Promise<void>;
|
|
81
|
+
/**
|
|
82
|
+
* Get stored client registration for a server
|
|
83
|
+
* @param serverUrl - The MCP server URL
|
|
84
|
+
* @returns Client info if found, null otherwise
|
|
85
|
+
*/
|
|
86
|
+
getClientInfo(serverUrl: string): Promise<ClientRegistration | null>;
|
|
87
|
+
/**
|
|
88
|
+
* Store client registration for a server
|
|
89
|
+
* @param serverUrl - The MCP server URL
|
|
90
|
+
* @param info - Client registration info
|
|
91
|
+
*/
|
|
92
|
+
setClientInfo(serverUrl: string, info: ClientRegistration): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Clear client registration for a server
|
|
95
|
+
* @param serverUrl - The MCP server URL
|
|
96
|
+
*/
|
|
97
|
+
clearClientInfo(serverUrl: string): Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Clear all stored data
|
|
100
|
+
*/
|
|
101
|
+
clearAll(): Promise<void>;
|
|
102
|
+
/**
|
|
103
|
+
* Get all stored sessions (optional)
|
|
104
|
+
* @returns Array of stored sessions
|
|
105
|
+
*/
|
|
106
|
+
getAllSessions?(): Promise<StoredSession[]>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if tokens are expired or about to expire
|
|
110
|
+
* @param tokens - OAuth tokens to check
|
|
111
|
+
* @param bufferSeconds - Seconds before expiry to consider expired (default: 60)
|
|
112
|
+
* @returns True if tokens are expired or will expire within buffer
|
|
113
|
+
*/
|
|
114
|
+
declare function isTokenExpired(tokens: OAuthTokens, bufferSeconds?: number): boolean;
|
|
115
|
+
/**
|
|
116
|
+
* Compute expires_at from expires_in if not present
|
|
117
|
+
* @param tokens - OAuth tokens to enhance
|
|
118
|
+
* @returns Tokens with expires_at computed
|
|
119
|
+
*/
|
|
120
|
+
declare function withExpiresAt(tokens: OAuthTokens): OAuthTokens;
|
|
121
|
+
|
|
122
|
+
export { type ClientRegistration as C, type OAuthTokens as O, type StoredSession as S, type TokenStorage as T, isTokenExpired as i, withExpiresAt as w };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leanmcp/auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Authentication and identity module
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Authentication and identity module with OAuth 2.1 client, token storage, and multiple providers",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -10,6 +10,26 @@
|
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"require": "./dist/index.js",
|
|
12
12
|
"import": "./dist/index.mjs"
|
|
13
|
+
},
|
|
14
|
+
"./client": {
|
|
15
|
+
"types": "./dist/client/index.d.ts",
|
|
16
|
+
"require": "./dist/client/index.js",
|
|
17
|
+
"import": "./dist/client/index.mjs"
|
|
18
|
+
},
|
|
19
|
+
"./storage": {
|
|
20
|
+
"types": "./dist/storage/index.d.ts",
|
|
21
|
+
"require": "./dist/storage/index.js",
|
|
22
|
+
"import": "./dist/storage/index.mjs"
|
|
23
|
+
},
|
|
24
|
+
"./proxy": {
|
|
25
|
+
"types": "./dist/proxy/index.d.ts",
|
|
26
|
+
"require": "./dist/proxy/index.js",
|
|
27
|
+
"import": "./dist/proxy/index.mjs"
|
|
28
|
+
},
|
|
29
|
+
"./server": {
|
|
30
|
+
"types": "./dist/server/index.d.ts",
|
|
31
|
+
"require": "./dist/server/index.js",
|
|
32
|
+
"import": "./dist/server/index.mjs"
|
|
13
33
|
}
|
|
14
34
|
},
|
|
15
35
|
"files": [
|
|
@@ -18,8 +38,8 @@
|
|
|
18
38
|
"LICENSE"
|
|
19
39
|
],
|
|
20
40
|
"scripts": {
|
|
21
|
-
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
22
|
-
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
41
|
+
"build": "tsup src/index.ts src/client/index.ts src/storage/index.ts src/proxy/index.ts src/server/index.ts --format esm,cjs --dts",
|
|
42
|
+
"dev": "tsup src/index.ts src/client/index.ts src/storage/index.ts src/proxy/index.ts src/server/index.ts --format esm,cjs --dts --watch",
|
|
23
43
|
"test": "jest --passWithNoTests",
|
|
24
44
|
"test:watch": "jest --watch"
|
|
25
45
|
},
|
|
@@ -34,14 +54,18 @@
|
|
|
34
54
|
"@types/node": "^20.0.0",
|
|
35
55
|
"dotenv": "^17.2.3",
|
|
36
56
|
"jest": "^29.7.0",
|
|
37
|
-
"ts-jest": "^29.1.0"
|
|
57
|
+
"ts-jest": "^29.1.0",
|
|
58
|
+
"express": "^5.0.0"
|
|
38
59
|
},
|
|
39
60
|
"peerDependencies": {
|
|
40
61
|
"@aws-sdk/client-cognito-identity-provider": "^3.0.0",
|
|
41
62
|
"@leanmcp/env-injection": "^0.1.0",
|
|
42
63
|
"axios": "^1.0.0",
|
|
43
64
|
"jsonwebtoken": "^9.0.0",
|
|
44
|
-
"jwk-to-pem": "^2.0.0"
|
|
65
|
+
"jwk-to-pem": "^2.0.0",
|
|
66
|
+
"keytar": "^7.0.0",
|
|
67
|
+
"open": "^10.0.0",
|
|
68
|
+
"express": "^5.0.0"
|
|
45
69
|
},
|
|
46
70
|
"peerDependenciesMeta": {
|
|
47
71
|
"@aws-sdk/client-cognito-identity-provider": {
|
|
@@ -58,6 +82,12 @@
|
|
|
58
82
|
},
|
|
59
83
|
"jwk-to-pem": {
|
|
60
84
|
"optional": true
|
|
85
|
+
},
|
|
86
|
+
"keytar": {
|
|
87
|
+
"optional": true
|
|
88
|
+
},
|
|
89
|
+
"open": {
|
|
90
|
+
"optional": true
|
|
61
91
|
}
|
|
62
92
|
},
|
|
63
93
|
"repository": {
|
|
@@ -77,7 +107,10 @@
|
|
|
77
107
|
"authentication",
|
|
78
108
|
"auth",
|
|
79
109
|
"cognito",
|
|
80
|
-
"jwt"
|
|
110
|
+
"jwt",
|
|
111
|
+
"oauth",
|
|
112
|
+
"pkce",
|
|
113
|
+
"oauth2"
|
|
81
114
|
],
|
|
82
115
|
"author": "LeanMCP <admin@leanmcp.com>",
|
|
83
116
|
"license": "MIT",
|