@leanmcp/auth 0.1.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.
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Authentication error class for better error handling
3
+ */
4
+ declare class AuthenticationError extends Error {
5
+ code: string;
6
+ constructor(message: string, code: string);
7
+ }
8
+ /**
9
+ * Decorator to protect MCP tools, prompts, resources, or entire services with authentication
10
+ *
11
+ * Usage:
12
+ *
13
+ * 1. Protect individual methods:
14
+ * ```typescript
15
+ * @Tool({ description: 'Analyze sentiment' })
16
+ * @Authenticated(authProvider)
17
+ * async analyzeSentiment(args: AnalyzeSentimentInput): Promise<AnalyzeSentimentOutput> {
18
+ * // This method requires authentication
19
+ * }
20
+ * ```
21
+ *
22
+ * 2. Protect entire service (all tools/prompts/resources):
23
+ * ```typescript
24
+ * @Authenticated(authProvider)
25
+ * export class SentimentAnalysisService {
26
+ * @Tool({ description: 'Analyze sentiment' })
27
+ * async analyzeSentiment(args: AnalyzeSentimentInput) {
28
+ * // All methods in this service require authentication
29
+ * }
30
+ * }
31
+ * ```
32
+ *
33
+ * The decorator expects authentication token in the MCP request _meta field:
34
+ * ```json
35
+ * {
36
+ * "method": "tools/call",
37
+ * "params": {
38
+ * "name": "toolName",
39
+ * "arguments": { ...businessData },
40
+ * "_meta": {
41
+ * "authorization": {
42
+ * "type": "bearer",
43
+ * "token": "your-jwt-token"
44
+ * }
45
+ * }
46
+ * }
47
+ * }
48
+ * ```
49
+ *
50
+ * @param authProvider - Instance of AuthProviderBase to use for token verification
51
+ */
52
+ declare function Authenticated(authProvider: AuthProviderBase): (target: any, propertyKey?: string | symbol, descriptor?: PropertyDescriptor) => any;
53
+ /**
54
+ * Check if a method or class requires authentication
55
+ */
56
+ declare function isAuthenticationRequired(target: any): boolean;
57
+ /**
58
+ * Get the auth provider for a method or class
59
+ */
60
+ declare function getAuthProvider(target: any): AuthProviderBase | undefined;
61
+
62
+ /**
63
+ * @leanmcp/auth - Authentication Module
64
+ *
65
+ * This module provides a base class for implementing authentication providers for MCP tools.
66
+ * Extend AuthProviderBase to integrate with different auth providers (Clerk, Stripe, Firebase, etc.)
67
+ */
68
+ /**
69
+ * Base class for authentication providers
70
+ * Extend this class to implement integrations with different auth providers
71
+ */
72
+ declare abstract class AuthProviderBase {
73
+ /**
74
+ * Initialize the auth provider with configuration
75
+ */
76
+ abstract init(config?: any): Promise<void>;
77
+ /**
78
+ * Refresh an authentication token
79
+ */
80
+ abstract refreshToken(refreshToken: string, username?: string): Promise<any>;
81
+ /**
82
+ * Verify if a token is valid
83
+ */
84
+ abstract verifyToken(token: string): Promise<boolean>;
85
+ /**
86
+ * Get user information from a token
87
+ */
88
+ abstract getUser(token: string): Promise<any>;
89
+ }
90
+ /**
91
+ * Unified AuthProvider class that dynamically selects the appropriate auth provider
92
+ * based on the provider parameter
93
+ */
94
+ declare class AuthProvider extends AuthProviderBase {
95
+ private providerInstance;
96
+ private providerType;
97
+ private config;
98
+ constructor(provider: string, config?: any);
99
+ /**
100
+ * Initialize the selected auth provider
101
+ */
102
+ init(config?: any): Promise<void>;
103
+ /**
104
+ * Refresh an authentication token
105
+ */
106
+ refreshToken(refreshToken: string, username?: string): Promise<any>;
107
+ /**
108
+ * Verify if a token is valid
109
+ */
110
+ verifyToken(token: string): Promise<boolean>;
111
+ /**
112
+ * Get user information from a token
113
+ */
114
+ getUser(token: string): Promise<any>;
115
+ /**
116
+ * Get the provider type
117
+ */
118
+ getProviderType(): string;
119
+ }
120
+
121
+ export { AuthProvider, AuthProviderBase, Authenticated, AuthenticationError, getAuthProvider, isAuthenticationRequired };
package/dist/index.js ADDED
@@ -0,0 +1,384 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
33
+
34
+ // src/decorators.ts
35
+ function Authenticated(authProvider) {
36
+ return function(target, propertyKey, descriptor) {
37
+ if (!propertyKey && !descriptor) {
38
+ Reflect.defineMetadata("auth:provider", authProvider, target);
39
+ Reflect.defineMetadata("auth:required", true, target);
40
+ const prototype = target.prototype;
41
+ const methodNames = Object.getOwnPropertyNames(prototype).filter((name) => name !== "constructor" && typeof prototype[name] === "function");
42
+ for (const methodName of methodNames) {
43
+ const originalDescriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
44
+ if (originalDescriptor && typeof originalDescriptor.value === "function") {
45
+ const originalMethod = originalDescriptor.value;
46
+ Reflect.defineMetadata("auth:provider", authProvider, originalMethod);
47
+ Reflect.defineMetadata("auth:required", true, originalMethod);
48
+ prototype[methodName] = createAuthenticatedMethod(originalMethod, authProvider);
49
+ copyMetadata(originalMethod, prototype[methodName]);
50
+ }
51
+ }
52
+ return target;
53
+ }
54
+ if (descriptor && typeof descriptor.value === "function") {
55
+ const originalMethod = descriptor.value;
56
+ Reflect.defineMetadata("auth:provider", authProvider, originalMethod);
57
+ Reflect.defineMetadata("auth:required", true, originalMethod);
58
+ descriptor.value = createAuthenticatedMethod(originalMethod, authProvider);
59
+ copyMetadata(originalMethod, descriptor.value);
60
+ return descriptor;
61
+ }
62
+ throw new Error("@Authenticated can only be applied to classes or methods");
63
+ };
64
+ }
65
+ function createAuthenticatedMethod(originalMethod, authProvider) {
66
+ return async function(args, meta) {
67
+ const token = meta?.authorization?.token;
68
+ if (!token) {
69
+ throw new AuthenticationError("Authentication required. Please provide a valid token in _meta.authorization.token", "MISSING_TOKEN");
70
+ }
71
+ try {
72
+ const isValid = await authProvider.verifyToken(token);
73
+ if (!isValid) {
74
+ throw new AuthenticationError("Invalid or expired token. Please authenticate again.", "INVALID_TOKEN");
75
+ }
76
+ } catch (error) {
77
+ if (error instanceof AuthenticationError) {
78
+ throw error;
79
+ }
80
+ throw new AuthenticationError(`Token verification failed: ${error instanceof Error ? error.message : String(error)}`, "VERIFICATION_FAILED");
81
+ }
82
+ return originalMethod.apply(this, [
83
+ args
84
+ ]);
85
+ };
86
+ }
87
+ function copyMetadata(source, target) {
88
+ const metadataKeys = Reflect.getMetadataKeys(source);
89
+ for (const key of metadataKeys) {
90
+ const value = Reflect.getMetadata(key, source);
91
+ Reflect.defineMetadata(key, value, target);
92
+ }
93
+ const designKeys = [
94
+ "design:type",
95
+ "design:paramtypes",
96
+ "design:returntype"
97
+ ];
98
+ for (const key of designKeys) {
99
+ const value = Reflect.getMetadata(key, source);
100
+ if (value !== void 0) {
101
+ Reflect.defineMetadata(key, value, target);
102
+ }
103
+ }
104
+ }
105
+ function isAuthenticationRequired(target) {
106
+ return Reflect.getMetadata("auth:required", target) === true;
107
+ }
108
+ function getAuthProvider(target) {
109
+ return Reflect.getMetadata("auth:provider", target);
110
+ }
111
+ var import_reflect_metadata, AuthenticationError;
112
+ var init_decorators = __esm({
113
+ "src/decorators.ts"() {
114
+ "use strict";
115
+ import_reflect_metadata = require("reflect-metadata");
116
+ AuthenticationError = class extends Error {
117
+ static {
118
+ __name(this, "AuthenticationError");
119
+ }
120
+ code;
121
+ constructor(message, code) {
122
+ super(message), this.code = code;
123
+ this.name = "AuthenticationError";
124
+ }
125
+ };
126
+ __name(Authenticated, "Authenticated");
127
+ __name(createAuthenticatedMethod, "createAuthenticatedMethod");
128
+ __name(copyMetadata, "copyMetadata");
129
+ __name(isAuthenticationRequired, "isAuthenticationRequired");
130
+ __name(getAuthProvider, "getAuthProvider");
131
+ }
132
+ });
133
+
134
+ // src/providers/cognito.ts
135
+ var cognito_exports = {};
136
+ __export(cognito_exports, {
137
+ AuthCognito: () => AuthCognito
138
+ });
139
+ var import_client_cognito_identity_provider, import_crypto, import_axios, import_jsonwebtoken, import_jwk_to_pem, AuthCognito;
140
+ var init_cognito = __esm({
141
+ "src/providers/cognito.ts"() {
142
+ "use strict";
143
+ import_client_cognito_identity_provider = require("@aws-sdk/client-cognito-identity-provider");
144
+ import_crypto = require("crypto");
145
+ import_axios = __toESM(require("axios"));
146
+ import_jsonwebtoken = __toESM(require("jsonwebtoken"));
147
+ import_jwk_to_pem = __toESM(require("jwk-to-pem"));
148
+ init_index();
149
+ AuthCognito = class extends AuthProviderBase {
150
+ static {
151
+ __name(this, "AuthCognito");
152
+ }
153
+ cognito = null;
154
+ region = "";
155
+ userPoolId = "";
156
+ clientId = "";
157
+ clientSecret = "";
158
+ jwksCache = null;
159
+ /**
160
+ * Initialize the Cognito client with configuration
161
+ */
162
+ async init(config) {
163
+ this.region = config?.region || process.env.AWS_REGION || "";
164
+ this.userPoolId = config?.userPoolId || process.env.COGNITO_USER_POOL_ID || "";
165
+ this.clientId = config?.clientId || process.env.COGNITO_CLIENT_ID || "";
166
+ this.clientSecret = config?.clientSecret || process.env.COGNITO_CLIENT_SECRET || "";
167
+ if (!this.region || !this.userPoolId || !this.clientId) {
168
+ throw new Error("Missing required Cognito configuration: region, userPoolId, and clientId are required");
169
+ }
170
+ this.cognito = new import_client_cognito_identity_provider.CognitoIdentityProviderClient({
171
+ region: this.region
172
+ });
173
+ }
174
+ /**
175
+ * Refresh access tokens using a refresh token
176
+ */
177
+ async refreshToken(refreshToken, username) {
178
+ if (!this.cognito) {
179
+ throw new Error("CognitoAuth not initialized. Call init() first.");
180
+ }
181
+ const authParameters = {
182
+ REFRESH_TOKEN: refreshToken
183
+ };
184
+ if (this.clientSecret) {
185
+ const usernameForHash = username;
186
+ const secretHash = this.calculateSecretHash(usernameForHash);
187
+ authParameters.SECRET_HASH = secretHash;
188
+ }
189
+ const command = new import_client_cognito_identity_provider.InitiateAuthCommand({
190
+ AuthFlow: "REFRESH_TOKEN_AUTH",
191
+ ClientId: this.clientId,
192
+ AuthParameters: authParameters
193
+ });
194
+ return await this.cognito.send(command);
195
+ }
196
+ /**
197
+ * Verify a Cognito JWT token using JWKS
198
+ */
199
+ async verifyToken(token) {
200
+ try {
201
+ await this.verifyJwt(token);
202
+ return true;
203
+ } catch (error) {
204
+ if (error instanceof Error) {
205
+ if (error.message.includes("jwt expired")) {
206
+ throw new Error("Token has expired");
207
+ } else if (error.message.includes("invalid signature")) {
208
+ throw new Error("Invalid token signature");
209
+ } else if (error.message.includes("jwt malformed")) {
210
+ throw new Error("Malformed token");
211
+ } else if (error.message.includes("invalid issuer")) {
212
+ throw new Error("Invalid token issuer");
213
+ }
214
+ throw error;
215
+ }
216
+ return false;
217
+ }
218
+ }
219
+ /**
220
+ * Get user information from an ID token
221
+ */
222
+ async getUser(idToken) {
223
+ const decoded = import_jsonwebtoken.default.decode(idToken);
224
+ if (!decoded) {
225
+ throw new Error("Invalid ID token");
226
+ }
227
+ return {
228
+ username: decoded["cognito:username"],
229
+ email: decoded.email,
230
+ email_verified: decoded.email_verified,
231
+ sub: decoded.sub,
232
+ attributes: decoded
233
+ };
234
+ }
235
+ /**
236
+ * Fetch JWKS from Cognito (cached)
237
+ */
238
+ async fetchJWKS() {
239
+ if (!this.jwksCache) {
240
+ const jwksUri = `https://cognito-idp.${this.region}.amazonaws.com/${this.userPoolId}/.well-known/jwks.json`;
241
+ const { data } = await import_axios.default.get(jwksUri);
242
+ this.jwksCache = data.keys;
243
+ }
244
+ return this.jwksCache;
245
+ }
246
+ /**
247
+ * Verify JWT token using JWKS
248
+ */
249
+ async verifyJwt(token) {
250
+ const decoded = import_jsonwebtoken.default.decode(token, {
251
+ complete: true
252
+ });
253
+ if (!decoded) {
254
+ throw new Error("Invalid token");
255
+ }
256
+ const jwks = await this.fetchJWKS();
257
+ const key = jwks.find((k) => k.kid === decoded.header.kid);
258
+ if (!key) {
259
+ throw new Error("Signing key not found in JWKS");
260
+ }
261
+ const pem = (0, import_jwk_to_pem.default)(key);
262
+ return import_jsonwebtoken.default.verify(token, pem, {
263
+ algorithms: [
264
+ "RS256"
265
+ ],
266
+ issuer: `https://cognito-idp.${this.region}.amazonaws.com/${this.userPoolId}`
267
+ });
268
+ }
269
+ /**
270
+ * Calculate SECRET_HASH for Cognito authentication
271
+ * SECRET_HASH = Base64(HMAC_SHA256(username + clientId, clientSecret))
272
+ */
273
+ calculateSecretHash(username) {
274
+ const message = username + this.clientId;
275
+ const hmac = (0, import_crypto.createHmac)("sha256", this.clientSecret);
276
+ hmac.update(message);
277
+ return hmac.digest("base64");
278
+ }
279
+ };
280
+ }
281
+ });
282
+
283
+ // src/index.ts
284
+ var index_exports = {};
285
+ __export(index_exports, {
286
+ AuthProvider: () => AuthProvider,
287
+ AuthProviderBase: () => AuthProviderBase,
288
+ Authenticated: () => Authenticated,
289
+ AuthenticationError: () => AuthenticationError,
290
+ getAuthProvider: () => getAuthProvider,
291
+ isAuthenticationRequired: () => isAuthenticationRequired
292
+ });
293
+ module.exports = __toCommonJS(index_exports);
294
+ var import_reflect_metadata2, AuthProviderBase, AuthProvider;
295
+ var init_index = __esm({
296
+ "src/index.ts"() {
297
+ import_reflect_metadata2 = require("reflect-metadata");
298
+ init_decorators();
299
+ AuthProviderBase = class {
300
+ static {
301
+ __name(this, "AuthProviderBase");
302
+ }
303
+ };
304
+ AuthProvider = class extends AuthProviderBase {
305
+ static {
306
+ __name(this, "AuthProvider");
307
+ }
308
+ providerInstance = null;
309
+ providerType;
310
+ config;
311
+ constructor(provider, config) {
312
+ super();
313
+ this.providerType = provider.toLowerCase();
314
+ this.config = config;
315
+ }
316
+ /**
317
+ * Initialize the selected auth provider
318
+ */
319
+ async init(config) {
320
+ const finalConfig = config || this.config;
321
+ switch (this.providerType) {
322
+ case "cognito": {
323
+ const { AuthCognito: AuthCognito2 } = await Promise.resolve().then(() => (init_cognito(), cognito_exports));
324
+ this.providerInstance = new AuthCognito2();
325
+ await this.providerInstance.init(finalConfig);
326
+ break;
327
+ }
328
+ // Add more providers here in the future
329
+ // case 'clerk': {
330
+ // const { AuthClerk } = await import('./providers/clerk');
331
+ // this.providerInstance = new AuthClerk();
332
+ // await this.providerInstance.init(finalConfig);
333
+ // break;
334
+ // }
335
+ default:
336
+ throw new Error(`Unsupported auth provider: ${this.providerType}. Supported providers: cognito`);
337
+ }
338
+ }
339
+ /**
340
+ * Refresh an authentication token
341
+ */
342
+ async refreshToken(refreshToken, username) {
343
+ if (!this.providerInstance) {
344
+ throw new Error("AuthProvider not initialized. Call init() first.");
345
+ }
346
+ return this.providerInstance.refreshToken(refreshToken, username);
347
+ }
348
+ /**
349
+ * Verify if a token is valid
350
+ */
351
+ async verifyToken(token) {
352
+ if (!this.providerInstance) {
353
+ throw new Error("AuthProvider not initialized. Call init() first.");
354
+ }
355
+ return this.providerInstance.verifyToken(token);
356
+ }
357
+ /**
358
+ * Get user information from a token
359
+ */
360
+ async getUser(token) {
361
+ if (!this.providerInstance) {
362
+ throw new Error("AuthProvider not initialized. Call init() first.");
363
+ }
364
+ return this.providerInstance.getUser(token);
365
+ }
366
+ /**
367
+ * Get the provider type
368
+ */
369
+ getProviderType() {
370
+ return this.providerType;
371
+ }
372
+ };
373
+ }
374
+ });
375
+ init_index();
376
+ // Annotate the CommonJS export names for ESM import in node:
377
+ 0 && (module.exports = {
378
+ AuthProvider,
379
+ AuthProviderBase,
380
+ Authenticated,
381
+ AuthenticationError,
382
+ getAuthProvider,
383
+ isAuthenticationRequired
384
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,16 @@
1
+ import {
2
+ AuthProvider,
3
+ AuthProviderBase,
4
+ Authenticated,
5
+ AuthenticationError,
6
+ getAuthProvider,
7
+ isAuthenticationRequired
8
+ } from "./chunk-NALGJYQB.mjs";
9
+ export {
10
+ AuthProvider,
11
+ AuthProviderBase,
12
+ Authenticated,
13
+ AuthenticationError,
14
+ getAuthProvider,
15
+ isAuthenticationRequired
16
+ };
package/package.json ADDED
@@ -0,0 +1,81 @@
1
+ {
2
+ "name": "@leanmcp/auth",
3
+ "version": "0.1.0",
4
+ "description": "Authentication and identity module supporting multiple providers",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.js",
12
+ "import": "./dist/index.mjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format esm,cjs --dts",
22
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
23
+ "test": "jest --passWithNoTests",
24
+ "test:watch": "jest --watch"
25
+ },
26
+ "dependencies": {
27
+ "@leanmcp/core": "^0.1.0",
28
+ "reflect-metadata": "^0.2.1"
29
+ },
30
+ "devDependencies": {
31
+ "@types/jest": "^29.5.0",
32
+ "@types/jsonwebtoken": "^9.0.10",
33
+ "@types/jwk-to-pem": "^2.0.3",
34
+ "@types/node": "^20.0.0",
35
+ "dotenv": "^17.2.3"
36
+ },
37
+ "peerDependencies": {
38
+ "@aws-sdk/client-cognito-identity-provider": "^3.0.0",
39
+ "axios": "^1.0.0",
40
+ "jsonwebtoken": "^9.0.0",
41
+ "jwk-to-pem": "^2.0.0"
42
+ },
43
+ "peerDependenciesMeta": {
44
+ "@aws-sdk/client-cognito-identity-provider": {
45
+ "optional": true
46
+ },
47
+ "axios": {
48
+ "optional": true
49
+ },
50
+ "jsonwebtoken": {
51
+ "optional": true
52
+ },
53
+ "jwk-to-pem": {
54
+ "optional": true
55
+ }
56
+ },
57
+ "repository": {
58
+ "type": "git",
59
+ "url": "git+https://github.com/LeanMCP/leanmcp-sdk.git",
60
+ "directory": "packages/auth"
61
+ },
62
+ "homepage": "https://github.com/LeanMCP/leanmcp-sdk#readme",
63
+ "bugs": {
64
+ "url": "https://github.com/LeanMCP/leanmcp-sdk/issues"
65
+ },
66
+ "keywords": [
67
+ "mcp",
68
+ "model-context-protocol",
69
+ "typescript",
70
+ "decorators",
71
+ "authentication",
72
+ "auth",
73
+ "cognito",
74
+ "jwt"
75
+ ],
76
+ "author": "LeanMCP <admin@leanmcp.com>",
77
+ "license": "MIT",
78
+ "publishConfig": {
79
+ "access": "public"
80
+ }
81
+ }