lemonsqueezy-mcp-server 1.0.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.
Files changed (97) hide show
  1. package/ENV_EXAMPLE.md +83 -0
  2. package/LICENSE +21 -0
  3. package/README.md +141 -0
  4. package/SETUP.md +244 -0
  5. package/dist/config.d.ts +25 -0
  6. package/dist/config.d.ts.map +1 -0
  7. package/dist/config.js +80 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/config.test.d.ts +2 -0
  10. package/dist/config.test.d.ts.map +1 -0
  11. package/dist/config.test.js +55 -0
  12. package/dist/config.test.js.map +1 -0
  13. package/dist/connections/firebase.d.ts +3 -0
  14. package/dist/connections/firebase.d.ts.map +1 -0
  15. package/dist/connections/firebase.js +62 -0
  16. package/dist/connections/firebase.js.map +1 -0
  17. package/dist/connections/salesforce.d.ts +3 -0
  18. package/dist/connections/salesforce.d.ts.map +1 -0
  19. package/dist/connections/salesforce.js +307 -0
  20. package/dist/connections/salesforce.js.map +1 -0
  21. package/dist/index.d.ts +3 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +101 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/resources/payment-context.d.ts +14 -0
  26. package/dist/resources/payment-context.d.ts.map +1 -0
  27. package/dist/resources/payment-context.js +111 -0
  28. package/dist/resources/payment-context.js.map +1 -0
  29. package/dist/server.d.ts +36 -0
  30. package/dist/server.d.ts.map +1 -0
  31. package/dist/server.js +14 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/sync-last-purchase.d.ts +3 -0
  34. package/dist/sync-last-purchase.d.ts.map +1 -0
  35. package/dist/sync-last-purchase.js +113 -0
  36. package/dist/sync-last-purchase.js.map +1 -0
  37. package/dist/tools/definitions.d.ts +3768 -0
  38. package/dist/tools/definitions.d.ts.map +1 -0
  39. package/dist/tools/definitions.js +753 -0
  40. package/dist/tools/definitions.js.map +1 -0
  41. package/dist/tools/handlers/analytics.d.ts +21 -0
  42. package/dist/tools/handlers/analytics.d.ts.map +1 -0
  43. package/dist/tools/handlers/analytics.js +398 -0
  44. package/dist/tools/handlers/analytics.js.map +1 -0
  45. package/dist/tools/handlers/lemonsqueezy.d.ts +436 -0
  46. package/dist/tools/handlers/lemonsqueezy.d.ts.map +1 -0
  47. package/dist/tools/handlers/lemonsqueezy.js +481 -0
  48. package/dist/tools/handlers/lemonsqueezy.js.map +1 -0
  49. package/dist/tools/handlers/salesforce.d.ts +13 -0
  50. package/dist/tools/handlers/salesforce.d.ts.map +1 -0
  51. package/dist/tools/handlers/salesforce.js +54 -0
  52. package/dist/tools/handlers/salesforce.js.map +1 -0
  53. package/dist/tools/handlers/vos.d.ts +26 -0
  54. package/dist/tools/handlers/vos.d.ts.map +1 -0
  55. package/dist/tools/handlers/vos.js +207 -0
  56. package/dist/tools/handlers/vos.js.map +1 -0
  57. package/dist/tools/index.d.ts +7 -0
  58. package/dist/tools/index.d.ts.map +1 -0
  59. package/dist/tools/index.js +202 -0
  60. package/dist/tools/index.js.map +1 -0
  61. package/dist/types.d.ts +24 -0
  62. package/dist/types.d.ts.map +1 -0
  63. package/dist/types.js +2 -0
  64. package/dist/types.js.map +1 -0
  65. package/dist/utils/logger.d.ts +3 -0
  66. package/dist/utils/logger.d.ts.map +1 -0
  67. package/dist/utils/logger.js +8 -0
  68. package/dist/utils/logger.js.map +1 -0
  69. package/dist/utils/response.d.ts +14 -0
  70. package/dist/utils/response.d.ts.map +1 -0
  71. package/dist/utils/response.js +17 -0
  72. package/dist/utils/response.js.map +1 -0
  73. package/dist/utils/retry.d.ts +11 -0
  74. package/dist/utils/retry.d.ts.map +1 -0
  75. package/dist/utils/retry.js +36 -0
  76. package/dist/utils/retry.js.map +1 -0
  77. package/dist/utils/secrets/provider.d.ts +30 -0
  78. package/dist/utils/secrets/provider.d.ts.map +1 -0
  79. package/dist/utils/secrets/provider.js +51 -0
  80. package/dist/utils/secrets/provider.js.map +1 -0
  81. package/dist/utils/secrets.d.ts +7 -0
  82. package/dist/utils/secrets.d.ts.map +1 -0
  83. package/dist/utils/secrets.js +16 -0
  84. package/dist/utils/secrets.js.map +1 -0
  85. package/dist/utils/validation.d.ts +580 -0
  86. package/dist/utils/validation.d.ts.map +1 -0
  87. package/dist/utils/validation.js +250 -0
  88. package/dist/utils/validation.js.map +1 -0
  89. package/dist/webhooks/listener.d.ts +17 -0
  90. package/dist/webhooks/listener.d.ts.map +1 -0
  91. package/dist/webhooks/listener.js +140 -0
  92. package/dist/webhooks/listener.js.map +1 -0
  93. package/dist/webhooks/ngrok.d.ts +8 -0
  94. package/dist/webhooks/ngrok.d.ts.map +1 -0
  95. package/dist/webhooks/ngrok.js +50 -0
  96. package/dist/webhooks/ngrok.js.map +1 -0
  97. package/package.json +68 -0
package/ENV_EXAMPLE.md ADDED
@@ -0,0 +1,83 @@
1
+ # Environment Variables Example
2
+
3
+ Create a `.env` file in the project root with the following variables:
4
+
5
+ ```bash
6
+ # Lemon Squeezy API Keys
7
+ # Get your API keys from: https://app.lemonsqueezy.com/settings/api
8
+ #
9
+ # For testing (recommended to start):
10
+ LEMONSQUEEZY_TEST_API_KEY=your_test_api_key_here
11
+ #
12
+ # For production (use when ready):
13
+ # LEMONSQUEEZY_API_KEY=your_production_api_key_here
14
+ #
15
+ # Note: If both are set, production key takes priority
16
+
17
+ # Salesforce Integration (Optional - Bonus Feature)
18
+ # Get your credentials from: https://help.salesforce.com/s/articleView?id=sf.user_security_token.htm
19
+ #
20
+ # Option 1: AWS Secrets Manager (Recommended for Production)
21
+ # Store your Salesforce credentials in AWS Secrets Manager as a JSON secret:
22
+ # {
23
+ # "username": "your_salesforce_username@example.com",
24
+ # "password": "your_salesforce_password",
25
+ # "securityToken": "your_security_token"
26
+ # }
27
+ # Or for JWT authentication:
28
+ # {
29
+ # "client_id": "your_connected_app_client_id",
30
+ # "username": "your_username@example.com",
31
+ # "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"
32
+ # }
33
+ # Or as plain text key=value pairs:
34
+ # username=your_salesforce_username@example.com
35
+ # password=your_salesforce_password
36
+ # securityToken=your_security_token
37
+ #
38
+ # Example configuration:
39
+ # AWS_SALESFORCE_SECRET_NAME=your-project/salesforce-credentials
40
+ # AWS_REGION=us-west-2 # Your AWS region (us-east-1, us-west-2, eu-west-1, etc.)
41
+ #
42
+ # SECURITY BEST PRACTICE: Do NOT set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY here.
43
+ # Instead, use AWS CLI to configure credentials: `aws configure`
44
+ # This stores credentials securely in ~/.aws/credentials (encrypted by OS).
45
+ # See README.md for full instructions.
46
+ #
47
+ # Note: Use your own secret name and region. The secret name should match
48
+ # exactly what you created in AWS Secrets Manager.
49
+ #
50
+ # Option 2: Environment Variables (For Local Development)
51
+ # SALESFORCE_USERNAME=your_salesforce_username@example.com
52
+ # SALESFORCE_PASSWORD=your_salesforce_password
53
+ # SALESFORCE_TOKEN=your_security_token
54
+ #
55
+ # Optional: Custom login URL (defaults to https://login.salesforce.com)
56
+ # SALESFORCE_LOGIN_URL=https://login.salesforce.com
57
+ #
58
+ # Note: Salesforce integration is optional. The server works perfectly without it.
59
+ # If AWS_SALESFORCE_SECRET_NAME is set, it will use AWS Secrets Manager.
60
+ # Otherwise, it falls back to environment variables.
61
+
62
+ # Proactive Context (Optional - Advanced Feature)
63
+ # Enable Resources capability for proactive AI context
64
+ #
65
+ # ENABLE_RESOURCES=true
66
+ # POLL_FAILED_PAYMENTS=true
67
+ # POLL_INTERVAL_MINUTES=5
68
+ # WEBHOOK_LOG_PATH=/path/to/webhook.log
69
+
70
+ # Webhook Listener (Sprint 2 - Event Engine)
71
+ # Real-time webhook processing replaces file watching
72
+ #
73
+ # WEBHOOK_PORT=3000
74
+ # LEMONSQUEEZY_WEBHOOK_SECRET=your_webhook_signing_secret_here
75
+ # ENABLE_NGROK=true
76
+ #
77
+ # Note: If ENABLE_NGROK=true, ngrok will create a public tunnel.
78
+ # The public URL will be logged on startup - use it to configure your Lemon Squeezy webhook.
79
+ ```
80
+
81
+ **Note:** Copy this content to a `.env` file (not `.env.example`) for local development. The `.env` file is already in `.gitignore` and won't be committed.
82
+
83
+
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
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,141 @@
1
+ # Lemon Squeezy MCP Server
2
+
3
+ ![Demo GIF showing the MCP server in action](./demo.gif)
4
+
5
+ ## 📖 About
6
+ **Give your AI assistant direct access to your payment and subscription data.**
7
+
8
+ This server acts as a bridge between your AI assistant (like VS Code, Claude Desktop, or any MCP-compatible client) and your Lemon Squeezy account. Instead of manually logging into dashboards, searching for orders, and copy-pasting details, you can simply ask your AI questions like:
9
+ - *"What was the last sale?"*
10
+ - *"Is subscription #12345 still active?"*
11
+ - *"Find all customers named 'Alice'"*
12
+
13
+ Your AI will query the data securely and give you an instant answer, right in your code editor.
14
+
15
+ ---
16
+
17
+ ## 🌱 Beginner Level: Getting Started
18
+
19
+ **Who is this for?** You are new to CLI tools or just want to get this running quickly with minimal fuss.
20
+
21
+ ### 1. Prerequisites
22
+ Before you start, make sure you have these two things installed on your computer:
23
+ * **Node.js (Version 18 or higher):** This is the software that runs the server. [Download Node.js here](https://nodejs.org/).
24
+ * **A Lemon Squeezy Account:** You need an account to get the data. [Sign up here](https://lemonsqueezy.com).
25
+
26
+ ### 2. Get Your API Key
27
+ Think of this as your password for the server.
28
+ 1. Log in to your [Lemon Squeezy Dashboard](https://app.lemonsqueezy.com/settings/api).
29
+ 2. Go to **Settings** -> **API**.
30
+ 3. Click **Create API Key**.
31
+ 4. Copy the key (it starts with `ls_...`). **Keep this safe!**
32
+
33
+ ### 3. Quick Installation
34
+ Open your terminal (Command Prompt on Windows, Terminal on Mac) and run these commands one by one:
35
+
36
+ ```bash
37
+ # 1. Download the project
38
+ git clone https://github.com/MichaelWeed/lemonsqueezy-mcp-server.git
39
+ cd lemonsqueezy-mcp-server
40
+
41
+ # 2. Install the necessary files
42
+ npm install
43
+
44
+ # 3. Build the server
45
+ npm run build
46
+ ```
47
+
48
+ ### 4. Connect to Your AI Editor
49
+ The exact steps depend on which AI editor you're using. Here are the most common:
50
+
51
+ **One-copy-paste setup (Claude Desktop / Cursor / npx):**
52
+ Add this to your MCP config file (e.g. `claude_desktop_config.json` or Cursor MCP settings). Replace `YOUR_KEY_HERE` with your Lemon Squeezy API key.
53
+
54
+ ```json
55
+ "mcpServers": {
56
+ "lemonsqueezy": {
57
+ "command": "npx",
58
+ "args": ["-y", "lemonsqueezy-mcp-server"],
59
+ "env": {
60
+ "LEMONSQUEEZY_API_KEY": "YOUR_KEY_HERE"
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ *(Requires the package to be [published on npm](https://www.npmjs.com/package/lemonsqueezy-mcp-server). For local development, use the path-based config below.)*
67
+
68
+ **For VS Code (local path):**
69
+ 1. Open VS Code Settings
70
+ 2. Go to Extensions → MCP
71
+ 3. Add a new MCP server with:
72
+ * **Command:** `node`
73
+ * **Args:** `["/absolute/path/to/lemonsqueezy-mcp-server/dist/index.js"]`
74
+ * **Env:** `{"LEMONSQUEEZY_API_KEY": "your_api_key_here"}`
75
+
76
+ **For Claude Desktop (local path):**
77
+ 1. Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
78
+ 2. Add the same configuration as above (command `node`, args with full path to `dist/index.js`)
79
+ 3. Restart Claude Desktop
80
+
81
+ **For Other MCP Clients:**
82
+ Refer to your client's documentation for adding MCP servers. The server runs via `node /path/to/dist/index.js` with the `LEMONSQUEEZY_API_KEY` environment variable set.
83
+
84
+ After configuring, restart your AI editor and try asking: *"Show me my last 5 orders."*
85
+
86
+ ---
87
+
88
+ ## 🚀 Medium User Level: Basic Usage & Configuration
89
+
90
+ **Who is this for?** You are comfortable with config files, environment variables, and want to customize how the server works.
91
+
92
+ ### Philosophy: "Sensible Defaults"
93
+ This server is designed to work out of the box with just **one** required setting: your `LEMONSQUEEZY_API_KEY`. Everything else is optional and has pre-configured defaults that work for 90% of users.
94
+
95
+ ### Configuration Options
96
+ You can configure the server using Environment Variables in your MCP settings or a `.env` file.
97
+
98
+ | Variable | Description | Default |
99
+ |----------|-------------|---------|
100
+ | `LEMONSQUEEZY_API_KEY` | **Required.** Your live API key. | - |
101
+ | `LEMONSQUEEZY_TEST_API_KEY` | Optional. Use for testing without affecting real data. | - |
102
+ | `ENABLE_RESOURCES` | Set to `true` to let the AI see "active context" like failed payments automatically. | `false` |
103
+ | `POLL_FAILED_PAYMENTS` | Set to `true` to check for failed payments every few minutes. | `false` |
104
+
105
+ ### Common Tasks
106
+
107
+ **1. Using Test Mode**
108
+ If you want to develop without touching real money, generate a "Test API Key" in Lemon Squeezy and use `LEMONSQUEEZY_TEST_API_KEY`. The server will automatically prioritize the live key if both are present, so remove the live key to force test mode.
109
+
110
+ **2. Enabling Salesforce Integration (Bonus)**
111
+ Want to sync customers to your CRM?
112
+ Add these variables:
113
+ - `SALESFORCE_USERNAME`
114
+ - `SALESFORCE_PASSWORD`
115
+ - `SALESFORCE_TOKEN` (Security Token)
116
+
117
+ The tools for Salesforce (like `sync_customer_to_crm`) will automatically appear in your AI's toolkit.
118
+
119
+ ### Troubleshooting
120
+ * **"Command not found":** Ensure you ran `npm run build` after installing.
121
+ * **"Authentication Error":** Double-check your API key. Did you copy an extra space?
122
+ * **Logs:** The server outputs logs to the "MCP Log" window in your editor. Check there for specific error messages.
123
+
124
+ ---
125
+
126
+ ## 🛠️ Advanced User Level: Technical Documentation
127
+
128
+ **Who is this for?** Developers, Architects, and DevOps engineers looking for deep technical details, architecture diagrams, security compliance, and deployment strategies.
129
+
130
+ **Containers:** This repo has both a **Containerfile** and a **Dockerfile** with the same build. The Dockerfile exists so registries (e.g. Smithery) and CI that look for the filename `Dockerfile` can discover and build the image. You can build with Podman or Docker; see **[CONTAINERS.md](./CONTAINERS.md)** for why both exist and how to build.
131
+
132
+ For a comprehensive breakdown of the system architecture, code modules, security protocols, and enterprise deployment guides, please refer to the:
133
+
134
+ 👉 **[Technical Solution Design Document (TSD)](./TECHNICAL_SOLUTION_DESIGN.md)**
135
+
136
+ The TSD covers:
137
+ * **System Architecture & Diagrams**
138
+ * **Module Interflows & Data Paths**
139
+ * **Security & Compliance (AWS Secrets Manager, etc.)**
140
+ * **Deployment (Docker/OCI Containers)**
141
+ * **Risk Mitigation Strategies**
package/SETUP.md ADDED
@@ -0,0 +1,244 @@
1
+ # Quick Setup Checklist
2
+
3
+ Follow these steps to get your Lemon Squeezy MCP Server running:
4
+
5
+ ## ✅ Pre-Flight Checklist
6
+
7
+ - [ ] Node.js 18+ installed (`node --version`)
8
+ - [ ] Lemon Squeezy account created
9
+ - [ ] API key obtained from Lemon Squeezy dashboard
10
+
11
+ ## 📦 Installation Steps
12
+
13
+ 1. **Install dependencies:**
14
+ ```bash
15
+ npm install
16
+ ```
17
+
18
+ 2. **Build the project:**
19
+ ```bash
20
+ npm run build
21
+ ```
22
+
23
+ 3. **Verify build succeeded:**
24
+ ```bash
25
+ ls dist/index.js
26
+ ```
27
+ (Should show the file exists)
28
+
29
+ ## 🔑 Configuration Steps
30
+
31
+ 1. **Get your API key:**
32
+ - Go to https://app.lemonsqueezy.com/settings/api
33
+ - Create a new API key (start with Test key)
34
+ - Copy it
35
+
36
+ 2. **Configure your AI client:**
37
+ - **VS Code:** Settings → Extensions → MCP
38
+ - **Claude Desktop:** Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
39
+ - **Other MCP clients:** Refer to your client's documentation for MCP server configuration
40
+
41
+ 3. **Add this configuration:**
42
+
43
+ **Option A — One-copy-paste (npx, after npm publish):**
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "lemonsqueezy": {
48
+ "command": "npx",
49
+ "args": ["-y", "lemonsqueezy-mcp-server"],
50
+ "env": {
51
+ "LEMONSQUEEZY_API_KEY": "paste_your_key_here"
52
+ }
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ **Option B — Local path (development):**
59
+ ```json
60
+ {
61
+ "mcpServers": {
62
+ "lemonsqueezy": {
63
+ "command": "node",
64
+ "args": ["/FULL/PATH/TO/THIS/PROJECT/dist/index.js"],
65
+ "env": {
66
+ "LEMONSQUEEZY_TEST_API_KEY": "paste_your_key_here"
67
+ }
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ 4. **Important:**
74
+ - Replace `/FULL/PATH/TO/THIS/PROJECT` with your actual path
75
+ - Use absolute path (full path from root), not relative
76
+ - Save the file
77
+ - Restart your AI client completely
78
+
79
+ ## 🧪 Testing
80
+
81
+ After restarting, ask your AI:
82
+
83
+ > "What was the last successful payment from Lemon Squeezy?"
84
+
85
+ If it works, you're done! 🎉
86
+
87
+ ## 🆘 Common Issues
88
+
89
+ **"Tools not showing up"**
90
+ - Did you save the config file?
91
+ - Did you restart your AI client?
92
+ - Check the path is absolute and correct
93
+
94
+ **"Server not found"**
95
+ - Run `npm run build` again
96
+ - Verify `dist/index.js` exists
97
+ - Check the path in your config
98
+
99
+ **"API errors"**
100
+ - Verify your API key is correct
101
+ - Make sure you copied the full key
102
+ - Check key hasn't expired in Lemon Squeezy dashboard
103
+
104
+ ## 📍 Finding Your Path
105
+
106
+ **Mac/Linux:**
107
+ ```bash
108
+ pwd
109
+ # Copy the output and use it in your config
110
+ ```
111
+
112
+ **Windows:**
113
+ ```cmd
114
+ cd
115
+ # Copy the output and use it in your config (use forward slashes or double backslashes)
116
+ ```
117
+
118
+ ## 🎯 Next Steps
119
+
120
+ Once it's working:
121
+ - Try different queries (see README examples)
122
+ - Switch to production API key when ready
123
+ - Explore all 45+ available tools
124
+
125
+ ---
126
+
127
+ ## 🔐 Adding Salesforce Integration with AWS Secrets Manager (Optional)
128
+
129
+ If you want to add Salesforce CRM integration later using AWS Secrets Manager:
130
+
131
+ ### Step 1: Create Secret in AWS Secrets Manager
132
+
133
+ 1. Go to [AWS Secrets Manager Console](https://console.aws.amazon.com/secretsmanager/)
134
+ 2. Click "Store a new secret"
135
+ 3. Choose "Other type of secret"
136
+ 4. Select "Plaintext" or "JSON"
137
+ 5. **For JSON format**, enter:
138
+ ```json
139
+ {
140
+ "username": "your_salesforce_username@example.com",
141
+ "password": "your_salesforce_password",
142
+ "securityToken": "your_security_token"
143
+ }
144
+ ```
145
+ 6. **For plain text**, enter:
146
+ ```
147
+ username=your_salesforce_username@example.com
148
+ password=your_salesforce_password
149
+ securityToken=your_security_token
150
+ ```
151
+ 7. Name your secret (e.g., `your-project/salesforce-credentials` or `prod/salesforce-auth`)
152
+ 8. Note your AWS region (e.g., `us-west-2`, `us-east-1`)
153
+
154
+ ### Step 2: Update Your MCP Configuration
155
+
156
+ Add these environment variables to your MCP config file (location depends on your AI client):
157
+
158
+ ```json
159
+ {
160
+ "mcpServers": {
161
+ "lemonsqueezy": {
162
+ "command": "node",
163
+ "args": ["/absolute/path/to/TypeScript-MCP-Server/dist/index.js"],
164
+ "env": {
165
+ "LEMONSQUEEZY_TEST_API_KEY": "your_test_api_key_here",
166
+ "AWS_SALESFORCE_SECRET_NAME": "your-project/salesforce-credentials",
167
+ "AWS_REGION": "us-west-2"
168
+ }
169
+ }
170
+ }
171
+ }
172
+ ```
173
+
174
+ **Do NOT include:**
175
+ - ❌ `AWS_ACCESS_KEY_ID`
176
+ - ❌ `AWS_SECRET_ACCESS_KEY`
177
+
178
+ **Important:**
179
+ - Replace `your-project/salesforce-credentials` with **your own secret name** from AWS Secrets Manager
180
+ - Replace `us-east-1` with **your AWS region** where the secret is stored
181
+ - Use your own naming convention (e.g., `your-project/salesforce-credentials`, `prod/salesforce-auth`)
182
+
183
+ ### Step 3: Configure AWS Credentials (IMPORTANT - Security Best Practice)
184
+
185
+ **DO NOT store AWS credentials directly in mcp.json.** Instead, use AWS CLI to configure credentials securely.
186
+
187
+ **Recommended Setup (AWS CLI Method):**
188
+
189
+ 1. **Install AWS CLI** (if not already installed):
190
+ ```bash
191
+ brew install awscli # macOS
192
+ # or download from https://aws.amazon.com/cli/
193
+ ```
194
+
195
+ 2. **Configure AWS credentials:**
196
+ ```bash
197
+ aws configure
198
+ ```
199
+
200
+ Enter when prompted:
201
+ - **AWS Access Key ID:** `AKIA...` (from AWS IAM Console)
202
+ - **AWS Secret Access Key:** `...` (from AWS IAM Console)
203
+ - **Default region:** `us-west-2` (or your region)
204
+ - **Output format:** `json` (or press Enter)
205
+
206
+ 3. **Validation:**
207
+
208
+ Test AWS credentials are working:
209
+ ```bash
210
+ aws secretsmanager get-secret-value --secret-id your-project/salesforce-credentials --region us-west-2
211
+ ```
212
+
213
+ If successful, your MCP server will automatically use these credentials.
214
+
215
+ **Why this is better:**
216
+ - ✅ More secure (credentials not in plain text config files)
217
+ - ✅ AWS best practice
218
+ - ✅ Credentials stored in `~/.aws/credentials` (encrypted by OS)
219
+ - ✅ Works across all AWS tools automatically
220
+ - ✅ Easier credential rotation
221
+
222
+ **Note:** The server uses AWS SDK's default credential chain, which will automatically use credentials from `~/.aws/credentials` configured via `aws configure`.
223
+
224
+ ### Step 4: Set IAM Permissions
225
+
226
+ Ensure your AWS credentials have permission to read the secret:
227
+
228
+ ```json
229
+ {
230
+ "Effect": "Allow",
231
+ "Action": ["secretsmanager:GetSecretValue"],
232
+ "Resource": "arn:aws:secretsmanager:us-east-1:account:secret:your-project/salesforce-credentials-*"
233
+ }
234
+ ```
235
+
236
+ Replace `us-east-1` with your region, `account` with your AWS account ID, and `your-project/salesforce-credentials` with your secret name.
237
+
238
+ ### Step 5: Restart Your AI Client
239
+
240
+ Save the MCP config file and restart your AI client completely.
241
+
242
+ **That's it!** The server will automatically fetch credentials from AWS Secrets Manager when you use Salesforce tools.
243
+
244
+
@@ -0,0 +1,25 @@
1
+ export interface Config {
2
+ apiKey: string;
3
+ enableResources: boolean;
4
+ awsRegion: string;
5
+ awsSalesforceSecretName?: string;
6
+ awsFirebaseSecretName?: string;
7
+ salesforceUsername?: string;
8
+ salesforcePassword?: string;
9
+ salesforceToken?: string;
10
+ salesforceClientId?: string;
11
+ salesforcePrivateKey?: string;
12
+ salesforceLoginUrl?: string;
13
+ firebaseServiceAccountKey?: string;
14
+ firebaseProjectId?: string;
15
+ lemonSqueezyStoreId?: string;
16
+ pollFailedPayments: boolean;
17
+ pollIntervalMinutes: number;
18
+ webhookLogPath?: string;
19
+ webhookPort: number;
20
+ webhookSecret?: string;
21
+ enableNgrok: boolean;
22
+ }
23
+ export declare const config: Config;
24
+ export declare function resetConfig(): void;
25
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB,EAAE,OAAO,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,OAAO,CAAC;CACtB;AAkED,eAAO,MAAM,MAAM,EAAE,MAKjB,CAAC;AAGL,wBAAgB,WAAW,IAAI,IAAI,CAGlC"}
package/dist/config.js ADDED
@@ -0,0 +1,80 @@
1
+ import * as dotenv from "dotenv";
2
+ import * as path from "path";
3
+ import { fileURLToPath } from "url";
4
+ import { lemonSqueezySetup } from "@lemonsqueezy/lemonsqueezy.js";
5
+ import { logger } from "./utils/logger.js";
6
+ // Load .env relative to script location
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = path.dirname(__filename);
9
+ const rootDir = path.resolve(__dirname, "..");
10
+ dotenv.config({ path: path.join(rootDir, ".env") });
11
+ // Validate and initialize Lemon Squeezy SDK
12
+ // Priority: LEMONSQUEEZY_API_KEY (production) > LEMONSQUEEZY_TEST_API_KEY (test)
13
+ function getApiKey() {
14
+ const apiKey = process.env.LEMONSQUEEZY_API_KEY ||
15
+ process.env.LEMON_SQUEEZY_API_KEY ||
16
+ process.env.LEMONSQUEEZY_TEST_API_KEY ||
17
+ process.env.LEMON_SQUEEZY_TEST_API_KEY;
18
+ if (!apiKey) {
19
+ const error = new Error("LEMONSQUEEZY_API_KEY or LEMONSQUEEZY_TEST_API_KEY must be set");
20
+ logger.fatal({
21
+ cwd: process.cwd(),
22
+ envPath: path.join(rootDir, ".env"),
23
+ envKeys: Object.keys(process.env).filter(k => k.includes("LEMONSQUEEZY"))
24
+ }, "API Key missing");
25
+ throw error;
26
+ }
27
+ return apiKey;
28
+ }
29
+ function initializeLemonSqueezy(apiKey) {
30
+ lemonSqueezySetup({
31
+ apiKey: apiKey,
32
+ onError: (error) => logger.error({ error }, "Lemon Squeezy Error"),
33
+ });
34
+ }
35
+ // Lazy initialization - only initialize SDK when config is accessed
36
+ let configInstance = null;
37
+ let sdkInitialized = false;
38
+ function createConfig() {
39
+ const apiKey = getApiKey();
40
+ // Initialize SDK only once
41
+ if (!sdkInitialized) {
42
+ initializeLemonSqueezy(apiKey);
43
+ sdkInitialized = true;
44
+ }
45
+ return {
46
+ apiKey,
47
+ enableResources: process.env.ENABLE_RESOURCES === "true",
48
+ awsRegion: process.env.AWS_REGION || process.env.AWS_DEFAULT_REGION || "us-east-1",
49
+ awsSalesforceSecretName: process.env.AWS_SALESFORCE_SECRET_NAME,
50
+ awsFirebaseSecretName: process.env.AWS_FIREBASE_SECRET_NAME,
51
+ salesforceUsername: process.env.SALESFORCE_USERNAME,
52
+ salesforcePassword: process.env.SALESFORCE_PASSWORD,
53
+ salesforceToken: process.env.SALESFORCE_TOKEN,
54
+ salesforceClientId: process.env.SALESFORCE_CLIENT_ID,
55
+ salesforcePrivateKey: process.env.SALESFORCE_PRIVATE_KEY,
56
+ salesforceLoginUrl: process.env.SALESFORCE_LOGIN_URL,
57
+ firebaseServiceAccountKey: process.env.FIREBASE_SERVICE_ACCOUNT_KEY,
58
+ firebaseProjectId: process.env.FIREBASE_PROJECT_ID,
59
+ lemonSqueezyStoreId: process.env.LEMON_SQUEEZY_STORE_ID,
60
+ pollFailedPayments: process.env.POLL_FAILED_PAYMENTS === "true",
61
+ pollIntervalMinutes: parseInt(process.env.POLL_INTERVAL_MINUTES || "5", 10),
62
+ webhookLogPath: process.env.WEBHOOK_LOG_PATH,
63
+ webhookPort: parseInt(process.env.WEBHOOK_PORT || "3000", 10),
64
+ webhookSecret: process.env.LEMONSQUEEZY_WEBHOOK_SECRET,
65
+ enableNgrok: process.env.ENABLE_NGROK === "true",
66
+ };
67
+ }
68
+ // Export config getter - throws error if API key is missing
69
+ export const config = (() => {
70
+ if (!configInstance) {
71
+ configInstance = createConfig();
72
+ }
73
+ return configInstance;
74
+ })();
75
+ // Export function for testing that allows overriding
76
+ export function resetConfig() {
77
+ configInstance = null;
78
+ sdkInitialized = false;
79
+ }
80
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.d.ts","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,55 @@
1
+ import { describe, it, expect, beforeEach } from "vitest";
2
+ import { resetConfig } from "./config.js";
3
+ describe("Config", () => {
4
+ beforeEach(() => {
5
+ // Reset config state before each test
6
+ resetConfig();
7
+ // Clear environment variables
8
+ delete process.env.LEMONSQUEEZY_API_KEY;
9
+ delete process.env.LEMONSQUEEZY_TEST_API_KEY;
10
+ });
11
+ it("should throw error when API key is missing", () => {
12
+ expect(() => {
13
+ // Access config to trigger initialization
14
+ const { config } = require("./config.js");
15
+ // eslint-disable-next-line @typescript-eslint/no-unused-expressions
16
+ config.apiKey;
17
+ }).toThrow("LEMONSQUEEZY_API_KEY or LEMONSQUEEZY_TEST_API_KEY must be set");
18
+ });
19
+ it("should use test API key when provided", () => {
20
+ process.env.LEMONSQUEEZY_TEST_API_KEY = "test_key_123";
21
+ const { config } = require("./config.js");
22
+ expect(config.apiKey).toBe("test_key_123");
23
+ });
24
+ it("should prefer production API key over test key", () => {
25
+ process.env.LEMONSQUEEZY_API_KEY = "prod_key_123";
26
+ process.env.LEMONSQUEEZY_TEST_API_KEY = "test_key_123";
27
+ const { config } = require("./config.js");
28
+ expect(config.apiKey).toBe("prod_key_123");
29
+ });
30
+ it("should parse webhook port from environment", () => {
31
+ process.env.LEMONSQUEEZY_TEST_API_KEY = "test_key";
32
+ process.env.WEBHOOK_PORT = "8080";
33
+ const { config } = require("./config.js");
34
+ expect(config.webhookPort).toBe(8080);
35
+ });
36
+ it("should default webhook port to 3000", () => {
37
+ process.env.LEMONSQUEEZY_TEST_API_KEY = "test_key";
38
+ delete process.env.WEBHOOK_PORT;
39
+ const { config } = require("./config.js");
40
+ expect(config.webhookPort).toBe(3000);
41
+ });
42
+ it("should parse poll interval minutes", () => {
43
+ process.env.LEMONSQUEEZY_TEST_API_KEY = "test_key";
44
+ process.env.POLL_INTERVAL_MINUTES = "10";
45
+ const { config } = require("./config.js");
46
+ expect(config.pollIntervalMinutes).toBe(10);
47
+ });
48
+ it("should default poll interval to 5 minutes", () => {
49
+ process.env.LEMONSQUEEZY_TEST_API_KEY = "test_key";
50
+ delete process.env.POLL_INTERVAL_MINUTES;
51
+ const { config } = require("./config.js");
52
+ expect(config.pollIntervalMinutes).toBe(5);
53
+ });
54
+ });
55
+ //# sourceMappingURL=config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.test.js","sourceRoot":"","sources":["../src/config.test.ts"],"names":[],"mappings":"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"}