@leanmcp/env-injection 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 +21 -0
- package/README.md +206 -0
- package/dist/index.d.mts +66 -0
- package/dist/index.d.ts +66 -0
- package/dist/index.js +113 -0
- package/dist/index.mjs +82 -0
- package/package.json +49 -0
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,206 @@
|
|
|
1
|
+
# @leanmcp/env-injection
|
|
2
|
+
|
|
3
|
+
Request-scoped environment variable injection for LeanMCP tools. Enables user-specific secrets (API keys, tokens) to be securely fetched and accessed within MCP tool methods.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Request-scoped isolation** - Each user's secrets are isolated using `AsyncLocalStorage`
|
|
8
|
+
- **@RequireEnv decorator** - Validate required env vars exist before method execution
|
|
9
|
+
- **getEnv() / getAllEnv()** - Access user-specific secrets in your tool code
|
|
10
|
+
- **Type-safe** - Full TypeScript support
|
|
11
|
+
- **Works with @leanmcp/auth** - Integrates with `@Authenticated` decorator
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @leanmcp/env-injection @leanmcp/auth @leanmcp/core
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### 1. Configure Auth Provider with projectId
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { AuthProvider } from "@leanmcp/auth";
|
|
25
|
+
|
|
26
|
+
export const projectId = process.env.LEANMCP_PROJECT_ID;
|
|
27
|
+
|
|
28
|
+
export const authProvider = new AuthProvider('leanmcp', {
|
|
29
|
+
apiKey: process.env.LEANMCP_API_KEY,
|
|
30
|
+
orchestrationApiUrl: 'https://api.leanmcp.com',
|
|
31
|
+
authUrl: 'https://auth.leanmcp.com'
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await authProvider.init();
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Use @RequireEnv and getEnv()
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
import { Tool } from "@leanmcp/core";
|
|
41
|
+
import { Authenticated } from "@leanmcp/auth";
|
|
42
|
+
import { RequireEnv, getEnv } from "@leanmcp/env-injection";
|
|
43
|
+
import { authProvider, projectId } from "./config.js";
|
|
44
|
+
|
|
45
|
+
@Authenticated(authProvider, { projectId })
|
|
46
|
+
export class SlackService {
|
|
47
|
+
|
|
48
|
+
@Tool({ description: 'Send a message to Slack' })
|
|
49
|
+
@RequireEnv(["SLACK_TOKEN", "SLACK_CHANNEL"])
|
|
50
|
+
async sendMessage(args: { message: string }) {
|
|
51
|
+
// getEnv() returns THIS USER's secret, not a global env var
|
|
52
|
+
const token = getEnv("SLACK_TOKEN")!;
|
|
53
|
+
const channel = getEnv("SLACK_CHANNEL")!;
|
|
54
|
+
|
|
55
|
+
// Send message using user's own Slack token
|
|
56
|
+
await slackApi.postMessage(channel, args.message, token);
|
|
57
|
+
|
|
58
|
+
return { success: true, channel };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API Reference
|
|
64
|
+
|
|
65
|
+
### runWithEnv(env, fn)
|
|
66
|
+
|
|
67
|
+
Run a function with environment variables in scope. Used internally by `@Authenticated`.
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { runWithEnv } from "@leanmcp/env-injection";
|
|
71
|
+
|
|
72
|
+
await runWithEnv({ API_KEY: "secret123" }, async () => {
|
|
73
|
+
console.log(getEnv("API_KEY")); // "secret123"
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### getEnv(key)
|
|
78
|
+
|
|
79
|
+
Get a single environment variable from the current request context.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { getEnv } from "@leanmcp/env-injection";
|
|
83
|
+
|
|
84
|
+
const token = getEnv("SLACK_TOKEN");
|
|
85
|
+
// Returns undefined if key doesn't exist
|
|
86
|
+
// Throws if called outside env context (projectId not configured)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### getAllEnv()
|
|
90
|
+
|
|
91
|
+
Get all environment variables from the current request context.
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
import { getAllEnv } from "@leanmcp/env-injection";
|
|
95
|
+
|
|
96
|
+
const env = getAllEnv();
|
|
97
|
+
// { SLACK_TOKEN: "xoxb-...", SLACK_CHANNEL: "#general" }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### hasEnvContext()
|
|
101
|
+
|
|
102
|
+
Check if currently inside an env context.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { hasEnvContext } from "@leanmcp/env-injection";
|
|
106
|
+
|
|
107
|
+
if (hasEnvContext()) {
|
|
108
|
+
// Safe to call getEnv()
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### @RequireEnv(keys)
|
|
113
|
+
|
|
114
|
+
Decorator to validate required environment variables exist before method execution.
|
|
115
|
+
|
|
116
|
+
```typescript
|
|
117
|
+
import { RequireEnv } from "@leanmcp/env-injection";
|
|
118
|
+
|
|
119
|
+
@RequireEnv(["SLACK_TOKEN", "SLACK_CHANNEL"])
|
|
120
|
+
async sendMessage(args: { message: string }) {
|
|
121
|
+
// Method only executes if BOTH keys exist
|
|
122
|
+
// Otherwise throws: "Missing required environment variables: SLACK_TOKEN, SLACK_CHANNEL"
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Requirements:**
|
|
127
|
+
- Must be used with `@Authenticated(authProvider, { projectId })`
|
|
128
|
+
- Throws clear error if `projectId` is not configured
|
|
129
|
+
|
|
130
|
+
## Error Messages
|
|
131
|
+
|
|
132
|
+
### Missing projectId Configuration
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
Environment injection not configured for SlackService.sendMessage().
|
|
136
|
+
To use @RequireEnv, you must configure 'projectId' in your @Authenticated decorator:
|
|
137
|
+
@Authenticated(authProvider, { projectId: 'your-project-id' })
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Missing Required Variables
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
Missing required environment variables: SLACK_TOKEN, SLACK_CHANNEL.
|
|
144
|
+
Please configure these secrets in your LeanMCP dashboard for this project.
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Called Outside Context
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
getEnv("SLACK_TOKEN") called outside of env context.
|
|
151
|
+
To use getEnv(), you must configure 'projectId' in your @Authenticated decorator:
|
|
152
|
+
@Authenticated(authProvider, { projectId: 'your-project-id' })
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## How It Works
|
|
156
|
+
|
|
157
|
+
1. User makes request to your MCP server with auth token
|
|
158
|
+
2. `@Authenticated` verifies token and fetches user's secrets from LeanMCP API
|
|
159
|
+
3. Secrets are stored in `AsyncLocalStorage` for this request only
|
|
160
|
+
4. `@RequireEnv` validates required secrets exist
|
|
161
|
+
5. `getEnv()` / `getAllEnv()` access secrets during method execution
|
|
162
|
+
6. Context is automatically cleaned up after request completes
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
Request → @Authenticated(projectId) → Fetch Secrets → runWithEnv() → @RequireEnv → Method → Cleanup
|
|
166
|
+
↓ ↓ ↓
|
|
167
|
+
Verify token Store in ALS getEnv() works
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Environment Variables
|
|
171
|
+
|
|
172
|
+
| Variable | Description |
|
|
173
|
+
|----------|-------------|
|
|
174
|
+
| `LEANMCP_API_KEY` | Your LeanMCP API key (with SDK scope) |
|
|
175
|
+
| `LEANMCP_PROJECT_ID` | Project ID to scope secrets to |
|
|
176
|
+
| `LEANMCP_ORCHESTRATION_API_URL` | API URL (default: https://api.leanmcp.com) |
|
|
177
|
+
| `LEANMCP_AUTH_URL` | Auth URL (default: https://auth.leanmcp.com) |
|
|
178
|
+
|
|
179
|
+
## Best Practices
|
|
180
|
+
|
|
181
|
+
1. **Always use @Authenticated with projectId** - Required for env injection to work
|
|
182
|
+
2. **Use @RequireEnv for validation** - Fails fast with clear error messages
|
|
183
|
+
3. **Don't cache secrets** - They're request-scoped for security
|
|
184
|
+
4. **Configure secrets in dashboard** - Users manage their own secrets
|
|
185
|
+
5. **Use non-null assertion** - After @RequireEnv, secrets are guaranteed to exist
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
@RequireEnv(["API_KEY"])
|
|
189
|
+
async method() {
|
|
190
|
+
const key = getEnv("API_KEY")!; // Safe to use ! here
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## License
|
|
195
|
+
|
|
196
|
+
MIT
|
|
197
|
+
|
|
198
|
+
## Related Packages
|
|
199
|
+
|
|
200
|
+
- [@leanmcp/auth](../auth) - Authentication decorators
|
|
201
|
+
- [@leanmcp/core](../core) - Core MCP server functionality
|
|
202
|
+
|
|
203
|
+
## Links
|
|
204
|
+
|
|
205
|
+
- [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
|
|
206
|
+
- [Documentation](https://github.com/LeanMCP/leanmcp-sdk#readme)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a function with the given environment variables in scope
|
|
3
|
+
* @param env - Environment variables to make available via getEnv()
|
|
4
|
+
* @param fn - Function to execute with the env context
|
|
5
|
+
*/
|
|
6
|
+
declare function runWithEnv<T>(env: Record<string, string>, fn: () => T | Promise<T>): T | Promise<T>;
|
|
7
|
+
/**
|
|
8
|
+
* Check if we're currently inside an env context
|
|
9
|
+
*/
|
|
10
|
+
declare function hasEnvContext(): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Get an environment variable from the current request context
|
|
13
|
+
*
|
|
14
|
+
* IMPORTANT: This function requires @Authenticated(authProvider, { projectId: '...' })
|
|
15
|
+
* to be configured. Throws an error if called outside of an env context.
|
|
16
|
+
*
|
|
17
|
+
* @param key - Environment variable key
|
|
18
|
+
* @returns The value or undefined if the key doesn't exist
|
|
19
|
+
* @throws Error if called outside of env context (projectId not configured)
|
|
20
|
+
*/
|
|
21
|
+
declare function getEnv(key: string): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Get all environment variables from the current request context
|
|
24
|
+
*
|
|
25
|
+
* IMPORTANT: This function requires @Authenticated(authProvider, { projectId: '...' })
|
|
26
|
+
* to be configured. Throws an error if called outside of an env context.
|
|
27
|
+
*
|
|
28
|
+
* @returns A copy of all environment variables
|
|
29
|
+
* @throws Error if called outside of env context (projectId not configured)
|
|
30
|
+
*/
|
|
31
|
+
declare function getAllEnv(): Record<string, string>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Decorator to validate required environment variables exist before method execution
|
|
35
|
+
*
|
|
36
|
+
* IMPORTANT: This decorator REQUIRES @Authenticated(authProvider, { projectId: '...' })
|
|
37
|
+
* to be applied either on the method or on the enclosing class. This is validated at
|
|
38
|
+
* runtime when the method is called.
|
|
39
|
+
*
|
|
40
|
+
* @param keys - List of required environment variable keys
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* @Authenticated(authProvider, { projectId: 'my-project' })
|
|
45
|
+
* class SlackService {
|
|
46
|
+
* @Tool("Send Slack message")
|
|
47
|
+
* @RequireEnv(["SLACK_TOKEN", "SLACK_CHANNEL"])
|
|
48
|
+
* async sendMessage({ message }: { message: string }) {
|
|
49
|
+
* const token = getEnv("SLACK_TOKEN"); // User's token, not global
|
|
50
|
+
* const channel = getEnv("SLACK_CHANNEL");
|
|
51
|
+
* // ... send message
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function RequireEnv(keys: string[]): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
57
|
+
/**
|
|
58
|
+
* Get the required env keys for a method
|
|
59
|
+
*/
|
|
60
|
+
declare function getRequiredEnvKeys(target: any, propertyKey: string): string[] | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Check if a method has @RequireEnv decorator
|
|
63
|
+
*/
|
|
64
|
+
declare function hasRequireEnv(target: any, propertyKey: string): boolean;
|
|
65
|
+
|
|
66
|
+
export { RequireEnv, getAllEnv, getEnv, getRequiredEnvKeys, hasEnvContext, hasRequireEnv, runWithEnv };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a function with the given environment variables in scope
|
|
3
|
+
* @param env - Environment variables to make available via getEnv()
|
|
4
|
+
* @param fn - Function to execute with the env context
|
|
5
|
+
*/
|
|
6
|
+
declare function runWithEnv<T>(env: Record<string, string>, fn: () => T | Promise<T>): T | Promise<T>;
|
|
7
|
+
/**
|
|
8
|
+
* Check if we're currently inside an env context
|
|
9
|
+
*/
|
|
10
|
+
declare function hasEnvContext(): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Get an environment variable from the current request context
|
|
13
|
+
*
|
|
14
|
+
* IMPORTANT: This function requires @Authenticated(authProvider, { projectId: '...' })
|
|
15
|
+
* to be configured. Throws an error if called outside of an env context.
|
|
16
|
+
*
|
|
17
|
+
* @param key - Environment variable key
|
|
18
|
+
* @returns The value or undefined if the key doesn't exist
|
|
19
|
+
* @throws Error if called outside of env context (projectId not configured)
|
|
20
|
+
*/
|
|
21
|
+
declare function getEnv(key: string): string | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Get all environment variables from the current request context
|
|
24
|
+
*
|
|
25
|
+
* IMPORTANT: This function requires @Authenticated(authProvider, { projectId: '...' })
|
|
26
|
+
* to be configured. Throws an error if called outside of an env context.
|
|
27
|
+
*
|
|
28
|
+
* @returns A copy of all environment variables
|
|
29
|
+
* @throws Error if called outside of env context (projectId not configured)
|
|
30
|
+
*/
|
|
31
|
+
declare function getAllEnv(): Record<string, string>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Decorator to validate required environment variables exist before method execution
|
|
35
|
+
*
|
|
36
|
+
* IMPORTANT: This decorator REQUIRES @Authenticated(authProvider, { projectId: '...' })
|
|
37
|
+
* to be applied either on the method or on the enclosing class. This is validated at
|
|
38
|
+
* runtime when the method is called.
|
|
39
|
+
*
|
|
40
|
+
* @param keys - List of required environment variable keys
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* @Authenticated(authProvider, { projectId: 'my-project' })
|
|
45
|
+
* class SlackService {
|
|
46
|
+
* @Tool("Send Slack message")
|
|
47
|
+
* @RequireEnv(["SLACK_TOKEN", "SLACK_CHANNEL"])
|
|
48
|
+
* async sendMessage({ message }: { message: string }) {
|
|
49
|
+
* const token = getEnv("SLACK_TOKEN"); // User's token, not global
|
|
50
|
+
* const channel = getEnv("SLACK_CHANNEL");
|
|
51
|
+
* // ... send message
|
|
52
|
+
* }
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
declare function RequireEnv(keys: string[]): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
57
|
+
/**
|
|
58
|
+
* Get the required env keys for a method
|
|
59
|
+
*/
|
|
60
|
+
declare function getRequiredEnvKeys(target: any, propertyKey: string): string[] | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Check if a method has @RequireEnv decorator
|
|
63
|
+
*/
|
|
64
|
+
declare function hasRequireEnv(target: any, propertyKey: string): boolean;
|
|
65
|
+
|
|
66
|
+
export { RequireEnv, getAllEnv, getEnv, getRequiredEnvKeys, hasEnvContext, hasRequireEnv, runWithEnv };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
RequireEnv: () => RequireEnv,
|
|
25
|
+
getAllEnv: () => getAllEnv,
|
|
26
|
+
getEnv: () => getEnv,
|
|
27
|
+
getRequiredEnvKeys: () => getRequiredEnvKeys,
|
|
28
|
+
hasEnvContext: () => hasEnvContext,
|
|
29
|
+
hasRequireEnv: () => hasRequireEnv,
|
|
30
|
+
runWithEnv: () => runWithEnv
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(index_exports);
|
|
33
|
+
|
|
34
|
+
// src/env-context.ts
|
|
35
|
+
var import_reflect_metadata = require("reflect-metadata");
|
|
36
|
+
var import_async_hooks = require("async_hooks");
|
|
37
|
+
var envStorage = new import_async_hooks.AsyncLocalStorage();
|
|
38
|
+
function runWithEnv(env, fn) {
|
|
39
|
+
return envStorage.run(env, fn);
|
|
40
|
+
}
|
|
41
|
+
__name(runWithEnv, "runWithEnv");
|
|
42
|
+
function hasEnvContext() {
|
|
43
|
+
return envStorage.getStore() !== void 0;
|
|
44
|
+
}
|
|
45
|
+
__name(hasEnvContext, "hasEnvContext");
|
|
46
|
+
function getEnv(key) {
|
|
47
|
+
const store = envStorage.getStore();
|
|
48
|
+
if (store === void 0) {
|
|
49
|
+
throw new Error(`getEnv("${key}") called outside of env context. To use getEnv(), you must configure 'projectId' in your @Authenticated decorator: @Authenticated(authProvider, { projectId: 'your-project-id' })`);
|
|
50
|
+
}
|
|
51
|
+
return store[key];
|
|
52
|
+
}
|
|
53
|
+
__name(getEnv, "getEnv");
|
|
54
|
+
function getAllEnv() {
|
|
55
|
+
const store = envStorage.getStore();
|
|
56
|
+
if (store === void 0) {
|
|
57
|
+
throw new Error(`getAllEnv() called outside of env context. To use getAllEnv(), you must configure 'projectId' in your @Authenticated decorator: @Authenticated(authProvider, { projectId: 'your-project-id' })`);
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
...store
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
__name(getAllEnv, "getAllEnv");
|
|
64
|
+
|
|
65
|
+
// src/decorators.ts
|
|
66
|
+
var import_reflect_metadata2 = require("reflect-metadata");
|
|
67
|
+
var REQUIRE_ENV_KEY = /* @__PURE__ */ Symbol("requireEnv");
|
|
68
|
+
function RequireEnv(keys) {
|
|
69
|
+
return function(target, propertyKey, descriptor) {
|
|
70
|
+
Reflect.defineMetadata(REQUIRE_ENV_KEY, keys, target, propertyKey);
|
|
71
|
+
const originalMethod = descriptor.value;
|
|
72
|
+
descriptor.value = async function(...args) {
|
|
73
|
+
if (!hasEnvContext()) {
|
|
74
|
+
const className = target.constructor?.name || target.name || "Unknown";
|
|
75
|
+
throw new Error(`Environment injection not configured for ${className}.${propertyKey}(). To use @RequireEnv, you must configure 'projectId' in your @Authenticated decorator: @Authenticated(authProvider, { projectId: 'your-project-id' })`);
|
|
76
|
+
}
|
|
77
|
+
const missing = keys.filter((key) => !getEnv(key));
|
|
78
|
+
if (missing.length > 0) {
|
|
79
|
+
throw new Error(`Missing required environment variables: ${missing.join(", ")}. Please configure these secrets in your LeanMCP dashboard for this project.`);
|
|
80
|
+
}
|
|
81
|
+
return originalMethod.apply(this, args);
|
|
82
|
+
};
|
|
83
|
+
copyMethodMetadata(originalMethod, descriptor.value);
|
|
84
|
+
return descriptor;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
__name(RequireEnv, "RequireEnv");
|
|
88
|
+
function getRequiredEnvKeys(target, propertyKey) {
|
|
89
|
+
return Reflect.getMetadata(REQUIRE_ENV_KEY, target, propertyKey);
|
|
90
|
+
}
|
|
91
|
+
__name(getRequiredEnvKeys, "getRequiredEnvKeys");
|
|
92
|
+
function hasRequireEnv(target, propertyKey) {
|
|
93
|
+
return Reflect.hasMetadata(REQUIRE_ENV_KEY, target, propertyKey);
|
|
94
|
+
}
|
|
95
|
+
__name(hasRequireEnv, "hasRequireEnv");
|
|
96
|
+
function copyMethodMetadata(source, target) {
|
|
97
|
+
const metadataKeys = Reflect.getMetadataKeys(source);
|
|
98
|
+
for (const key of metadataKeys) {
|
|
99
|
+
const value = Reflect.getMetadata(key, source);
|
|
100
|
+
Reflect.defineMetadata(key, value, target);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
__name(copyMethodMetadata, "copyMethodMetadata");
|
|
104
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
105
|
+
0 && (module.exports = {
|
|
106
|
+
RequireEnv,
|
|
107
|
+
getAllEnv,
|
|
108
|
+
getEnv,
|
|
109
|
+
getRequiredEnvKeys,
|
|
110
|
+
hasEnvContext,
|
|
111
|
+
hasRequireEnv,
|
|
112
|
+
runWithEnv
|
|
113
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
3
|
+
|
|
4
|
+
// src/env-context.ts
|
|
5
|
+
import "reflect-metadata";
|
|
6
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
7
|
+
var envStorage = new AsyncLocalStorage();
|
|
8
|
+
function runWithEnv(env, fn) {
|
|
9
|
+
return envStorage.run(env, fn);
|
|
10
|
+
}
|
|
11
|
+
__name(runWithEnv, "runWithEnv");
|
|
12
|
+
function hasEnvContext() {
|
|
13
|
+
return envStorage.getStore() !== void 0;
|
|
14
|
+
}
|
|
15
|
+
__name(hasEnvContext, "hasEnvContext");
|
|
16
|
+
function getEnv(key) {
|
|
17
|
+
const store = envStorage.getStore();
|
|
18
|
+
if (store === void 0) {
|
|
19
|
+
throw new Error(`getEnv("${key}") called outside of env context. To use getEnv(), you must configure 'projectId' in your @Authenticated decorator: @Authenticated(authProvider, { projectId: 'your-project-id' })`);
|
|
20
|
+
}
|
|
21
|
+
return store[key];
|
|
22
|
+
}
|
|
23
|
+
__name(getEnv, "getEnv");
|
|
24
|
+
function getAllEnv() {
|
|
25
|
+
const store = envStorage.getStore();
|
|
26
|
+
if (store === void 0) {
|
|
27
|
+
throw new Error(`getAllEnv() called outside of env context. To use getAllEnv(), you must configure 'projectId' in your @Authenticated decorator: @Authenticated(authProvider, { projectId: 'your-project-id' })`);
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
...store
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
__name(getAllEnv, "getAllEnv");
|
|
34
|
+
|
|
35
|
+
// src/decorators.ts
|
|
36
|
+
import "reflect-metadata";
|
|
37
|
+
var REQUIRE_ENV_KEY = /* @__PURE__ */ Symbol("requireEnv");
|
|
38
|
+
function RequireEnv(keys) {
|
|
39
|
+
return function(target, propertyKey, descriptor) {
|
|
40
|
+
Reflect.defineMetadata(REQUIRE_ENV_KEY, keys, target, propertyKey);
|
|
41
|
+
const originalMethod = descriptor.value;
|
|
42
|
+
descriptor.value = async function(...args) {
|
|
43
|
+
if (!hasEnvContext()) {
|
|
44
|
+
const className = target.constructor?.name || target.name || "Unknown";
|
|
45
|
+
throw new Error(`Environment injection not configured for ${className}.${propertyKey}(). To use @RequireEnv, you must configure 'projectId' in your @Authenticated decorator: @Authenticated(authProvider, { projectId: 'your-project-id' })`);
|
|
46
|
+
}
|
|
47
|
+
const missing = keys.filter((key) => !getEnv(key));
|
|
48
|
+
if (missing.length > 0) {
|
|
49
|
+
throw new Error(`Missing required environment variables: ${missing.join(", ")}. Please configure these secrets in your LeanMCP dashboard for this project.`);
|
|
50
|
+
}
|
|
51
|
+
return originalMethod.apply(this, args);
|
|
52
|
+
};
|
|
53
|
+
copyMethodMetadata(originalMethod, descriptor.value);
|
|
54
|
+
return descriptor;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
__name(RequireEnv, "RequireEnv");
|
|
58
|
+
function getRequiredEnvKeys(target, propertyKey) {
|
|
59
|
+
return Reflect.getMetadata(REQUIRE_ENV_KEY, target, propertyKey);
|
|
60
|
+
}
|
|
61
|
+
__name(getRequiredEnvKeys, "getRequiredEnvKeys");
|
|
62
|
+
function hasRequireEnv(target, propertyKey) {
|
|
63
|
+
return Reflect.hasMetadata(REQUIRE_ENV_KEY, target, propertyKey);
|
|
64
|
+
}
|
|
65
|
+
__name(hasRequireEnv, "hasRequireEnv");
|
|
66
|
+
function copyMethodMetadata(source, target) {
|
|
67
|
+
const metadataKeys = Reflect.getMetadataKeys(source);
|
|
68
|
+
for (const key of metadataKeys) {
|
|
69
|
+
const value = Reflect.getMetadata(key, source);
|
|
70
|
+
Reflect.defineMetadata(key, value, target);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
__name(copyMethodMetadata, "copyMethodMetadata");
|
|
74
|
+
export {
|
|
75
|
+
RequireEnv,
|
|
76
|
+
getAllEnv,
|
|
77
|
+
getEnv,
|
|
78
|
+
getRequiredEnvKeys,
|
|
79
|
+
hasEnvContext,
|
|
80
|
+
hasRequireEnv,
|
|
81
|
+
runWithEnv
|
|
82
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@leanmcp/env-injection",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Request-scoped environment variable injection for LeanMCP",
|
|
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
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format esm,cjs --dts",
|
|
21
|
+
"dev": "tsup src/index.ts --format esm,cjs --dts --watch",
|
|
22
|
+
"test": "jest --passWithNoTests"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"reflect-metadata": "^0.2.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^20.0.0",
|
|
29
|
+
"tsup": "^8.0.0",
|
|
30
|
+
"typescript": "^5.0.0"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/LeanMCP/leanmcp-sdk.git",
|
|
35
|
+
"directory": "packages/env-injection"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"mcp",
|
|
39
|
+
"model-context-protocol",
|
|
40
|
+
"environment",
|
|
41
|
+
"injection",
|
|
42
|
+
"secrets"
|
|
43
|
+
],
|
|
44
|
+
"author": "LeanMCP <admin@leanmcp.com>",
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
}
|
|
49
|
+
}
|