@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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 LeanMCP Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,321 @@
1
+ # @leanmcp/auth
2
+
3
+ Authentication module for LeanMCP providing token-based authentication decorators and multi-provider support.
4
+
5
+ ## Features
6
+
7
+ - **@Authenticated decorator** - Protect MCP tools, prompts, and resources with token authentication
8
+ - **Multi-provider support** - AWS Cognito (more providers coming soon)
9
+ - **Method or class-level protection** - Apply to individual methods or entire services
10
+ - **Automatic token validation** - Validates tokens before method execution
11
+ - **Custom error handling** - Detailed error codes for different auth failures
12
+ - **Type-safe** - Full TypeScript support with type inference
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @leanmcp/auth @leanmcp/core
18
+ ```
19
+
20
+ ### Provider Dependencies
21
+
22
+ For AWS Cognito:
23
+ ```bash
24
+ npm install @aws-sdk/client-cognito-identity-provider axios jsonwebtoken jwk-to-pem
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ### 1. Initialize Auth Provider
30
+
31
+ ```typescript
32
+ import { AuthProvider } from "@leanmcp/auth";
33
+
34
+ // Initialize with AWS Cognito
35
+ const authProvider = new AuthProvider('cognito', {
36
+ region: 'us-east-1',
37
+ userPoolId: 'us-east-1_XXXXXXXXX',
38
+ clientId: 'your-client-id'
39
+ });
40
+
41
+ await authProvider.init();
42
+ ```
43
+
44
+ ### 2. Protect Individual Methods
45
+
46
+ ```typescript
47
+ import { Tool } from "@leanmcp/core";
48
+ import { Authenticated } from "@leanmcp/auth";
49
+
50
+ export class SentimentService {
51
+ // This method requires authentication
52
+ @Tool({ description: 'Analyze sentiment (requires auth)' })
53
+ @Authenticated(authProvider)
54
+ async analyzeSentiment(input: { text: string }) {
55
+ // Token is automatically validated from _meta.authorization.token
56
+ // Only business arguments are passed to the method
57
+ return { sentiment: 'positive', score: 0.8 };
58
+ }
59
+
60
+ // This method is public
61
+ @Tool({ description: 'Get sentiment categories (public)' })
62
+ async getCategories() {
63
+ return { categories: ['positive', 'negative', 'neutral'] };
64
+ }
65
+ }
66
+ ```
67
+
68
+ ### 3. Protect Entire Service
69
+
70
+ ```typescript
71
+ import { Authenticated } from "@leanmcp/auth";
72
+
73
+ // All methods in this class require authentication
74
+ @Authenticated(authProvider)
75
+ export class SecureService {
76
+ @Tool({ description: 'Protected tool 1' })
77
+ async tool1(input: { data: string }) {
78
+ // All methods require authentication via _meta
79
+ }
80
+
81
+ @Tool({ description: 'Protected tool 2' })
82
+ async tool2(input: { data: string }) {
83
+ // All methods require authentication via _meta
84
+ }
85
+ }
86
+ ```
87
+
88
+ ## Usage
89
+
90
+ ### Client Side - Calling Protected Methods
91
+
92
+ Authentication tokens are passed via the `_meta` field following MCP protocol standards:
93
+
94
+ ```typescript
95
+ // With token (succeeds)
96
+ await mcpClient.callTool({
97
+ name: "analyzeSentiment",
98
+ arguments: {
99
+ text: "Hello world"
100
+ },
101
+ _meta: {
102
+ authorization: {
103
+ type: "bearer",
104
+ token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
105
+ }
106
+ }
107
+ });
108
+
109
+ // Without token (fails with MISSING_TOKEN error)
110
+ await mcpClient.callTool({
111
+ name: "analyzeSentiment",
112
+ arguments: {
113
+ text: "Hello world"
114
+ }
115
+ });
116
+ ```
117
+
118
+ ### Raw MCP Request Format
119
+
120
+ ```json
121
+ {
122
+ "method": "tools/call",
123
+ "params": {
124
+ "name": "analyzeSentiment",
125
+ "arguments": {
126
+ "text": "Hello world"
127
+ },
128
+ "_meta": {
129
+ "authorization": {
130
+ "type": "bearer",
131
+ "token": "your-jwt-token"
132
+ }
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### Error Handling
139
+
140
+ ```typescript
141
+ import { AuthenticationError } from "@leanmcp/auth";
142
+
143
+ try {
144
+ await service.protectedMethod({ text: "test" });
145
+ } catch (error) {
146
+ if (error instanceof AuthenticationError) {
147
+ switch (error.code) {
148
+ case 'MISSING_TOKEN':
149
+ console.log('No token provided in request');
150
+ break;
151
+ case 'INVALID_TOKEN':
152
+ console.log('Token is invalid or expired');
153
+ break;
154
+ case 'VERIFICATION_FAILED':
155
+ console.log('Token verification failed:', error.message);
156
+ break;
157
+ }
158
+ }
159
+ }
160
+ ```
161
+
162
+ ## Error Codes
163
+
164
+ | Code | When | Message |
165
+ |------|------|---------|
166
+ | `MISSING_TOKEN` | No token in `_meta` | "Authentication required. Please provide a valid token in _meta.authorization.token" |
167
+ | `INVALID_TOKEN` | Token invalid/expired | "Invalid or expired token. Please authenticate again." |
168
+ | `VERIFICATION_FAILED` | Verification error | "Token verification failed: [details]" |
169
+
170
+ ## Supported Auth Providers
171
+
172
+ ### AWS Cognito
173
+
174
+ ```typescript
175
+ const authProvider = new AuthProvider('cognito', {
176
+ region: 'us-east-1',
177
+ userPoolId: 'us-east-1_XXXXXXXXX',
178
+ clientId: 'your-client-id'
179
+ });
180
+ await authProvider.init();
181
+ ```
182
+
183
+ **Token Requirements:**
184
+ - JWT token from AWS Cognito User Pool
185
+ - Token must be valid and not expired
186
+ - Token must be issued by the configured User Pool
187
+
188
+ ### More Providers Coming Soon
189
+
190
+ - Clerk
191
+ - Auth0
192
+ - Firebase Auth
193
+ - Custom JWT providers
194
+
195
+ ## API Reference
196
+
197
+ ### AuthProvider
198
+
199
+ ```typescript
200
+ class AuthProvider {
201
+ constructor(provider: string, config: any);
202
+ async init(config?: any): Promise<void>;
203
+ async verifyToken(token: string): Promise<boolean>;
204
+ async refreshToken(refreshToken: string, username?: string): Promise<any>;
205
+ async getUser(token: string): Promise<any>;
206
+ getProviderType(): string;
207
+ }
208
+ ```
209
+
210
+ ### @Authenticated Decorator
211
+
212
+ ```typescript
213
+ function Authenticated(authProvider: AuthProvider): ClassDecorator | MethodDecorator;
214
+ ```
215
+
216
+ Can be applied to:
217
+ - **Classes** - Protects all methods in the class
218
+ - **Methods** - Protects individual methods
219
+
220
+ ### AuthenticationError
221
+
222
+ ```typescript
223
+ class AuthenticationError extends Error {
224
+ code: 'MISSING_TOKEN' | 'INVALID_TOKEN' | 'VERIFICATION_FAILED';
225
+ constructor(message: string, code: string);
226
+ }
227
+ ```
228
+
229
+ ### Helper Functions
230
+
231
+ ```typescript
232
+ // Check if method/class requires authentication
233
+ function isAuthenticationRequired(target: any, propertyKey?: string): boolean;
234
+
235
+ // Get auth provider for method/class
236
+ function getAuthProvider(target: any, propertyKey?: string): AuthProvider | undefined;
237
+ ```
238
+
239
+ ## Environment Variables
240
+
241
+ For AWS Cognito:
242
+ ```bash
243
+ AWS_REGION=us-east-1
244
+ COGNITO_USER_POOL_ID=us-east-1_XXXXXXXXX
245
+ COGNITO_CLIENT_ID=your-client-id
246
+ ```
247
+
248
+ ## Complete Example
249
+
250
+ See [examples/slack-with-auth](../../examples/slack-with-auth) for a complete working example with AWS Cognito.
251
+
252
+ ```typescript
253
+ import { createHTTPServer, MCPServer } from "@leanmcp/core";
254
+ import { AuthProvider, Authenticated } from "@leanmcp/auth";
255
+
256
+ // Initialize auth
257
+ const authProvider = new AuthProvider('cognito', {
258
+ region: process.env.AWS_REGION,
259
+ userPoolId: process.env.COGNITO_USER_POOL_ID,
260
+ clientId: process.env.COGNITO_CLIENT_ID
261
+ });
262
+ await authProvider.init();
263
+
264
+ // Create service with protected methods
265
+ @Authenticated(authProvider)
266
+ class MyService {
267
+ @Tool()
268
+ async protectedTool(input: { data: string }) {
269
+ return { result: "Protected data" };
270
+ }
271
+ }
272
+
273
+ // Start server
274
+ const serverFactory = () => {
275
+ const server = new MCPServer({ name: "auth-server", version: "1.0.0" });
276
+ server.registerService(new MyService());
277
+ return server.getServer();
278
+ };
279
+
280
+ await createHTTPServer(serverFactory, { port: 3000 });
281
+ ```
282
+
283
+ ## How It Works
284
+
285
+ 1. **Request arrives** with `_meta.authorization.token`
286
+ 2. **Decorator intercepts** the method call before execution
287
+ 3. **Token is extracted** from `_meta.authorization.token`
288
+ 4. **Token is validated** using the configured auth provider
289
+ 5. **Method executes** with clean business arguments (no token)
290
+ 6. **Response returns** to client
291
+
292
+ **Key Benefits:**
293
+ - **Clean separation** - Authentication metadata separate from business data
294
+ - **MCP compliant** - Follows standard `_meta` pattern
295
+ - **Type-safe** - Input classes don't need token fields
296
+ - **Reusable** - Same input classes work for authenticated and public methods
297
+
298
+ ## Best Practices
299
+
300
+ 1. **Always use HTTPS in production** - Tokens should never be sent over HTTP
301
+ 2. **Store tokens securely** - Use secure storage mechanisms (keychain, encrypted storage)
302
+ 3. **Implement token refresh** - Use refresh tokens to get new access tokens
303
+ 4. **Add rate limiting** - Protect against brute force attacks
304
+ 5. **Log authentication failures** - Monitor for suspicious activity
305
+ 6. **Use environment variables** - Never hardcode credentials
306
+ 7. **Use _meta for auth** - Don't include tokens in business arguments
307
+
308
+ ## License
309
+
310
+ MIT
311
+
312
+ ## Related Packages
313
+
314
+ - [@leanmcp/core](../core) - Core MCP server functionality
315
+ - [@leanmcp/cli](../cli) - CLI tool for creating new projects
316
+ - [@leanmcp/utils](../utils) - Utility functions
317
+
318
+ ## Links
319
+
320
+ - [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
321
+ - [Documentation](https://github.com/LeanMCP/leanmcp-sdk#readme)
@@ -0,0 +1,185 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/index.ts
5
+ import "reflect-metadata";
6
+
7
+ // src/decorators.ts
8
+ import "reflect-metadata";
9
+ var AuthenticationError = class extends Error {
10
+ static {
11
+ __name(this, "AuthenticationError");
12
+ }
13
+ code;
14
+ constructor(message, code) {
15
+ super(message), this.code = code;
16
+ this.name = "AuthenticationError";
17
+ }
18
+ };
19
+ function Authenticated(authProvider) {
20
+ return function(target, propertyKey, descriptor) {
21
+ if (!propertyKey && !descriptor) {
22
+ Reflect.defineMetadata("auth:provider", authProvider, target);
23
+ Reflect.defineMetadata("auth:required", true, target);
24
+ const prototype = target.prototype;
25
+ const methodNames = Object.getOwnPropertyNames(prototype).filter((name) => name !== "constructor" && typeof prototype[name] === "function");
26
+ for (const methodName of methodNames) {
27
+ const originalDescriptor = Object.getOwnPropertyDescriptor(prototype, methodName);
28
+ if (originalDescriptor && typeof originalDescriptor.value === "function") {
29
+ const originalMethod = originalDescriptor.value;
30
+ Reflect.defineMetadata("auth:provider", authProvider, originalMethod);
31
+ Reflect.defineMetadata("auth:required", true, originalMethod);
32
+ prototype[methodName] = createAuthenticatedMethod(originalMethod, authProvider);
33
+ copyMetadata(originalMethod, prototype[methodName]);
34
+ }
35
+ }
36
+ return target;
37
+ }
38
+ if (descriptor && typeof descriptor.value === "function") {
39
+ const originalMethod = descriptor.value;
40
+ Reflect.defineMetadata("auth:provider", authProvider, originalMethod);
41
+ Reflect.defineMetadata("auth:required", true, originalMethod);
42
+ descriptor.value = createAuthenticatedMethod(originalMethod, authProvider);
43
+ copyMetadata(originalMethod, descriptor.value);
44
+ return descriptor;
45
+ }
46
+ throw new Error("@Authenticated can only be applied to classes or methods");
47
+ };
48
+ }
49
+ __name(Authenticated, "Authenticated");
50
+ function createAuthenticatedMethod(originalMethod, authProvider) {
51
+ return async function(args, meta) {
52
+ const token = meta?.authorization?.token;
53
+ if (!token) {
54
+ throw new AuthenticationError("Authentication required. Please provide a valid token in _meta.authorization.token", "MISSING_TOKEN");
55
+ }
56
+ try {
57
+ const isValid = await authProvider.verifyToken(token);
58
+ if (!isValid) {
59
+ throw new AuthenticationError("Invalid or expired token. Please authenticate again.", "INVALID_TOKEN");
60
+ }
61
+ } catch (error) {
62
+ if (error instanceof AuthenticationError) {
63
+ throw error;
64
+ }
65
+ throw new AuthenticationError(`Token verification failed: ${error instanceof Error ? error.message : String(error)}`, "VERIFICATION_FAILED");
66
+ }
67
+ return originalMethod.apply(this, [
68
+ args
69
+ ]);
70
+ };
71
+ }
72
+ __name(createAuthenticatedMethod, "createAuthenticatedMethod");
73
+ function copyMetadata(source, target) {
74
+ const metadataKeys = Reflect.getMetadataKeys(source);
75
+ for (const key of metadataKeys) {
76
+ const value = Reflect.getMetadata(key, source);
77
+ Reflect.defineMetadata(key, value, target);
78
+ }
79
+ const designKeys = [
80
+ "design:type",
81
+ "design:paramtypes",
82
+ "design:returntype"
83
+ ];
84
+ for (const key of designKeys) {
85
+ const value = Reflect.getMetadata(key, source);
86
+ if (value !== void 0) {
87
+ Reflect.defineMetadata(key, value, target);
88
+ }
89
+ }
90
+ }
91
+ __name(copyMetadata, "copyMetadata");
92
+ function isAuthenticationRequired(target) {
93
+ return Reflect.getMetadata("auth:required", target) === true;
94
+ }
95
+ __name(isAuthenticationRequired, "isAuthenticationRequired");
96
+ function getAuthProvider(target) {
97
+ return Reflect.getMetadata("auth:provider", target);
98
+ }
99
+ __name(getAuthProvider, "getAuthProvider");
100
+
101
+ // src/index.ts
102
+ var AuthProviderBase = class {
103
+ static {
104
+ __name(this, "AuthProviderBase");
105
+ }
106
+ };
107
+ var AuthProvider = class extends AuthProviderBase {
108
+ static {
109
+ __name(this, "AuthProvider");
110
+ }
111
+ providerInstance = null;
112
+ providerType;
113
+ config;
114
+ constructor(provider, config) {
115
+ super();
116
+ this.providerType = provider.toLowerCase();
117
+ this.config = config;
118
+ }
119
+ /**
120
+ * Initialize the selected auth provider
121
+ */
122
+ async init(config) {
123
+ const finalConfig = config || this.config;
124
+ switch (this.providerType) {
125
+ case "cognito": {
126
+ const { AuthCognito } = await import("./cognito-VCVS77OX.mjs");
127
+ this.providerInstance = new AuthCognito();
128
+ await this.providerInstance.init(finalConfig);
129
+ break;
130
+ }
131
+ // Add more providers here in the future
132
+ // case 'clerk': {
133
+ // const { AuthClerk } = await import('./providers/clerk');
134
+ // this.providerInstance = new AuthClerk();
135
+ // await this.providerInstance.init(finalConfig);
136
+ // break;
137
+ // }
138
+ default:
139
+ throw new Error(`Unsupported auth provider: ${this.providerType}. Supported providers: cognito`);
140
+ }
141
+ }
142
+ /**
143
+ * Refresh an authentication token
144
+ */
145
+ async refreshToken(refreshToken, username) {
146
+ if (!this.providerInstance) {
147
+ throw new Error("AuthProvider not initialized. Call init() first.");
148
+ }
149
+ return this.providerInstance.refreshToken(refreshToken, username);
150
+ }
151
+ /**
152
+ * Verify if a token is valid
153
+ */
154
+ async verifyToken(token) {
155
+ if (!this.providerInstance) {
156
+ throw new Error("AuthProvider not initialized. Call init() first.");
157
+ }
158
+ return this.providerInstance.verifyToken(token);
159
+ }
160
+ /**
161
+ * Get user information from a token
162
+ */
163
+ async getUser(token) {
164
+ if (!this.providerInstance) {
165
+ throw new Error("AuthProvider not initialized. Call init() first.");
166
+ }
167
+ return this.providerInstance.getUser(token);
168
+ }
169
+ /**
170
+ * Get the provider type
171
+ */
172
+ getProviderType() {
173
+ return this.providerType;
174
+ }
175
+ };
176
+
177
+ export {
178
+ __name,
179
+ AuthenticationError,
180
+ Authenticated,
181
+ isAuthenticationRequired,
182
+ getAuthProvider,
183
+ AuthProviderBase,
184
+ AuthProvider
185
+ };